- Прямой, обратный и симметричный обходы дерева
- Способы рекурсивного описания:
- Схемы алгоритмов обходов:
- Деревья выражений
- Реализация деревьев
- Дерево
- 5.2 Операции над деревом
- 5.2.1 Вычисление высоты дерева (height)
- 5.2.2 Вычисление размера дерева
- 5.3 Обход дерева
- 5.3.1 Симметричный обход (in-order traversal)
- 5.3.2 Прямой обход (pre-order traversal)
- 5.3.3 Обход в обратном порядке (post-order traversal)
- 5.3.4 Обход дерева по уровням (level traversal)
Прямой, обратный и симметричный обходы дерева
Если сыновья узла упорядочиваются слева направо, такое дерево называется упорядоченным. В противном случае дерево называетсянеупорядоченым.
Для упорядоченных деревьев существует три способа рекурсивного описания. Для данных способов существуют правила:
1. Если дерево T является нулевым, то в список обхода заносится нулевая запись.
2. Если дерево состоит ровно из 1 узла, то в список обхода заносится этот узел.
Способы рекурсивного описания:
- Прямой обход— сначала посещается корень, затем узлы поддерева
- Симметричный обход— сначала посещаются все узлы поддерева t1, затем корень n, затем последовательно в симметричном порядке все узлы поддеревьев t1,…,tk
- Обход в обратном порядке— сначала посещаются в обратном порядке все узлы поддерева t1, затем t2и т.д., последним посещается корень n.
Схемы алгоритмов обходов:
Деревья выражений
Если в каждом узле дерева хранятся некоторые данные, то это значение называется меткой узла. Существуют деревья, метки узлов которых являются числами (операндами), а метки внутренних узлов являются символами математических операций (операторами). Такие деревья называются деревьями выражений. При обходе деревьев выражений составляется список узлов, который можно интерпретировать как запись арифметического выражения. В порядке прямого обхода получается список меток узлов x + a b — c d. Такая форма записи называется префиксной формой выражения. В порядке обратного обхода получается постфиксная форма выражения: a b + c d — x, а в порядке симметричного обхода — инфиксная: (a+b)x(c..d). Вопрос №23. Деревья как АТД, набор операций. Реализация АТД — дерево (с помощью массивов, с использованием списка сыновей). Список операций АТД TREE:
- MAKENULL(T) — создать пустое дерево;
- ROOT(T) — получить метку корня дерева;
- PARENT(n, T) — узнать родителя;
- LEFTMOST_CHILD(n, T) -самый левый сын;
- RIGHT_SIBLING(n, T) — правый брат;
- LABEL(n, T) получить метку узла;
- CREATE(n, T1, T2, . ) — создать дерево из узла-корня и набора поддеревьев.
Рекурсивная функция, которая позволяет обходить дерево в порядке прямого обхода и составлять его список:
Реализация деревьев
Источник
Дерево
Дерево — структура данных, эмулирующая древовидную структуру в виде набора связанных узлов.
┌─────┐ | | Корневой узел └─────┘ / | \ ┌─────┐ ┌─────┐ ┌─────┐ | | | | | | Дочерние узлы └─────┘ └─────┘ └─────┘ / | \ ┌─────┐ ┌─────┐ ┌─────┐ | | | | | | Дочерние узлы дочерних узлов └─────┘ └─────┘ └─────┘ Пример — синтаксическое дерево для выражения 2 * sin(3 * z — 7)
┌─────┐ | * | └─────┘ / \ ┌─────┐ ┌─────┐ | 2 | | sin | └─────┘ └─────┘ | ┌─────┐ | - | └─────┘ / \ ┌─────┐ ┌─────┐ | * | | 7 | └─────┘ └─────┘ / \ ┌─────┐ ┌─────┐ | 3 | | z | └─────┘ └─────┘ Еще один пример — двоичное дерево поиска. Термин «двоичное» означет, что у каждого из узлов есть не более двух дочерних узлов. «Дерево поиска» означает, что все значения в левом поддереве каждого узла меньше или равны значению этого узла, а все значения в правом поддереве — наоборот — больше.
Если в таком дереве нам надо найти значение 7, то нам нужно совершить такие действия:
- Зайти в узел 0. Искомое 7 больше 0, значит нужно продолжить итерирование вправо;
- Зайти в узел 8. Искомое 7 меньше 8, значит нужно продолжить итерирование влево;
- Зайти в узел 5. Искомое 7 больше 5, значит нужно продолжить итерирование вправо;
- Зайти в узел 7. Это и есть искомое значение, можно закончить итерирование.
5.2 Операции над деревом
5.2.1 Вычисление высоты дерева (height)
function height(tree) < // Если дерево пусто, то его высота равна нулю if (!tree) < return 0; > // Иначе высота равна единице плюс максимальная высота из высот левого и правого поддеревьев return 1 + Math.max(height(tree.left), height(tree.right)); > 5.2.2 Вычисление размера дерева
function size(tree) < // Если дерево пусто, то его размер равен нулю if (!tree) < return 0; > // Иначе размер равен сумме единицы и размеров левого и правого поддеревьев return 1 + size(tree.left) + size(tree.right); > 5.3 Обход дерева
Часто возникает задача обхода всех узлов дерева в определенном порядке. Существует два основных способа обхода деревьев:
- Обход в глубину (depth-first) — обход всего поддерева прежде, чем переход к следущему одноуровневому (sibling) поддереву;
- Обход в ширину (breadth-first) — обход всего уровня прежде, чем переход к следущему уровню.
5.3.1 Симметричный обход (in-order traversal)
Симметричный обход — это обход двоичного дерева в глубину, при котором сначала обходится левое поддерево, затем сам текущий узел, а после этого — правое поддерево.
function inOrderTraversal(tree) < // Если дерево пусто, то нужно остановить обход if (!tree) < return; > // Обходим сначала левое поддерево inOrderTraversal(tree.left); // Выводим значение в текущем узле console.log(tree.key); // Затем обходим правое поддерево inOrderTraversal(tree.right); > Например, если у нас есть такое двоичное дерево поиска:
const tree = < key: 0, left: -1>, right: < key: 8, left: < key: 5, left: 1>, right: 7> >, right: 9> > >; То при симметричном обходе мы получим вывод узлов в порядке возрастания.
inOrderTraversal(tree); // -1, 0, 1, 5, 7, 8, 9 5.3.2 Прямой обход (pre-order traversal)
Этот обход отличается от симметричного тем, что обработка значения в текущем узле производится перед обходом левого поддерева. Так же в отличие от симметричного обхода, этот способ определен для всего множества деревьев (не только для двоичных). Главное условие — обработка текущего узла должна произойти раньше обхода поддеревьев.
function preOrderTraversal(tree) < // Если дерево пусто, то нужно остановить обход if (!tree) < return; > // Сначала выводим значение в текущем узле console.log(tree.key); // Затем обходим левое поддерево preOrderTraversal(tree.left); // В конце обходим правое поддерево preOrderTraversal(tree.right); > При прямом обходе того же дерева tree вывод будет таким:
preOrderTraversal(tree); // 0, -1, 8, 5, 1, 7, 9 5.3.3 Обход в обратном порядке (post-order traversal)
Обход в обратном порядке отличается от прямого обхода тем, что обработка узла происходит после обхода левого и правого поддеревьев. Этот способ так же определен на всем множестве деревьев.
function postOrderTraversal(tree) < // Если дерево пусто, то нужно остановить обход if (!tree) < return; > // Сначала обходим левое поддерево postOrderTraversal(tree.left); // Затем обходим правое поддерево postOrderTraversal(tree.right); // В конце выводим значение текущего узла console.log(tree.key); > При обратном обходе дерева tree вывод будет таким:
preOrderTraversal(tree); // -1, 1, 7, 5, 9, 8, 0 5.3.4 Обход дерева по уровням (level traversal)
Обход дерева по уровням — обход в ширину.
В предыдущих случаях (при обходе в глубину) мы неявно использовали некоторую структуру данных для сохранения узлов, в которые нам предстоит зайти в будущем. Эта структура — стек. Каждый раз, когда мы заканчивали работу с текущим узлом, мы возвращались к предыдущему фрейму в стеке вызовов и продолжали обход.
В случае обхода дерева в ширину нам необходимо использовать другую структуру данных — очередь. В этот раз ее использование будет явным.
function levelTraversal(tree) < // Если дерево пусто, то нужно остановить обход if (!tree) < return; > // Создаем очередь и добавляем в нее корневой узел const queue = new ArrayBasedQueue(100); queue.enqueue(tree); // Итерируемся до тех пор, пока очередь не пуста while (!queue.empty()) < // Достаем узел из очереди const node = queue.dequeue(); // Выводим значение узла в консоль console.log(node.key); // Если у узла есть левое поддерево, // добавляем его в очередь if (node.left) < queue.enqueue(node.left); >// То же делаем и с правым поддеревом if (node.right) < queue.enqueue(node.right); >> > При обходе в ширину дерева tree вывод будет таким:
levelTraversal(tree); // 0, -1, 8, 5, 9, 1, 7 Источник