В математике бинарным (двоичным) деревом называют конечное множество вершин, которое либо пусто, либо состоит из корня и не более чем двух непересекающихся бинарных деревьев, называемых левым и правым поддеревьями данного корня.
Таким образом, каждая вершина бинарного дерева может включать одно или два поддерева или не включать поддеревьев вовсе. Первое поддерево обычно называют левым, а второе - правым. Соответственно ветвь, исходящую из вершины и ведущую в корень левого поддерева, называют левощ а ветвь, ведущую в корень правого поддерева - правой. Вершины, из которых не выходит ни одной ветви, называют листьями (рис. 7.17).
В программах для реализации бинарных деревьев используют п-связные списки. С вершинами бинарного дерева обычно связывают записи, хранящие некоторую информацию.
Построение дерева выполняется следующим образом. Если дерево пусто, то первая же вершина становится корнем дерева. Добавление остальных вершин регламентируется в зависимости от условия задачи: в соответствии с заданной дисциплиной построения дерева отыскивается подходящая вершина, к которой й подсоединяется новая вершина.
Достаточно часто используют регулярные бинарные деревья с разными законами построения. Примером могут служить сортированные бинарные деревья, построение которых осуществляется по правилу: ключевое поле левого поддерева всегда должно содержать значение меньше, чем в корне, а ключевое поле правого поддерева - значение больше или равное значению в корне.
Рассмотрим основные операции с сортированными бинарными деревьями.
Исходные установки. В начале программы необходимо описать элемент и его тип:
Type topjptr=*top; {тип «указатель на вершину»} top=record
value:integer; {число}
left, {указатель на левое поддерево}
right:topj)tr; {указатель на правое поддерево} end;
В статической памяти описываем указатель корня дерева и несколько указателей, используемых при выполнении операций со списком:
Var root, {указатель структуры - адрес корня дерева} pass, next, q:topjptr; {вспомогательные указатели}
Исходное состояние - «пустое дерево»: root:-nil;
Построение дерева. Дерево строится в соответствии с главным правилом. Например, пусть дана последовательность целых чисел {5, 2, 8, 7, 2, 9, 1, 5}. Первое число 5 будет записано в корень дерева (рис. 7.18, а). Второе число 2 меньше значения в корне дерева, следовательно, оно будет записано в левое поддерево (рис. 7.18, б). Следующее число 8 больше значения в корне, соответственно оно будет записано в правое поддерево (рис. 7.18, в). Следующее число 7 больше, чем значение в корне дерева, значит, оно должно быть записано в правое поддерево, но правое поддерево уже построено. Сравниваем 7 со значением в корне правого поддерева - числом 8. Так как добавляемое значение меньше значения в корне правого поддерева, то добав-
ляем левое поддерево уже к этому корню (рис. 7.18, г). Полностью сформированное бинарное дерево представлено на рис. 7.18, д.
Фрагмент программы, реализующий добавление вершины к дереву, состоит из трех частей: создания вершины, поиска корня, к которому можно добавить поддерево, придерживаясь основного правила, и, непосредственно, добавления вершины:
{создание новой вершины}
new(q); {выделяем память для нового элемента} with qA do {заносим значения} begin value—п; left—nil; right.—nil;
end;
{поиск корня для добавляемой вершины}
pass:=root; {начинаем с корня бинарного дерева}
whilepass<>nil do {пока не найдено свободное место} begin next—pass; {сохраняем адрес корня-кандидата}
if q А. value <passA value then pass—pass A. left {влево}
else pass—passA right; {вправо}
end;
{добавление вершины}
if qAvalue<nextA value then {если значение меньше корня} nextA.left:=q {добавляем левое поддерево} else nextA right—q; {добавляем правое поддерево}
Используя рекурсивцость определения дерева, можно построить рекурсивную процедуру добавления вершин к дереву. В качестве параметров эта процедура будет получать указатель на корень дерева и указатель на добавляемый элемент.
Procedure Add(Vdr r:topjptr; pass:topjptr); begin
if r=nil then r.—pass {если место свободно, то добавляем} else {иначе идем налево или направо}
if (passA.value<rA.value) then Add(rA.leftypass)
else Add(r^. right, pass);
end;...
Поиск вершины в сортированном бинарном дереве. Поиск в сортированном бинарном дереве осуществляется следующим образом: вначале значение ключа поиска сравнивается со значением в корне. Если значение ключа в искомой вершине меньше, чем в корневой, то поиск переходит в левую ветвь. Если больше или равно - то в правую ветвь. И так в каждой следующей вершине до тех пор, пока не отыщется искомая. Так, для предыду- щего варианта последовательности поиск вершины с ключом 7 будет выполнен за три шага (рис. 7.19).
Если мы добрались до листа, а искомая вершина не обнаружена, то следует выдать соответствующее сообщение. В противном случае вершину помечают как найденную (запоминают ее адрес) и обрабатывают в соответствии с алгоритмом программы:
pass:=root; {начинаем с корня бинарного дерева}
flag:=false; {признак «вершина не найдена»}
while (pass<>nil) and not flag do {пока не найден элемент или не дошли до листа} if n=passA.value then flag:-true {значение найдено} else
if n<pass\value then pass:-passAleft {влево}
else pass:-passA right; {вправо}
if flag then < вершина найдена>
else <вершина не найдена> ...
Поиск вершины также можно осуществлять, используя рекурсию. Для удобства использования построим рекурсивную функцию, которая будет возвращать true, если элемент найден, и false - в противном случае. Адрес найденного элемента будем возвращать через параметр pass:
Function Find(r:topjptr; Var pass:topjptr; n:integer):boolean;
Begin
ifr=nil then Find:=false {значение не найдено} else
ifn=rA.value then begin
Find:-true; {значение найдено} pass:=r; {запомнили адрес} end
else
if n<rA.value then
Find: =Find(r*. left, n) {влево} else Find: =Find(r*.rightfn); {вправо}
End;
Вызывать такую функцию можно непосредственно из условия оператора условной передачи управления или циклов, например:
if Find(r,pass,n) then <вершина найдена, адрес в pass> else < вершина не найдена> ...
Удаление вершины с указанным ключом. Удалению вершины с указанным ключом предшествует ее поиск (см. выше). Непосредственное удаление вершины реализуется в зависимости от того, какая вершина удаляется:
• удаляемая вершина не содержит поддеревьев (лист) - удаляем ссылку на вершину из корня соответствующего поддерева (рис. 7.20);
• удаляемая вершина содержит одну ветвь (рис. 7.21, а): для удаления необходимо скорректировать соответствующую ссылку в корне, заменив адрес удаляемой вершины адресом вершины, из нее выходящей (рис. 7.21, б);
• удаляемая вершина содержит две ветви (рис. 7.22, а): в этом случае нужно найти подходящую вершину, которую можно вставить на место удаляемой, причем эта подходящая вершина должна легко перемещаться. Такая вершина всегда существует: это либо самый правый элемент левого поддерева, либо самый левый элемент правого поддерева удаляемой вершины (рис. 7.22, б).
Ниже представлена рекурсивная процедура удаления вершины с указанным значением (параметры: г - адрес корня дерева, к - значение).
Procedure Delete(var r:top j)tr;k:integer);
{Внутренняя рекурсивная процедура поиска заменяющей вершины в левом поддереве. Параметры: г - адрес корня левого поддерева, q - адрес заменяемой вершины} Procedure del(var r:top_ptr; q:top_ptr); Var ql:topjptr; begin
if r\right=nil then {заменяющая вершина найдена} begin
qA.value:-r\value; {копируем значение} ql:=r;
r:-r\left; {удаляем заменяющую вершину}
Dispose(ql); {освобождаем память} end
else del(r\right,q) {идем по поддереву направо} End; Var q:top_ptr; begin
if r=nil then WriteLnC3nmeHm не найден или дерево пустое *) else {поиск элемента с заданным ключом}
if k<rA.value then {если меньше, то налево} Delete(rAleft,k)
else
if k>rA value then {если больше, то направо} Delete (г*, right, к)
else
begin {элемент найден, его необходимо удалить} {удаление листа или корня с одним поддеревом} if rAright=nil then {нет правого поддерева} begin q:=r; r:=rA.left; Dispose(q); end
else
if rAJeft=nil then {нет левого поддерева} begin
r:-rA, right; Dispose(q);
end
else {удаление корня с двуми поддеревьями}
del(rAleft,r);
end End;...
Сортировка с использованием дерева. Так как дерево формируется по определенным выше правилам, то сортировка по возрастанию осуществляется обходом дерева «слева направо». Обход начинается с самого нижнего левого листа или, если такого листа нет, корня. Вывод значений осуществляется в следующем порядке: сначала выводится значение самого нижнего левого поддерева, затем корня, затем самого нижнего левого поддерева правого поддерева и т.д. (рис. 7.23).
Пример 7.6. Разработать программу сортировки заданной последовательности целых чисел с использованием сортированного бинарного дерева.
Программа должна строить бинарное дерево из вводимых с клавиатуры целых чисел, а затем осуществлять обход дерева для вывода отсортированных данных.
Построение дерева реализуем отдельной подпрограммой, которая будет получать адрес добавляемой вершины и адрес корня бинарного дерева. Поиск корня для добавляемой вершины, как показано выше, будем осуществлять рекурсивно.
Обход дерева «слева направо» также будем осуществлять рекурсивно. Нерекурсивный вариант достаточно сложен, и его использование нецелесообразно.
Полностью текст программы приведен ниже. Program Sort4;
Type topjptr=Atop; {тип "указатель на вершину дерева"} top=record {тип вершины дерева}
value:integer; {целое число}
left, right:topjptr; {указатели на левое и правое поддеревья} end;
Var next number:integer;
r, pass:topjptr; {корень бинарного дерева} {процедура добавления вершины к дереву}
Procedure Add (Var r:topjptr; pass:topjptr); begin
if r=nil then repass {если место свободно, то добавляем} else {иначе идем налево или направо}
if (passAvalue<rA value) then Add(rA.left,pass)
else Add(rA. right,pass);
end;
{процедура сортировки - обход дерева}
procedure Tree(r:topjptr); begin
if r<>nil then
begin {если есть поддерево}
Tree(rA.left); {обход левого поддерева} Write(rA value:4); {вывод значения из корня} Tree(rA right); {обход правого поддерева} end;
end;
{основная программа}
begin
{формирование исходного дерева} WriteLnCBeodume числа); г:-nil;
Read(nextjiumber); while not EOF do
begin
new(pass); {выделяем память для нового элемента} withpassA do {заносим значения} begin
value: =nextjiumber; left: -nil; right: -nil; end;
Add(r, pass); {добавляем элемент к дереву} Read(nextjiumber)
end;
ReadLn;
WriteLnCCopmupoeaHuan последовательность: ');
Tree(r);
End
Оценка вычислительной сложности операций с сортированными бинарными деревьями. Использование сортированных бинарных деревьев достаточно эффективно с точки зрения временных оценок. Оценим вычислительную сложность основных операций с данной структурой.
Операция поиска вершины. Худшим случаем при поиске вершины является случай, когда дерево имеет вид последовательности вершин (рис. 7.24, а, б). В этом случае для поиска вершины в среднем потребуется (п+1)/2 проверок, как при последовательном поиске, например, в массиве, или Ох(п).
Оценку в среднем выполним для сбалансированного дерева (рис. 7.24, в). Поиск вершины в этом случае потребует (log2 п+1)/2 проверок, т. е. получаем вычислительную сложность в среднем Ocp(log2 п).
Операция построения дерева. Худшим случаем при построении дерева является дерево, представляющее собой последовательность вершин, так как при этом поиск вершины, к которой необходимо добавить новую, потребует максимального числа проверок. Количество проверок в этом случае: п-1 - для последнего элемента, 1 - для второго (для первого элемента проверки не
требуется). В среднем необходимо [(п-1)+1]/2 проверок; умножив на п-1 элемент, получаем п(п-1)/2 проверок, или Ох(п2).
Оценку в среднем выполним также на сбалансированном дереве. Количество проверок для поиска родительской вершины составит (log2 (п-1 )+1 )/2; соответственно, умножив на (п-1), получим Ocp(nlog2 п).
Операция обхода дерева в процессе получения сортированной последовательности. Для деревьев обоих видов сложность одинакова, так как для каждой вершины при обходе проверяется наличие левого и правого поддеревьев, т.е. суммарное количество проверок равно 2п. Следовательно, вычислительная сложность операции обхода равна О(п). С учетом построения дерева вычислительная сложность в среднем составит Оср(п log2 п).
0 коммент.:
Отправить комментарий