Алгоритм построения идеально сбалансированного дерева

Идея алгоритма: Построим и выведем на экран бинарное дерево минимальной глубины из n узлов, вычислим его глубину. Значениями узлов будут целые числа, вводимые с клавиатуры. Для получения минимальной глубины при заданном числе узлов надо располагать максимально возможное число узлов на всех уровнях, кроме самого последнего. Это достигается распределением всех поступающих узлов поровну слева и справа от каждого узла. Реализует эту идею рекурсивный алгоритм:

1. Взять один узел в качестве корня;

2. Построить левое поддерево с количеством узлов nL = n div 2 тем же способом;

3. Построить правое поддерево с количеством узлов nR = n-nL-1 тем же способом.

В программе этот алгоритм реализован рекурсивной функцией Tree.

program BinTree; {Построение идеально сбалансированного дерева,
вывод его на экран, вычисление глубины дерева}

type ref = ^Node;

Node= record

Inf: integer;

Left,Right: ref

end;

var Root: ref; {Указатель на корень дерева}

H: integer; {Высота дерева}

n: integer; {Количество узлов}

function Tree(n: integer):ref; {Рекурсивная функция построения дерева из n узлов}

var t: ref; x,nL,nR: integer;

begin

If (n=0) then Tree:=nil

else begin

nL:=n div 2; {Половина узлов – в левом поддереве}

nR:=n-nL-1; {Половина узлов минус корень – в правом поддереве}

read(x);

new(t); {Выделение динамической памяти для узла}

t^.Inf:= x; {Итак, на рекурсивном спуске }

t^.Left :=Tree(nL); {Построение левого поддерева}

t^.Right:=Tree(nR); {Построение правого поддерева}

Tree:= t

end

end; { конец функции Tree }

procedure PrintTree (t: ref; h: integer); {Вывод дерева. Обход дерева справа налево.

t – указатель на поддерево; h – уровень узла поддерева, используется для отступа от левого края экрана}

const blank=' '; {пробелы для отступа каждого уровня}

var i : integer;

begin

if (t<>nil) then begin

PrintTree (t^.Right, h+1);

for i:=1 to h do write(blank); {Отступ на уровень h}

writeln(t^.Inf);

PrintTree (t^.Left, h+1) end

end;

function Height(t:ref):integer; {Определение высоты дерева. Обход дерева снизу вверх}

var hL, hR: integer;

begin

if (t=nil) then Height:= -1 {Если дерево пустое, функция выдает значение –1}

else begin

hL:=Height(t^.Left); {Определение высоты левого поддерева}

hR:=Height(t^.Right); {Определение высоты правого поддерева}

if (hL>hR) then Height:=hL + 1

else Height:=hR + 1

end

end; { конец функции Height}

begin {основная программа}

write('n='); readln(n);

Root:= Tree(n);

PrintTree(Root, 0); {Корень дерева печатается на уровне 0, без отступа от края экрана}

H:=Height(Root); writeln('Высота H=', H)

End.

Если ввести n=7 и значения узлов 1, 2, 3, 4, 5, 6, 7, то получим на экране изображение дерева:

  7 5 6 1 4 2 3 Уровень узла: h=2 h=1 h=2 h=0 h=2 h=1 h=2
  Высота H=2  

Способы обхода дерева

Обходом дерева называется последовательное обращение ко всем узлам. Существует четыре принципа упорядоченного обхода дерева, которые вытекают из его структуры. Как и саму древовидную структуру, их удобно выразить с помощью рекурсии.

Для иллюстрации обхода возьмем дерево, построенное для арифметического выражения
a*b + c/d с двухместными операциями. Замечание: для произвольного дерева обходы будут выполняться точно так же, но будут иметь другую интерпретацию.

 

В этом дереве каждая операция изображается узлом
с операндами в качестве поддеревьев.

 

Обозначим через Root корень дерева, а через L и R – левое и правое поддеревья.

Различные принципы обхода дают различные формы записи выражения.

1) Прямой обход, или обход сверху вниз (Root, L, R – посетить корень, затем левое и правое поддерево)
дает префиксную форму записи выражения, когда знак операции находится перед операндами:
+*ab/cd

2) Обратный обход, или обходснизу вверх (L, R, Root – посетить левое и правое поддерево, затем корень)
дает постфиксную форму записи выражения, когда знак операции находится после операндов:
ab*cd/+
Постфиксная нотация – форма записи математических выражений, в которой операнды расположены перед операторами.

3) Синтаксический обход, или обходслева направо (L, Root, R – посетить левое поддерево, затем корень и правое поддерево)
дает привычную инфиксную форму записи выражения, когда знак операции находится между операндами, к которым эта операция применяется: a*b + c/d

4) Обход справа налево (R, Root, L – посетить правое поддерево, затем корень и левое поддерево) используется для печати дерева.

