Глава 2 (продолжение 1)
Вверх
Параметризованный класс дерева
Для построения параметризованного класса бинарного дерева будет
template <class DataT> class tree{
Каждый узел дерева будет представлять собой объект типа tree.
Вверх
Добавление элементов в дерево
Для включения в состав дерева новых элементов используется функция stree()
template <class DataT> void tree<DataT> :: stree(
if(!r) { Эта функция вставляет объект в бинарное дерево, отслеживая ссылки на
Если вы попытаетесь создать аналогичную процедуру с использованием
Фактически эта функция сортирует передаваемую ей информацию
Вверх
Прохождение дерева
Для прохождения дерева, построенного с помощью функции styree(),
template <class DataT> void tree<DataT> :: inorder(tree *r)
{
Ниже приведены соответствующие функции для прохождения дерева в нисходящем
template <class DataT> void tree <DataT> ::preorder(tree *r)
{
Теперь рассмотрим короткую но интересную программу, которая строит
template <class DataT> void tree<DataT> :: print_tree(tree *r,int l)
{ Ниже приведен полный листинг программы отображения дерева.
//программа вывода бинарного дерева
#include<iostream.h>
template <class DataT> class tree{
if(!r) {
template <class DataT> void tree<DataT> :: print_tree(tree *r,int l)
{
if(!r) return;
print_tree(r->right,l+1);
{
char s[80];
tree<char> chTree;
do{
if(*s!='.') chTree.stree(chTree.root,NULL,*s);
return 0;
.-.-.-.-.
Вверх
Поиск по дереву
Операции поиска для бинарных деревьев реализуются легко.
template <class DataT>
Эту функцию следует вызывать указывая в качестве аргументов указатель
Вверх
Удаление элемента дерева
К сожалению удаление узла из дерева не столь простой процесс, как
template <class DataT>
if(!r) return r; //элемент не найден
if(r->info==key) {
if(r->left==r->right) {
Эту функцию необходимо вызывать с указателем на начало(корень)
Бинарные деревья представляют собой необычайно мощный, гибкий и
Вверх
Рекомендации для самостоятельной разработки
В первую очередь попробуйте использовать описанные в этой главе контейнерные классы для хранения пользовательских типов данных. Например поэкспериментируйте с построением собственной программы построения почтовых списков рассылки. Например постройте класс почтовых списков, который будет содержать инфу об имени и адресе получателя, и перегружать необходимые операторы. Попробуйте хранить почтовый список в виде связного списка и в виде бинарного дерева.
Попробуйте развить класс бинарных деревьев в соответствии со своими потребностями и расширить его новыми возможностями. Во-первых, по аналогии с классом связных списков можно попробовать отделить объекты дерева от операций, выполняемых над деревом. Для этого можно например определить класс treeob, который будет опреедлять природу объектов, формирующих дерево, а затем построить класс tree, наследующий класс treeob. Кроме того можнжо перегружать операторы ввода и вывода по отношению к классам дерева.
Все пять контейнеров, которые были исследованы в этой главе, являются достаточно простыми и широко распространенными. Однако, существуют и другие типы контейнеров. Попробуйте создать некоторые типы самостоятельно. Для самостоятельной реализации предлагаем вам несколько идей:
Назад |
Начало урока |
Вверх |
Вперед
использоваться следующий шаблон с названием tree :
tree *left;
tree *right;
tree(){root = NULL;}
void stree(tree *r,tree *previous,DataT info);
tree *dtree(tree *r,DataT key);
void preorder(tree *r);
void inorder(tree *r);
void postorder(tree *r);
void print_tree(tree *r,int l);
tree *seach_tree(tree &r,DataT key);
Член info будет содержать информацию, хранящуюся в каждом из узлов.
Для каждого из объектов left будет представлять собой указатель на левое
поддерево, right -указатель на правое поддерево, а root _ указатель
на корень всего дерева. Обратите внимание указатель root не является
необходимым (указатель на корень дерева можно хранить в любом другом месте),
но включен для удобства. Его наличие позволяет определить начало всего
дерева ,находясь в любом из его узлов. В некоторых случаях эта возможность
может оказаться очень удобной.
приведенная ниже :
tree *r,tree *previous,DataT info)
{
if(!r) {
exit(1);
r->left = NULL;
r->right = NULL;
r->info = info;
if(!root) root = r; //первый элемент
else{
else previous->right = r;
return;
if(info < r->info)
элементы дерева и перемещаясь влево и вправо, в зависимости от содержимого
поля info, до тех пор, пока для элемента не будет найдено соответствующее
ему место в иерархии дерева. Функция stree() представляет собой
рекурсивный алгоритм, как и большинство процедур, связанных с деревьями.
итеративных методов, она получится в несколько раз длиннее.
При вызове функции следует указывать следующие аргументы :
указатель на корень дерева или поддерева, в котором должен производиться
поиск, указатель на предыдущий узел, сохраняемая информация.
При первом вызове второй параметр может иметь значение NULL.
прежде, чем поместить ее в дерево. Она представляет собой вариацию
алгоритма сортировки методом вставки, обсуждавшегося в предыдущей главе.
В общем случае ее производительность достаточно хороша, хотя метод быстрой
сортировки все же является наилучшим из универсальных методов ,так как
требует меньше памяти и генерирует меньше избыточных операций для процессора.
Тем не менее, если вам требуется строить дерево от нуля или поддерживать
уже упорядоченное дерево, рекомендуется всегда добавлять новые элементы
с помощью функции stree(), одновременно со вставкой элементов осуществляющей
их сортировку.
с последующей распечаткой поля info каждого из узлов этого дерева
можно использовать нижеприведенную функцию inorder() :
inorder(r->left);
if(r->info) cout<<r->info<<" ";
inorder(r->right);
Эту функцию следует вызывать, указывая в качестве аргумента указатель
на корень поддерева, которое требуется пройти. Если требуется пройти
все дерево, вызовите функцию, указав аргументом указатель на корень дерева.
Выход из этой рекурсивной функции происходит, когда в процессе прохождения
дерева встречается терминальный узел (нулевой указатель).
и восходящем порядках :
if(r->info) cout<<r->info<<" ";
preorder(r->left);
preorder(r->right);
template <class DataT> void tree <DataT> ::postorder(tree *r)
{
postorder(r->left);
postorder(r->right);
if(r->info) cout<<r->info<<" ";
отсортированное дерево, а затем обходит его в последовательном
порядке и выводит на экран. Для того, чтобы программа могла осуществлять
вывод на экран, требуется лишь незначительная модификация функции inorder().
Для того, чтобы распечатанное дерево правильно выглядело на экране,
правое поддерево должно распечатываться раньше левого.(Технически это -
противоположность последовательному прохождению дерева).
Эта новая функция называется print_tree() и приведена ниже :
if(!r) return;
print_tree(r->right,l+1);
for(i=0; i<l; ++i) cout <<" ";
cout<<r->info<<endl;
print_tree(r->left,l+1);
Для того, чтобы хорошо понять принцип ее работы, попробуйте вводить
разнообразные деревья.
#include<stdlib.h>
void print_tree(tree *r,int l);
template <class DataT> void tree<DataT> :: stree(
tree *r,tree *previous,DataT info)
{
exit(1);
r->left = NULL;
r->right = NULL;
r->info = info;
if(!root) root = r; //первый элемент
else{
else previous -> right = r;
return;
if(info< r->info)
stree(r->left,r,info);
else
stree(r->right, r, info)
//отображение дерева
for(i=0; i<l; ++i) cout <<" ";
cout<<r->info<<endl;
print_tree(r->left,l+1);
main()
cin >> s;
chTree.print_tree(chTree.root,0);
Несколько раз выполнив эту программу, вы заметите что некоторые деревья
сбалансированы - каждое их поддерево имеет такую же или почти такую же
высоту, как и любое другое в то время как другие деревья чрезвычайно
далект от этого. Фактически если вы введете следующее дерево :
a,b,c,d ,оно будет выглядеть примерно так же как на рисунке.
Ни каких левых поддеревьев в этом случае не появится. Такое дерево называется
вырожденным (degenerate tree), так как фактически оно выродилось в линейный
список.
Ниже приведен листинг функции, которая возвращает указатель на
узел дерева, который содержит информацию, совпадающую с ключом поиска.
Если искомый элемент не будет найден, функция возвратит NULL.
tree<DataT>*tree<DataT> :: search_tree(tree *r,DataT key)
{
else r = r->right;
if(r==NULL) break;
return r;
на начало поддерева, в котором требуется произвести поиск, и искомую
информацию. Для того, чтобы выполнить поиск по всему дереву, передайте
функции указатель на корневой узел дерева.
поиск по дереву. Удаленный узел может быть корневым, левым или правым.
Кроме того узел может давать начало другим поддеревьям (их может быть
от 0 до 2).Процесс переприсваивания значений указателям представляет
собой рекурсивный алгоритм, приведенный ниже.
tree<DataT> *tree<DataT> :: dtree(tree *r,DataT key)
{
else if(r->left==NULL) {
else if(r->right==NULL) {
else {
while(p->left) p = p->left;
p->left = r->left;
free(r);
if(r==root) root = p2;
return p2;
if(r->info<key) r->right = dtree(r->right,key);
else r->left = dtree(r->left.key);
return r;
поддерева, по которому требуется выполнить поиск информации, которую
нужно удалить. Для того, чтобы выполнить поиск по всему дереву,
необходимо передать функции указатель на корневой узел дерева.
эффективный инструмент. Поскольку при поиске по сбалансированному
бинарному дереву (наихудший случай) выполняется log2n сравнений,
оно по сравнению со связным списком представляет собой структуру,
гораздо более удобную для поиска информации.
Благодаря новым возможностям, которые предоставляют параметризованные классы, усилия, затраченные вами на выполнение этих управжнений, многократно окупятся на протяжении всей вашей карьеры программиста.
Содержание