Часто при обработке представленной в дереве информации требуется обойти некоторым регулярным способом все его вершины. Имеется два естественных стандартных способа обхода деревьев. Каждый из них позволяет линейно упорядочить вершины дерева и тем самым представить его «двумерную структуру» в виде линейной последовательности вершин.
Прямой (префиксный) обход дерева основан на принципе: «сначала родитель, затем дети». Определим индукцией по построению дерева T в определении 10.3 его прямое представление ПР(T) следующим образом.
Задачи
Задача 10.1. Докажите, что если в связном неориентированном графе число вершин равно числу ребер, то можно выбросить одно из ребер так, что после этого граф станет деревом.
Задача 10.2. Пусть G=(V, E) — неориентированное дерево и — произвольная вершина . Докажите, что если для каждого ребра выбрать ориентацию от u к w , если им заканчивается путь из v в w , и ориентацию от w к u , если им заканчивается путь из v в u , то полученный ориентированный граф будет ориентированным деревом с корнем v . Используйте это утверждение для доказательства следующего факта: если в неориентированном дереве G=(V, E) имеется вершина степени d >1 , то в нем имеется по крайней мере d вершин степени 1.
Задача 10.3. Пусть T=(V,E) — это ориентированное дерево с корнем
Для полученного дерева определите прямой , обратный и инфиксный обходы .
Задача 10.10. Постройте дерево и ациклический ориентированный граф , представляющие следующую арифметическую формулу
Особенно важную роль играют упорядоченные деревья второй степени. Их называют двоичными или бинарными деревьями. Определим двоичное дерево как конечное множество элементов – узлов, которое или пусто, или состоит из корня и двух непересекающихся двоичных деревьев, называемых левым и правым поддеревьями данного корня. Заметим, что двоичное дерево не является частным случаем дерева, хотя эти два понятия связаны между собой. Основные отличия между ними: 1) Дерево никогда не бывает пустым, т.е. имеет, по меньшей мере, один узел, а двоичное дерево может быть пустым. 2) Двоичное дерево — упорядоченное дерево, т.е. делается различие между левым и правым поддеревом, даже в том случае, когда узел имеет лишь одного потомка. В графическом изображении дерева (рисунок 4) “наклон” ветвей важен. Реализация таких рекурсивных структур, как двоичные деревья, приводит к использованию ссылок (указателей). Ссылки на пустые деревья будут обозначаться nil. Из определения двоичных деревьев следует естественный способ их описания ( и представления в компьютере ) : для этого достаточно иметь две связи L и R в каждом узле и переменную связи T, которая является указателем на это дерево. Если дерево пусто, то T = nil ; в противном случае T — адрес корня этого дерева, а L и R — указатели соответственно на левое и правое поддеревья этого корня. На языке Паскаль узлы бинарного дерева описываются как записи с одним или несколькими информационными полями и двумя полями – указателями. Если Elem — тип информационной части узлов дерева, то компоненты дерева ( узел и ссылка на узел ) имеют такие типы: type Tree = ^Node; < указатель на узел >Node = record < узел дерева >
Inf : Elem;
L, R : Treeend; Таким образом, дерево на рисунке 4 б) можно представить так, как на рисунке 5. Далее будем иметь дело только с двоичными деревьями, поэтому термин “дерево” будет означать двоичное дерево.
3. Основные операции с двоичными деревьями
3.1. Обход дерева Наиболее распространенная задача обработки древовидных структур – выполнение некоторой определенной операции над каждым элементом дерева. При этом происходит “посещение” всех вершин, т.е. обход дерева. При обходе каждый узел проходится, по меньшей мере, один раз, а, вообще говоря, три раза. Полное прохождение дерева дает линейную расстановку узлов. Если, обходя дерево, обрабатывать вершины при первой встрече, то ( см. рис. 4 б) ) получим последовательность A, B, D, E, C, F ; если при второй встрече, то получим D, B, E, A, C, F ; если при третьей встрече, то получим D, E, B, F, C, A. Эти три способа обхода называются соответственно – обходом сверху вниз (в прямом порядке, префикснымобходом, preorder); – обходом слева направо (в обратном порядке, инфиксным обходом, inorder); – обходом снизу вверх (в концевом порядке, постфиксным обходом, postorder или endorder). Способы прохождения деревьев определяются рекурсивно. Если дерево пусто, то никаких действий не выполняется, в противном случае обход выполняется в три этапа
Постфиксный обход Пройти левое поддерево Пройти правое поддерево Обработать узел Обход слева направо (инфиксный обход) часто используется при сортировке (см. раздел 4 ). Префиксный и постфиксный способы обхода дерева играют важную роль при анализе текстов на языках программирования. Все три метода легко представляются как рекурсивные процедуры. Пример3.1. Префиксный обход дерева : procedure PreOrder(T : Tree);beginif T <> nil thenbegin < операция обработки узла дерева , например, writeln( T^.inf );> PreOrder (T^.L);PreOrder (T^.R)endend; PreOrder>Пример 3.2. Инфиксный обход дерева : procedure InOrder(T : Tree);beginif T <> nil thenbeginInOrder (T^.L); < операция обработки узла дерева , например, writeln( T^.inf );> InOrder (T^.R)endend; Пример3.3. Постфиксный обход дерева : procedure PostOrder(T : Tree);beginif T <> nil thenbeginPostOrder (T^.L);PostOrder(T^.R) < операция обработки узла дерева , например, writeln( T^.inf);> endend; PostOrder> Замечание. Ссылка T передается как параметр — значение, т.е. в процедуре используется ее локальная копия. При реализации нерекурсивных процедур обхода дерева обычно используют вспомогательный стек и операции работы с ним: – очистить стек (создать пустой стек); – проверить, является ли стек пустым; – добавить в стек элемент; – извлечь элемент из стека. В стеке запоминаются ссылки на вершины (поддеревья), обработка которых временно откладывается. Пример 3.4. Описать нерекурсивную процедуру префиксного обхода дерева. Описание вспомогательного стека : type Stack = ^Rec;Rec = record