Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Информатика_и_Пр_Бизнес_лекции.doc
Скачиваний:
84
Добавлен:
10.05.2015
Размер:
1.21 Mб
Скачать

10.2.1. Основная терминология

Дерево – конечное множество элементов, образующих иерархическую структуру.

Элементы дерева называют вершинами (узлами). Отрезок, соединяющий вершины дерева, называется ветвью (дугой, ребром). Дерево состоит из вершин и ветвей. У дерева есть единственная вершина, называемая корнем его поддеревьев. В корень не входят ветви. Деревья изображаются корнем вверх. Лист – это вершина, из которой не выходят ветви. Уровень вершины – это путь от корня до вершины (количество ветвей). Глубина (высота) дерева – это максимальная длина пути от корня до листьев.

Вершина х называется родителем (отцом) для вершины у, если вершина у находится на один уровень ниже, чем вершина х, и связана с ней. Вершина у называется сыном вершины х. Предки вершины это ее родитель и все родители родителей. Потомки вершины – это все ее сыновья все сыновья сыновей.

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

Двоичное дерево – это дерево, из каждой вершины которого выходит не более двух ветвей. Примером двоичного дерева является генеалогическое дерево – родословная схема. Это дерево показывает всех предков данного лица.

Двоичное дерево поиска – это особым образом организованное двоичное дерево, в котором слева от каждой вершины находятся вершины с меньшим значением ключа данных, а справа – с большим и/ или равным. Предполагается, что данные, хранящиеся в вершинах, можно сравнивать. В дереве поиска могут храниться данные любого типа, допускающие сравнение. Пример дерева поиска приведен на рис. 1.

Рис. 1. Дерево поиска

Дерево поиска используется для быстрого поиска данных. Рассмотрим процесс поиска вершины со значением 4. Зная принцип построения дерева поиска, процесс поиска вершины в дереве становится целенаправленным: 8, 3, 6, 4. Для поиска любого данного в дереве поиска высотой h требуется не более h+1 сравнений. Для полного дерева из n вершин, у которого все вершины имеют двух сыновей, высота выражается формулой [3]:

h=log2n-1

При использовании дерева поиска из 1000000 элементов для поиска элемента потребуется порядка 20 сравнений.

10.2.2. Реализация двоичных деревьев поиска Для реализации дерева поиска используются массивы и связанные указателями элементы [3, 4].

Рассмотрим второй способ реализации дерева. В С++ вершину дерева можно представить как структуру, состоящую из информационного поля и двух указателей на левого и правого сыновей. Например, тип вершины двоичного дерева, загруженного целыми числами, можно объявить следующим образом:

struct element

{

int info; //информационное поле

rec *left; //адрес левого сына

element *right; //адрес правого сына

};

Основные операции над двоичным деревом поиска:

  • поиск вершины;

  • включение вершины;

  • удаление вершины;

  • обход дерева.

Пример программы, выполняющей операции над деревом поиска (добавление вершины, поиск вершины, обход дерева в прямом порядке):

#include <iostream.h>

#include <conio.h>

struct element //тип вершины дерева

{

int info;

element* left,*right;

};

//Поиск вершины по ключу key в дереве с корнем tree

element * seach(element * tree, int key); //возвращает адрес или 0

//Добавление вершины с ключом key в дереве с корнем tree

void insert(element * &tree, int key);

//Вывод дерева с корнем tree в прямом порядке

void view_cab(element * tree)

void main()

{

int n; //количество вершин в дереве

int x; //значение вершины

element * tree; //корень дерева

tree=0; //дерево пустое

cout<<"n "; cin >>n;

//Цикл построения дерева из n вершин

for(int i=1;i<=n; i++)

{

cout<<"x? ";

cin>>x;

insert(tree,x); //добавление вершины

}

view_cab(tree); //вывод дерева в прямом порядке

cout <<endl;

// Поиск вершины

cout<<"x? ";

cin>>x;

if (seach(tree,x) ==0)

cout<<"No"<<endl; //вершина не найдена

else

cout<<"Yes"<<endl;

getch();

}

element * seach(element * tree, int key)

{

element * t; //адрес текущей вершины

//Направленный обход узлов дерева, начиная с корня

t=tree;

while (t!=0 && t->info!=key) //выйти из цикла при совпадении //или после проверки листа

{

if (key<t->info) //поиск слева

t=t->left;

else //поиск справа

t=t->right;

}

return t; //при t=0 адрес не найден

}

void insert(element * &tree, int key)

{

element *p1,*p2; //адреса текущей и родительской вершин

//Создание вершины-листа

element * t= new element;

//Заполнение вершины

t->info=key;

t->left=0; //лист

t->right=0;

//Включение вершины в дерево

if (tree==0) //дерево пустое

tree= t; //сделать вершину корнем

else //дерево не пустое

{

p1=tree;

//Поиск места для вставки – адреса родительской вершины p2

while (p1!=0) //пока дерево (поддерево) не пустое

{

//Обновить указатель p2 перед изменением p1

p2=p1;

if (key<p1->info) //поиск слева

p1=p1->left;

else //поиск справа

p1=p1->right;

}

//Вставить элемент слева или справа от родителя с адресом р2

if (key<p2->info) //включить как левый лист родителя р2

p2->left=t;

else //включить как правый лист родителя р2

p2->right=t);

}

}

void view_cab(element * tree)

{

if( tree!=0) // если дерево не пустое

{

cout<<tree->info<<" "; //посетить корень

//Посетить левое дерево по тому же алгоритму

view_cab(tree->left); //рекурсивный вызов

//Посетить правое дерево по тому же алгоритму

view_cab(tree->right); ; //рекурсивный вызов

}

}

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

void viewcab1(element* tree)

{

int n; //текущее количество адресов в стеке

element* stack[20]; //стек для адресов необработанных правых //поддеревьев

element* p; //адрес текущей вершины дерева

n=0; // стек пуст

p=tree;

while (p!=0) //цикл обхода дерева

{

cout<<p->info<<' ';

if (p->right!=0) //есть правое дерево у текущей вершины

{stack[n]= p->right; n++;} //добавить адрес правого дерева в стек

if (p->left!=0) //есть левое дерево у текущей вершины

p=p->left; //идти к левому дереву

else //нет левого дерева – проверить стек

if (n!=0) // стек не пуст: не все обработано

{

//Взять адрес правого дерева из стека для обработки

p=stack[n-1]; n--;

}

else //стек пуст: все вершины обработаны

p=0;

}

cout<<endl;

}

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

Пример рекурсивной функции симметричного обхода дерева:

void view_acb(element* tree)

{

if( tree!=0)

{

view_acb(tree->left);

cout<<tree->info<<" ";

view_acb(tree->right);

}

}

Алгоритмы других способов обхода деревьев, а также алгоритм удаления вершины из дерева поиска рассмотрены в [1, 3, 4].