← Назад к вопросам

Какие знаешь способы обхода деревьев?

2.0 Middle🔥 241 комментариев
#Основы Go

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Способы обхода деревьев в программировании

В контексте разработки на Go (и в программировании вообще) существует несколько фундаментальных алгоритмов обхода древовидных структур данных. Эти алгоритмы делятся на две основные категории: обходы в глубину (DFS) и обходы в ширину (BFS).

Обход в глубину (Depth-First Search)

DFS исследует ветви дерева максимально глубоко перед переходом к соседним ветвям. Включает три основных варианта, которые отличаются порядком обработки узла (N), левого поддерева (L) и правого поддерева (R):

1. Прямой (Pre-Order) обход: N → L → R

Узел обрабатывается до своих потомков. Полезен для копирования структуры дерева или сериализации.

func preOrder(node *TreeNode) {
    if node == nil {
        return
    }
    fmt.Println(node.Val) // Обработка узла
    preOrder(node.Left)   // Рекурсивный обход левого поддерева
    preOrder(node.Right)  // Рекурсивный обход правого поддерева
}

2. Симметричный (In-Order) обход: L → N → R

Для бинарных деревьев поиска (BST) даёт элементы в отсортированном порядке.

func inOrder(node *TreeNode) {
    if node == nil {
        return
    }
    inOrder(node.Left)    // Рекурсивный обход левого поддерева
    fmt.Println(node.Val) // Обработка узла
    inOrder(node.Right)   // Рекурсивный обход правого поддерева
}

3. Обратный (Post-Order) обход: L → R → N

Узел обрабатывается после потомков. Используется для удаления дерева или вычисления выражений.

func postOrder(node *TreeNode) {
    if node == nil {
        return
    }
    postOrder(node.Left)  // Рекурсивный обход левого поддерева
    postOrder(node.Right) // Рекурсивный обход правого поддерева
    fmt.Println(node.Val) // Обработка узла
}

Обход в ширину (Breadth-First Search)

BFS обрабатывает узлы уровень за уровнем, используя очередь. Эффективен для поиска кратчайшего пути в деревьях.

func bfs(root *TreeNode) {
    if root == nil {
        return
    }
    
    queue := []*TreeNode{root}
    
    for len(queue) > 0 {
        node := queue[0]
        queue = queue[1:]
        
        fmt.Println(node.Val) // Обработка узла
        
        if node.Left != nil {
            queue = append(queue, node.Left)
        }
        if node.Right != nil {
            queue = append(queue, node.Right)
        }
    }
}

Итеративные реализации (без рекурсии)

В Go часто используют итеративные подходы для избежания ограничений глубины рекурсии и контроля памяти.

Пример итеративного in-order обхода с использованием стека:

func inOrderIterative(root *TreeNode) {
    stack := []*TreeNode{}
    curr := root
    
    for curr != nil || len(stack) > 0 {
        // Достигаем крайнего левого узла
        for curr != nil {
            stack = append(stack, curr)
            curr = curr.Left
        }
        
        // Извлекаем и обрабатываем узел
        curr = stack[len(stack)-1]
        stack = stack[:len(stack)-1]
        fmt.Println(curr.Val)
        
        // Переходим к правому поддереву
        curr = curr.Right
    }
}

Практические аспекты в Go

  1. Выбор алгоритма зависит от задачи:

    • Для BST чаще используется in-order
    • Для создания копии дерева — pre-order
    • Для освобождения ресурсов — post-order
    • Для поиска минимального пути — BFS
  2. Производительность:

    • Все алгоритмы имеют сложность O(n) по времени
    • BFS требует O(w) памяти (где w — ширина дерева)
    • DFS требует O(h) памяти (где h — высота дерева)
  3. Параллельные обходы: В Go можно использовать горутины для параллельного обхода независимых поддеревьев, но требуется осторожность с синхронизацией.

Выбор конкретного способа обхода в Go зависит от структуры данных, требований к памяти, необходимости избегать рекурсии (при большой глубине дерева) и конкретной задачи обработки данных.

Какие знаешь способы обхода деревьев? | PrepBro