Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Пособие часть1.doc
Скачиваний:
22
Добавлен:
01.03.2025
Размер:
6.94 Mб
Скачать

3.6. Ссылочная реализация бинарных деревьев

Наиболее понятным и естественным вариантом для бинарных деревьев является ссылочная реализация, однако может быть выполнена и непрерывная реализация, учитывая возможность нумерации узлов бинарных деревьев, рассмотренную в 3.3.2. Поскольку непрерывная реализация используется в одном из способов сортировки, она будет рассмотрена в главе 4.

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

Рис.3.10. Пример бинарного дерева (a) и его ссылочного представления (б)

На рис.3.10 представлен пример ссылочной реализации бинарного дерева. Обратим внимание, что структурно каждый элемент бинарного дерева напоминает элемент двунаправленного списка, который тоже содержит ровно две ссылки. Однако смысл данных ссылок разный, что приводит к различным алгоритмам обработки. Заметим также, что представление бинарного дерева содержит большое количество пустых ссылок, гораздо больше, чем линейные списки. Каждый лист содержит две пустые ссылки, а если дерево не является строго бинарным, то и внутренние узлы могут содержать пустую левую или правую ссылку. К этому обстоятельству мы еще вернемся.

Проанализируем два варианта ссылочной реализации — на основе указателей и на основе массива (вектора).

3.6.1. Ссылочная реализация бинарного дерева на основе указателей

Реализация на основе указателей является простой и естественной, поэтому она наиболее распространена. Каждый узел описывается структурой, которая содержит, кроме данных, указатели на левого и правого сына.

struct node //структура одного узла BT

{type_of_data data; // данные, тип был определен в typedef

node *left_bt,*right_bt;//указатели на левого и правого сына

};

//указатель на узел BT (корень дерева или поддерева)

typedef node* bt;

При таком подходе каждая из базовых функций реализуется буквально «в лоб» в соответствии с приведенной выше спецификацией. Пустому дереву, как обычно, соответствует пустой указатель NULL, все функции-селекторы обязательно проверяют дерево на пустоту.

void error() //небольшая вспомогательная функция

{cerr<<"дерево пусто!!!"; exit(1);}

//базовые функции обработки бинарного дерева

type_of_data root(bt t) //получить значение корня

{ if (t) return t->data; else error();

}

bt left(bt t) //перейти к левому поддереву

{if (t) return t->left_bt; else error();

}

bt right(bt t) //перейти к правому поддереву

{ if (t) return t->right_bt; else error();

}

bt consbt(type_of_data r, bt l_tree, bt r_tree)

//формирование дерева из корня и двух поддеревьев

{ bt t=new node; t->data=r;

t->left_bt=l_tree; t->right_bt=r_tree;

return t;

}

bool isnull(bt t) {return t==NULL;}// проверка на пустоту

Используя базовые функции, реализуем дополнительную функцию — вывод левого скобочного представления.

void out(bt t) // вывод дерева t

{ if (!isnull(t))

{cout << root(t)<<'('; out(left(t));out(right(t));cout<<')';}

}

Например, построим и выведем дерево из рисунка 3.10,а (тип элементов определим с помощью typedef char type_of_data;)

bt t=consbt('a', consbt('b', consbt('d',NULL,NULL),

consbt('e',consbt('g',NULL,NULL), NULL)),

consbt('c',NULL,consbt('f',NULL,NULL)));

out(t);

Дерево формируется «снизу вверх», начиная с листьев, поскольку это самый глубокий уровень вложенности функции consbt. Каждый родитель формируется после того, как сформированы оба его сына. Последним будет сформирован корень.

Для дерева из рисунка 3.10,а узлы будут сформированы в следующей последовательности — f c g e d b a (это легко проверить, добавив в тело функции consbt вывод значения корня).