Опишем обходы в виде рекурсивных процедур с параметром t, означающим ссылку на узел дерева, в частности, корень дерева. Ссылка t передается по значению, так как никаких изменений структуры дерева не происходит. Процедуры обхода вызывают некоторую процедуру OP для выполнения действий в каждом узле, например, печать узла, проверка узла на максимум и т.п.

Способы обхода дерева отличаются:

· порядком просмотра поддеревьев (левое, затем правое или правое, затем левое);

· выполнением операции над текущим узлом (на рекурсивном спуске или на рекурсивном возврате).

procedure Prefix (t: ref); {Обход сверху вниз Root, L, R} begin {дает префиксную запись выражения} if (t<>nil) then begin OP(t); {Выполняется в текущем узле на рекурсивном спуске} Prefix(t^.Left); {Переход в левое поддерево} Prefix(t^.Right) {Переход в правое поддерево} end end; procedure Postfix(t: ref); {Обход снизу вверх L, R Root} begin {дает постфиксную запись выражения} if (t<>nil) then begin Postfix(t^.Left);{Переход в левое поддерево} Postfix(t^.Right);{Переход в правое поддерево} OP(t) {Выполняется в текущем узле на рекурсивном возврате} end end;
procedure Infix(t: ref); {Обход слева направо L, Root< R} begin {дает инфиксную, обычную запись} if (t<>nil) then begin Infix(t^.Left);{Переход в левое поддерево} OP(t); {Выполняется в текущем узле на рекурсивном возврате из левого поддерева перед рекурсивным спуском в правое поддерево} Infix(t^.Right) {Переход в правое поддерево} end end;  
     

Деревья поиска

Двоичные деревья часто используются для представления наборов данных, элементы которого ищутся по уникальному, только им присущему ключу.

Деревом поиска называется такое бинарное дерево, в котором для ключа каждого узла все ключи его левого поддерева меньше ключа узла, а все ключи его правого поддерева больше ключа узла.

Деревья поиска можно использовать для сортировки. Например, для сортировки массива надо построить дерево поиска по значениям элементов массива, а затем обойти дерево слева направо (синтаксический обход), записывая значения узлов дерева в результирующий массив. Скорость поиска в деревьях поиска примерно такая же, что и в отсортированных массивах: O(C×log2n), в худшем случае O(n).

Построение дерева поиска

При построении дерева поиска и при последующем поиске узла по его ключу место каждого ключа можно найти, двигаясь, начиная от корня, и переходя на левое или правое поддерево каждого узла в зависимости от значения ключа.

Начиная с пустого дерева, очередное число ищется в дереве:

- Если число найдено, увеличивается его счетчик появления.

- Если нет, число вставляется в дерево с начальным значением счетчика=1.

Поскольку определение двоичного дерева рекурсивно, то все операции над деревом могут быть реализованы в виде рекурсивных подпрограмм. Замечание: использование рекурсии замедляет работу программы и расходует лишнюю память при её выполнении.

program TreeSearch; {Дерево поиска}

type ref=^node;

node=record

key:integer;

count:integer;

left,right:ref

end;

var Root:ref; k:integer;

procedure Gen(x:integer; var t:ref);{Генерация нового узла}

begin

new(t);

t^.key:=x; t^.count:=1;

t^.left:=nil; t^.right:=nil

end;

procedure Search (x:integer; var t:ref);{Поиск х в дереве t}

begin

if t=nil

then Gen(x,t) {Добавление узла в дерево, т.к. числа x в дереве нет}

else if (x = t^.key)

then t^.count:=t^.count+1 {Число x есть в дереве}

else if (x < t^.key)

then Search(x, t^.left) {Поиск в левом поддереве}

else Search(x, t^.right) {Поиск в правом поддереве}

end;

procedure PrintTree(t:ref; h:integer);{Вывод дерева. Обход дерева справа налево}

var i:integer;

begin

if t<>nil then

begin

PrintTree(t^.right,h+1);

for i:=1 to h do write(' ');

writeln(t^.key, ’_', t^.count);

PrintTree(t^.left, h+1);

end

end;

procedure Infix(t: ref);{Обход дерева слева направо. Дает вывод в отсортированном порядке}

begin

if (t<>nil)

then begin

Infix(t^.Left); {Переход в левое поддерево}

write(t^.key:3);

Infix(t^.Right) {Переход в правое поддерево}

end

end;

Begin

Root:=nil; {Создание пустого дерева}

write('='); readln(k);

while (k<>0) do {При k=0 построение дерева завершается}

begin

Search(k, Root);

write('='); readln(k);

end;

PrintTree(Root,0);{Вывод дерева}

Infix(Root); {Вывод листьев дерева в отсортированном порядке}

End.



/footer.php"; ?>