Avl — деревья
На прошлом занятии мы изучили упорядоченные бинарные деревья(УБД). Однако при некоторых данных дерево может оказаться вырожденным. Тогда его высота будет n и доступ к данным существенно замедлится.
В этом разделе мы рассмотрим модифицированный класс деревьев, обладающий всеми преимуществами упорядоченных бинарных деревьев и никогда не вырождающихся. Они называются сбалансированными или AVL — деревьями. AVL — деревьями они названы в честь их изобретателей Адельсона — Вельского и Ландиса.
Высотой некоторого бинарного дерева является максимальный уровень его листьев.
Баланс некоторого узла определяется как высота его левого поддерева минус высота его правого поддерева.
Дерево называется сбалансированным тогда и только тогда, когда для каждого узла высота его двух поддеревьев различается не более чем на 1.
АVL — дерево с такими же значениями, как и в узлах предыдущего дерева можно представить так.
Следует заметить, что сбалансированные деревья очень эффективны для поиска. Предположим у нас сбалансированное дерево и упорядоченное бинарное дерево, которое вырождено, оба эти дерева содержат 10000 узлов. Так, например, упорядоченном бинарном дереве следует просмотреть 9999 уровней дерева прежде, чем он найдет самый крайний правый узел, а сбалансированном дереве 8 уровней.
Давайте расставим балансы для каждого узла для следующего дерева.
Все идет хорошо до тех пор пока мы ищем узлы в дереве, но как только мы захотим вставить узел в это дерево или удалить узел из такого дерева, как его сбалансированность нарушится. Давайте при вставке и удалении будет модифицировать дерево, так чтобы сбалансированность его сохранялась.
Мы рассмотрим только вставку в сбалансированное дерево, удаление очень сложное и поэтому его мы рассматривать не будем.
Включение в сбалансированное дерево
- hL = hR: L и R становятся неравной высоты, но критерий сбалансированности не нарушается
- hL < hR: L и R приобретают равную высоту, т.е. сбалансированность даже улучшается
- hL > hR: критерий сбалансированности нарушается, и дерево нужно трансформировать, такая перестройка называется поворотом.
- Прохождение трансформированного дерева в симметричном порядке должно быть таким же, как и для первоначального дерева (т.е. трансформированное дерево должно оставаться деревом бинарного поиска)
- Трансформированное дерево должно быть сбалансированным
Одинарный поворот вправо происходит тогда, когда родительский узел Р и его левый сын LC начинают перевешивать влево после включения узла в позицию X. В результате такого поворота LC замещает своего родителя, который становится правым сыном. Бывая правое поддерево узла LC (ST) присоединяется к Р в качестве левого поддерева. Это сохраняет упорядоченность, так как узлы в ST больше или равны узлу LC, но меньше узла Р. Поворот уравновешивает как родителя, так и его левого сына.
Двойной поворот вправо происходит тогда, когда родительский узел (Р) становится перевешивающим влево, а его левый сын (LC) — перевешивающим вправо. NP — корень правого перевешивающего дерева узла LC. Тогда в результате поворота узел NP замещает родительский узел.
- Следовать по пути поиска, пока не окажется, что ключа нет в дереве
- Включить новый узел и определить новый показатель сбалансированности
- Пройти обратно по пути поиска и проверить показатель сбалансированности у каждого узла
Принцип работы алгоритма показан на рисунках.
Рассмотрим бинарное дерево (а), которое состоит только из двух узлов.
Включение ключа 7 вначале дает несбалансированное дерево (т. е. линейный список). Его балансировка требует однократного — левого поворота, давая в результате идеально сбалансированное дерево (b).
Последующее включение узлов 2 и 1 дает несбалансированное поддерево с корнем 4. Это поддерево балансируется однократным правым поворотом (d).
Далее включение ключа 3 сразу нарушает критерий сбалансированности в корневом узле 5. Сбалансированность теперь восстанавливается с помощью более сложного двукратного поворота направо и налево; результатом является дерево (е).
Теперь при следующем включении потерять сбалансированность может лишь узел 5. Включение узла 6 должно привести двукратному повороту налево и направо.
Давайте напишем программу поиска и вставки узла.
Type node = record
Key: integer;
Left, right: ref;
Bal: -1..1;
End;
procedure search(x: integer; var p: ref; var h: boolean);
var
p1, p2: ref;
begin
if p = nil then
begin <узла нет в дереве; включить его>
new(p);
h := true;
with p^ do
begin
key := x;
count := 1;
left := nil;
right := nil;
bal := 0;
end
end
else
if x < p^.key then
begin
search(x, p^.left, h);
if h then <выросла левая ветвь>
case p^.bal of
1: begin p^.bal := 0; h := false end ;
0: p^.bal := -1;
-1: begin <балансировка>
p1:= p^.left;
if p1^.bal = -1 then
begin <однократный правый поворот>
p^.left := p1^.right;
p1^.right := p;
p^.bal := 0;
p := p1
end
else
begin <двукратный правый - левый поворот>
p2 := p1^.right;
p1^.right := p2^.left;
p2^.left :=p1;
p^.left := p2^.right;
p2^. right := p;
if p2^.bal =-1 then p^.bal:=1 else p^.bal := 0;
it p2^.bal = 1 then p1^.bal := -1 else p1.^bal :=0;
p :=p2;
end;
p^.bal := 0;
h := false;
end
end
end
else
if x > p^.key then
begin
search(x, p^.right, h);
if h then <выросла правая ветвь>
case p^.bal of
-1: begin p^.bal := 0; h :=false; end ;
0: p^.bal := 1;
1: begin <балансировка>
p1 := p^.right;
if р1^.bal =1 then
begin <однократный левый поворот>
p^.right := p1^.left;
p1^.left := p;
p^.bal := 0;
p := p1;
end
else
begin <двукратный левый - правый поворот>
p2 := p1^.left;
p1^.left := p2^.right;
p2^.right := p1;
p^.right := p2^.left;
p2^.left := p;
if p2^.bal = 1 then p^.bal := -1 else p^.bal:=0;
if p2^.bal = -1 then p1^.bal := 1 else p1^.bal: = 0;
p :=p2;
end ;
p^.bal := 0; h:= false;
end
end
end
else
begin
p^.count := p^.count + 1; h :=false;
end
end
Как сделать идеально сбалансированное дерево паскаль
Существует несколько определений того, что такое дерево. Например, дерево – это лес, состоящий из одного связного компонента (т.е. из одного дерева) (рекурсивное определение). Или (с точки зрения теории графов), дерево – это граф без циклов. Рассмотрим деревья с точки зрения структур данных, которые должны использоваться в программах и храниться в памяти.
Деревья или, в более широком смысле, древовидные структуры данных, представляют собой динамические свЯзные структуры, отличающиеся от списков тем, что система связей не носит линейного характера, а образует ветви, подобно природному дереву (откуда и произошло название этой структуры данных).
Эти структуры данных в общем случае можно разделить на две группы, которые отличаются друг от друга способом построения и (как следствие) реализацией процедур обработки – собственно деревья и пирамиды (или «кучи«). Но у обеих этих групп сохраняется общий древовидный характер и часто их объединяют под общим коротким названием «деревья«. Отличие пирамид от деревьев (и значения термина «куча«) будет рассмотрено позже.
Обычно предполагается, что дерево – это неориентированная структура данных, но при реализации связей, аналогичной спискам, направление (т.е. ориентация) задаётся автоматически. Кроме того, некоторые алгоритмы обработки деревьев предполагают, что дерево является ориентированным (что не всегда удобно).
Классификация. Существует очень большое количество видов древовидных структур данных, которые можно классифицировать по нескольким различным признакам. Одно такое разделение уже было приведено – деревья и пирамиды.
Из других признаков классификации следует указать (в 1-ю очередь) число ветвей, отходящих от корня дерева. Деревья могут быть:
— двоичными (или бинарными) – имеющими не более двух ветвей (примеров таких деревьев очень много, они будут приведены позже);
– с числом ветвей больше 2 – часто такие деревья называют мультивариантными (многопутевыми) или K-деревьями (т.е. K-мерными). Примерами могут быть 2-3-деревья (и им подобные), Б-деревья (различные деревья Байера с подвариантами, например, дерево Байера-МакКрейта, Байера-Баума) и т.п. Видов K-деревьев очень много (свыше 40 различных древовидных структур для представления многомерной информации: R-Tree, R+-Tree, Hilbert R-Tree, hB-Tree, hBII-Tree, BV-Tree, BD-Tree, GBD-Tree, G-Tree, SKD-Tree, kd-Tree, kd2B-Tree, BSP-Tree, LSD-Tree, P-Tree, TR-Tree, Cell-Tree, Quadtree, Grid File, Z-Hashing, SS-Tree).
Важным признаком является состояние сбалансированности дерева и способы его достижения. Деревья могут быть:
– идеально сбалансированными (отличие этих двух первых состояний будет рассмотрено позже);
– отличающимися более или менее сильно от сбалансированных и от вырожденных.
Автоматическая балансировка дерева выполняется, например, для АВЛ-деревьев, красно-чёрных деревьев (все эти деревья являются двоичными), Б-деревьев и некоторых других видов деревьев.
Ещё один важный признак классификации – применение деревьев (хотя не всегда из названия дерева следует, что оно применяется именно с этой целью). В качестве примера можно указать деревья поиска или сортировки, но из названия «дерево поиска» не следует, что оно применяется только для поиска.
Отдельные типы деревьев применяются для решения специальных задач, например остовные деревья на графах (каркасы графов, Spanning Tree) или PATRICIA-деревья (Practical Algorithm To Retrieve Information Coded In Alphanumeric), используемые для поиска данных на основе их поразрядного двоичного представления.
Несколько несвязанных между собой деревьев образуют структуру данных, которая называется «лес» (иногда её называют «бор«).
4.1 Двоичные деревья поиска
Ещё раз дадим определение дерева как структуры данных.
Дерево — динамическая нелинейная структура данных, каждый элемент которой содержит собственно информацию (или ссылку на то место в памяти ЭВМ, где хранится информация) и ссылки на несколько (не менее двух) других таких же элементов. Для двоичного (бинарного) дерева таких ссылок две: на правый соседний и левый соседний элементы.
В общем случае данные, хранящиеся в дереве, не обязаны быть упорядочены каким-либо образом. Но часто требуется соблюдение упорядоченности по некоторому принципу. Именно по такому принципу упорядоченности и отличаются «пирамиды» и «деревья«:
– пирамида («куча«) – древовидная структура данных, в которой значения всех узлов, размещённых на одном уровне, больше (или меньше) значений узлов, размещённых на выше лежащем уровне (см. рисунок);
Рисунок 4.1 – Двоичная пирамида
– дерево – древовидная структура данных, в которой значения всех узлов, размещённых правее некоторого узла, больше значений узлов, размещённых левее, причём это справедливо как для всего дерева, так и для любой его части (см. рисунок). Здесь необходимо сделать одно существенное уточнение: по указанному принципу строится дерево, получившее название «двоичного дерева поиска» (Binary Search Tree, BST), т.е. дерево, в котором очень удобно искать данные, причём с высокой эффективностью этого поиска. В дальнейшем под двоичными деревьями будут подразумеваться именно двоичные деревья поиска.
Рисунок 4.2 – Двоичное дерево поиска
Как видно из сравнения рисунков 4.1 и 4.2, структура у пирамиды и дерева практически одинакова, отличаются они только характером размещения данных.
Также на этих рисунках видны и связи между элементами деревьев (узлами), похожие на связи между звеньями списка. Чем же отличается двоичное дерево от двусвязного списка? Иногда ничем, не только от двусвязного, но и от односвязного списка. Основное отличие — у списка соседними являются предшествующий и последующий элементы, и структура линейна; а у двоичного дерева поиска соседними являются элементы с меньшим и большим ключом, и структура, в общем случае ветвящаяся — в виде дерева, откуда она и получила свое название. Кроме того, любая часть дерева по своей структуре повторяет всё дерево в целом, т.е. дерево (любое, не только двоичное) – это рекурсивная структура данных (фактически дерево можно считать фрактальной структурой).
Рисунок 4.3 – Фрактальные деревья
Нужно помнить, что дерево – это только метод логической организации информации в памяти, а память – линейна.
В случае, если информация хранится в произвольном (и неизвестном программисту) месте памяти, а ключ и информация – разные понятия (иногда они могут и совпадать), структура узла дерева может быть представлена следующим образом:
Рисунок 4.4 – Обобщённая структура узла дерева
Этой структуре соответствует набор типов на языке Паскаль:
-
type
- Название темы должно отражать её суть! (Не следует добавлять туда слова «помогите», «срочно» и т.п.)
- При создании темы, первым делом в квадратных скобках укажите область, из которой исходит вопрос (язык, дисциплина, диплом). Пример: [C++].
- В названии темы не нужно указывать происхождение задачи (например «школьная задача», «задача из учебника» и т.п.), не нужно указывать ее сложность («простая задача», «легкий вопрос» и т.п.). Все это можно писать в тексте самой задачи.
- Если Вы ошиблись при вводе названия темы, отправьте письмо любому из модераторов раздела (через личные сообщения или report).
- Для подсветки кода пользуйтесь тегами [code][/code] (выделяйте код и нажимаете на кнопку «Код»). Не забывайте выбирать при этом соответствующий язык.
- Помните: один топик — один вопрос!
- В данном разделе запрещено поднимать темы , т.е. при отсутствии ответов на Ваш вопрос добавлять новые ответы к теме, тем самым поднимая тему на верх списка.
- Если вы хотите, чтобы вашу проблему решили при помощи определенного алгоритма, то не забудьте описать его!
- Если вопрос решён, то воспользуйтесь ссылкой «Пометить как решённый», которая находится под кнопками создания темы или специальным флажком при ответе.
-
info = (* тип хранимых данных *);
ptr = ^node;
node = record
-
key: Integer;
left, right: ptr;
data: ^info
-
tree: ptr;
И на языках Си/Си++:
struct node
<
int key; // Ключ, по которому строится дерево
float data; // Полезные данные (могут быть любого типа)
node *left, *right; // Указатели на соседние узлы
>;
Двоичное дерево, образованное такими элементами, показано ниже.
Рисунок 4.5 – Двоичное дерево
Элемент дерева получил название «узел» (node). Единственный начальный элемент (откуда дерево развивается) – корень (root). Фрагмент (часть) дерева – поддерево или ветвь. Узел, от которого не отходят ветви – конечный или терминальный узел, иногда его называют листом. Как видно на рисунках, узлы располагаются по уровням. Количество уровней – высота дерева (иногда вместо высоты используют термин «глубина«). Поскольку дерево – рекурсивная структура данных, то любой его узел может считаться корнем какой-либо ветви, в том числе пустой.
Области применения деревьев: для хранения информации, как таковой; в процедурах поиска и сортировки; для описания хранения файлов на дисковых носителях; при построении эффективных кодов для сжатия информации (коды Шеннона–Фано и Хаффмэна).
Преимущество двоичных деревьев поиска: в случае, если дерево отсортировано (а это дерево именно такое), то операция поиска выполняется быстро, с эффективностью, близкой к эффективности двоичного поиска.
4.2 Операции с деревьями
Для деревьев имеются те же основные операции, что и для списков – добавление элемента, удаление, прохождение (просмотр) и поиск элементов. Кроме них можно использовать операции принудительной балансировки, подсчёта числа узлов в дереве, измерения высоты дерева, поиска ближайшего общего корня (ближайшего общего предка – Nearest Common Ancestor (nca)) для двух элементов (в многопутевых деревьях – для более чем двух элементов). При балансировке деревьев используются операции поворотов (вращений), простых и двойных, левых и правых.
Поскольку дерево – рекурсивная структура данных, то наиболее эффективными при работе с деревьями оказываются рекурсивные процедуры. Хотя возможно применение и нерекурсивных процедур. Рассмотрим разные варианты построения этих процедур.
4.2.1 Добавление узла в дерево
Рассмотрим принцип построения дерева при занесении в него информации. Пусть последовательно вводятся записи с ключами 70, 60, 85, 87, 90, 45, 30, 88, 35, 20, 86, 82.
2. 60 70, следовательно новый узел будет расположен справа от 70;
Рисунок 4.6 – Процесс построения дерева
При поступлении очередной записи с ключом k, этот ключ сравнивают, начиная с корня, с ключом очередного узла. В зависимости от результата сравнения, процесс продолжают в левой или правой ветви до тех пор, пока не будет достигнут один из узлов, с которым можно связать входящую запись. В зависимости от результата сравнения входящего ключа с ключом этого узла, входящая запись будет размещена слева или справа от него и станет новым терминальным узлом.
Рекурсивная процедура добавления узла в дерево на языке "Паскаль"
-
if r = nil then
begin
-
new(r);
r^.left := nil;
r^.right := nil;
r^.key := newkey;
r^.data := newdata;
if tree = nil then
-
tree := r; (* Первый узел *)
-
if newkey<prev^.key then
-
prev^.left := r
-
prev^.right := r
-
addnode_r(r^.left,r,newkey)
-
addnode_r(r^.right,r,newkey)
Процедура добавляет информацию в двоичное дерево, отслеживая указатели на узлы и выбирая левые или правые ветви, в зависимости от ключа, пока не будет найдено соответствующее место в иерархии дерева.
— указатель на корень дерева или ветви, в котором ищется место для нового узла;
— указатель на предыдущий узел;
— сохраняемые данные или указатель на них.
При первом вызове второй аргумент может быть равен nil.
Фактически этот процесс сортирует ключ, прежде чем добавить узел в дерево. Это вариант алгоритма сортировки методом простых вставок. При построении нового дерева или обслуживании уже упорядоченного дерева рекомендуется добавлять новые узлы именно такой процедурой.
Рассмотренная процедура использует ранее опредёленный указатель на дерево tree, которому необходимо присвоить значение nil перед первым вызовом этой процедуры (поскольку оно проверяется в строке if tree = nil . ). Приведём пример аналогичной функции на языке Си++, у которой такой же указатель является аргументом (это позволяет избежать использования глобальных структур данных):
void add_node(node*& tree, node*& r, node*& prev, node& buf)
if (buf.key < prev->key)
add_node(tree, r->left, r, buf);
add_node(tree, r->right, r, buf);
Аргументы этой функции:
tree – указатель на всё дерево;
r – указатель на некоторый текущий узел;
prev – указатель на узел, «родительский» для r;
buf – буферная структура данных типа node, содержащая вводимые данные.
Перед первым вызовом указатель tree должен быть обнулён, а сам вызов может быть следующим:
tree = NULL;
node buf;
// .
buf.key = . // Ввод данных в буферную область памяти
buf.data = .
// .
add_node(tree,tree,tree,buf);
4.2.2 Прохождение дерева
Прохождение дерева, т.е. последовательное обращение ко всем его узлам может выполняться с разными целями: просмотр (т.е. вывод на дисплей), сохранение в файл, поиск. Наиболее просто реализуется прохождение с целью просмотра.
Порядок следования узлов дерева при его прохождении зависит от того, каким образом нужно организовать доступ к узлам. Процесс получения доступа ко всем узлам — прохождение дерева.
Существует три способа прохождения дерева:
1. Последовательный – дерево проходится, начиная с левой ветви вверх к корню, затем к правой ветви;
2. Нисходящий – от корня к левой ветви, затем к правой;
3. Восходящий – проходится левая ветвь, затем правая, затем корень.
Рисунок 4.7 – Обход дерева
Под корнем здесь понимается как корень всего дерева, так и корень текущей ветви (поддерева).
Последовательный способ удобен для сортировки данных. Хотя двоичное дерево не обязательно должно быть отсортировано (т.е. быть двоичным деревом поиска), во многих практических случаях это требуется. Порядок сортировки дерева определяется методом, которым дерево будет проходиться. Фактически сортировка как таковая для двоичного дерева поиска не требуется, поскольку при последовательном просмотре данные ужЕ оказываются отсортированными по возрастанию.
Нисходящий способ удобен для сохранения дерева (т.е. данных, в нём хранящихся) в файл так, чтобы при последующем считывании была бы полностью восстановлена структура дерева.
Аргумент этих процедур – указатель на корень ветви, которую требуется найти. Если нужно найти все дерево, используется указатель на корень всего дерева. Выход из рекурсивной процедуры происходит, когда встречается терминальный узел.
Аналогичные процедуры на языке Си:
void inorder(node* r)
<
if (r == NULL) return;
inorder(r->left);
cout<<r->key right);
>
void preorder(node* r)
<
if (r == NULL) return;
cout<<r->key left);
preorder(r->right);
>
void postorder(node* r)
<
if (!r) return;
postorder(r->left);
postorder(r->right);
cout<<r->key
4.2.3 Поиск узла в дереве
Поиск узла в дереве (в том числе в двоичном дереве поиска) может выполняться по такому же принципу, что и просмотр дерева, но в этом случае теряется эффективность этой структуры данных, а поиск фактически приобретает линейный характер. Для сохранения эффективности поиска следует или использовать рекурсивную функцию, аналогичную показанным выше inorder, preorder и т.п., преобразовав её так, чтобы её работа (и все рекурсивные вызовы) завершалась после нахождения требуемых данных:
void poisk1(node* r, int skey, node*& f)
<
if (r == NULL || f) return;
if (r->key == skey)
< f = r; return; >// Выход, если нашли
else
if (skey < r->key)
poisk1(r->left, skey, f);
else
poisk1(r->right, skey, f);
>
Третий аргумент node*& f – одновременно узел, в котором находятся найденные данные, и признак успеха поиска. Перед вызовом этой функции соответствующий фактический аргумент должен быть равен нулю.
Рекурсивные процедуры и функции, при всех их достоинствах, обладают одним серьёзным недостатком – возможностью переполнения стека возврата из подпрограмм при большом количестве рекурсивных вызовов (а это может произойти при большом размере структуры данных), поэтому рассмотрим другой вариант реализации поиска – использование нерекурсивной процедуры:
Код |
procedure kolvo(top: pnode; var k: integer); begin if top <> nil then begin kolvo(top^.left, k); inc(k); kolvo(top^.right, k); end; end; |
Код |
type people = record Name: string[NameL]; mark: real; end; massiv = array[1..200] of people; pmassiv = ^massiv; |
Записать в него данные по возрастанию, уничтожить это дерево, и создать новое таким образом:
1. Корень дерева — элемент массива с номером (k div 2);
2. Левый сын — ((k div 2) div 2);
3. Правый сын — ((k div 2) div 2) + (k div 2);
4. Далее с каждым сыном поступаем так же, как с корнем.
У меня не получается реализовать этот алгоритм. Если кто знает как сбалансировать дерево (этим алгоритмом или другим) то прошу помочь.
Если же найдены ошибки в коде или алгоритме прошу исправить.
Профиль
Группа: Участник Клуба
Сообщений: 1189
Регистрация: 16.6.2006
Где: Минск
Репутация: 32
Всего: 61
Профиль
Группа: Участник
Сообщений: 13
Регистрация: 21.12.2006
Репутация: нет
Всего: 2
ВНИМАНИЕ! Прежде чем создавать темы, или писать сообщения в данный раздел, ознакомьтесь, пожалуйста, с Правилами форума и конкретно этого раздела.
Несоблюдение правил может повлечь за собой самые строгие меры от закрытия/удаления темы до бана пользователя!
Если Вам помогли и атмосфера форума Вам понравилась, то заходите к нам чаще! С уважением, Poseidon, Rodman
0 Пользователей читают эту тему (0 Гостей и 0 Скрытых Пользователей) | |
0 Пользователей: | |
« Предыдущая тема | Центр помощи | Следующая тема » |
[ Время генерации скрипта: 0.1039 ] [ Использовано запросов: 21 ] [ GZIP включён ]
Тема: Представление деревьев. Основные операции над деревом.
Дерево – это сложная динамическая структура данных, применяющаяся для эффективного хранения информации.
Очевидно, что для описания требуются ссылки. Опишем, как переменные с фиксированной структурой – сами вершины, тогда степень дерева будет определять число ссылочных компонент, указывающих на вершины поддеревьев. В бинарном дереве их два: левое и правое.
Type TreeLink = ^Tree; Tree = Record Data : <тип данных>; Left, Right : TreeLink; End; |
Корень дерева опишем в разделе описания переменных:
Var kd : TreeLink; |
К основным операциям над деревьями относятся:
-
занесение элемента в дерево;
Рассмотрим вставку и обход дерева на примере следующей задачи.
Задача. Создать и вывести на экран дерево, элементы которого вводятся с клавиатуры и имеют целый тип. Причем для каждой вершины дерева во всех левых вершинах должны находиться числа меньшие, а в правой большие, чем числа, хранящиеся в этой вершине. Такое дерево называется деревом поиска.
Опишем процедуру вставки в дерево новой вершины. При вставке в дерево вершина вставляется либо как поддерево уже существующей вершины или как единственная вершина дерева. Поэтому и левая и правая связи новой вершины должны быть Nil. Когда дерево пусто, значение передаваемое в виде параметра ссылки равно Nil. В этом случае нужно изменить ее так, чтобы она указывала на новую вершину, которая была вставлена как корневая. При вставке второго элемента переданный из основной программы параметр t уже не будет равен Nil, и надо принимать решение о том, в какое поддерево необходимо вставить новую вершину.
Procedure InsTree(n : integer; Var t : TreeLink); Begin if t=nil then begin new(t); with t^ do begin Left := nil; Right := nil; Data := n; end; end; else if n<=t^.data then InsTree(n, t^.Left) else InsTree(n, t^.Right) End; |
Опишем процедуру вывода значений элементов двоичного дерева на экран. Для этого необходимо выполнить полный обход дерева. При обходе дерева его отдельные вершины посещаются в отдельном порядке. Вывод двоичного дерева можно производить рекурсивно, выполняя для каждой вершины три действия:
-
вывод числа, хранящегося в узле;
Порядок выполнения этих действий определяет способ обхода дерева. Способы вывода:
-
прямой вывод (сверху вниз);
Процедура обратного вывода дерева имеет следующий вид:
Procedure PrintTree(t : TreeLink); Begin if t<>Nil then begin PrintTree(t^.Left); Write(t^.Data:3); PrintTree(t^.Right); end; End; |
Задание. Написать процедуры прямого и концевого вывода значений элементов дерева.
Основная программа осуществляет ввод чисел с клавиатуры. Используются: переменная nd типа TreeLink – значение указателя на корень дерева; переменная Digit типа integer для хранения очередного введенного числа.
Begin writeln(‘Вводите вершины дерева. Окончание ввода – 0’); kd := nil; read(Digit); while Digit <> 0 do begin InsTree(Digit, kd); writeln(‘ Введите очередное число ‘); read(Digit); end; PrintTree(kd); End. |
Задание. Напишите программу создания и вывода на экран двоичного дерева, используя свою процедуру вывода. Протестируйте программу и представьте учителю файл и листинг для оценки.