Вы находитесь на странице: 1из 16

Лабораторная работа №5.

Рекурсивные иерархические
структуры данных: бинарные деревья

1 Цель работы
Получить практические навыки использования структур данных типа
«Упорядоченное бинарное дерево» в разработке приложений.

2 Порядок выполнения работы


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

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

4 Теоретические сведения. Бинарные деревья


4.1. Основные определения
Дерево – это граф T = (V,Е) без циклов, здесь V – множество вершин, Е
– множество ребер. Одна вершина v∈V в дереве выделена, и называется
корневой вершиной. Лист – вершина, не имеющая потомков.
Поддерево. Поддеревом дерева T = (V,Е) называется любое дерево
T'=(V',Е'), для которого выполнены условия:
1. V'⊆V, V'≠∅,
2. Е'⊆Е,
3. ни одна вершина из множества V\V' не является потомком вершины
из множества V'.
Лес – множество непересекающихся деревьев.
Бинарное дерево – дерево, которое либо пусто, либо состоит из узла,
называемого корнем, вместе с двумя двоичными деревьями, называемыми
левым поддеревом и правым поддеревом корня.

Рис. 1. Пример различных бинарных деревьев


Бинарное дерево не является частным случаем дерева. Пустое дерево
– это бинарное дерево, а не дерево в обычном смысле. Также два дерева на
рис. 1 одинаковы в обычном смысле, но являются различными бинарными
деревьями, потому что у одного дерева нет правого поддерева, а у другого –
левого.
Высотой дерева называется длина максимального пути от корня до
вершины-листа.
Бинарное дерево называется полным, если присутствуют все листья
одного уровня, и каждая внутренняя вершина имеет непустые правое и левое
поддеревья.
Дерево называется сбалансированным тогда и только тогда, когда
высоты двух поддеревьев каждой из его вершин отличаются не более чем на
единицу.
Бинарное дерево является сортирующим деревом для элементов
множества S, если значения в его узлах удовлетворяют правилу:
1. значение в узле u < значения в узле v для каждого узла u из левого
поддерева узла v,
2. значение в узле w > значения в узле v для каждого узла w из правого
поддерева узла v, 3. для любого элемента a∈S существует единственный узел
v, такой, что значение в узле v равно a.
Обход дерева – это способ посещения узлов дерева, при котором
каждый узел проходится только один раз.
Между лесом и бинарным деревом существует тесная связь: любой лес
можно представить в виде бинарного дерева.

Рис. 2. Лес, состоящий из двух деревьев


Рассмотрим следующий лес из двух деревьев. Узлы, расположенные на
одном уровне являются «братьями». На рис. 2 они обведены в овалы.
Построим бинарное дерево, в котором соединим «братьев» и разорвем
их связь с «родителем» для всех, кроме первого «ребенка».

Рис. 3. Соответствующее бинарное дерево с корнем в вершине 1

4.2. Операции для работы с сортирующим бинарным деревом


Вершина дерева, как и узел любой динамической структуры, имеет две
группы данных: полезную информацию и ссылки на узлы, связанные с ним.
Для двоичного дерева таких ссылок будет две – ссылка на левое поддерево и
ссылка на правое поддерево. В результате получаем структуру,
описывающую вершину (предполагая, что полезными данными для каждой
вершины является одно целое число):
struct Node
{
int info;
Node *left;
Node *right;
};

4.2.1. Алгоритм добавления вершины


Алгоритм добавления вершины в сортирующее дерево.
Шаг 0. Первый элемент становится коневым.
Шаг 1. Сравнить значение очередного элемента со значением в корне.
Шаг 2. Если значение меньше, то в качестве корня принять левого
потомка корня, иначе правого потомка. Перейти на Шаг 1.
Шаг 3. Если потомка нет, то создать вершину и включить ее в дерево.
Пусть есть множество элементов {3,7,2,9,11,4,33,88,34,6}. Рассмотрим,
как они добавляются сортирующее дерево.

Рис. 4. Добавление первого узла Рис. 5. Добавление второго узла

Рис. 6. Добавление третьего узла Рис. 7. Добавление четвертого узла


Рис. 8. Вид дерева после добавления шести узлов

Рис. 9. Конечный вид дерева

4.2.2. Алгоритм удаления вершины


На подготовительном этапе необходимо найти узел дерева,
содержащий заданное значение или убедиться, что такого нет. Этот процесс
имеет в среднем O(log n) операций сравнения.
2

Предполагаем, что вершина найдена.


Рассмотрим варианты расположения вершины для удаления.
1. Лист.
2. Внутренняя вершина, имеющая одного поддерево.
3. Внутренняя вершина, имеющая оба поддерева. Сюда же входит
случай удаления корня.

Рис. 10. Удаление вершины Рис. 11. Удаление вершины


с одним поддеревом с двумя поддеревьями

Рис. 12 Удаление корня

4.2.3. Реализация операций работы с бинарным деревом


Определим тип данных – вершина дерева:
struct Node
{
int info;
Node *left;
Node *right;
};
Процедура создания узла:
Node* New(int data)
{
Node* V;
V =(Node*)malloc (sizeof Node);
V->info=data;
V->left=V->right=NULL;
return V;
};
Процедура добавления узла:
Node* Add(Node* v, int data)
{
if(v==NULL)// добавление нового
return New(data);
//поиск места для включения узла в дерево
if(v->info>data) v->right=Add(v->right, data);
else v->left=Add(v->left, data);
return v;
};
Удаление узла: 1. Поиск вершины для удаления.
Node* Delete_node(Node* v, int key)
{
Node* q;
// начинаем поиск узла по полю данных
if (v==NULL) return v; // дерево пусто
else if (key > v->info) v->left=Delete_node(v->left, key); // пошли по левой
//ветви
else if (key < v->info) v->right=Delete_node(v->right, key); //пошли по
//правой ветви
else
{
q=v; //нашли и пометили
if (q->right == NULL) // нет правого поддерева
v=q->left;
else
if (q->left == NULL) // нет левого поддерева,
// см. рис.10
v=q->right;
else
Del(&(q->left), &q);//есть оба поддерева,
// копируем данные
delete q; // Удаление
}
return v;
}
2. Вспомогательная процедура копирования данных при удалении вершины.
void Del(Node** r, Node** q) {
if ((*r)->right==NULL) // правого поддерева нет
{
(*q)->info=(*r)->info; // перенос данных
(*q)->data=(*r)->data;
*q=*r;
(*r)=(*r)->left; }
else
Del(&((*r)->right), &(*q));
}
Подсчет узлов:
int Count(Node* v)
{
static int n=0;
if ( !v ) return 0; // лист – окончание рекурсии
Count(v->left); // обход левого поддерева
n++;
Count(v->right); // обход правого поддерева
return n;
};
Печать дерева:
void Print(Node* v)
{
static int n=0;//считаем уровень
if ( !v ) return;// лист – окончание рекурсии
n++;
Print(v->right); // обход правого поддерева
cout << "(Level " << n << ") " << v->info); // вывод информации о вершине
Print(v->left); // обход левого поддерева
n--;
}
Использование:
int _tmain(int argc, _TCHAR* argv[])
{
int i; Node* V=NULL;
int mass[10]={3,7,2,9,11,4,33,88,34,6};
for(i=0;i<10;i++) V=Add(V,mass[i]);
Print(V);
cout << "\ncount= " << Kol(V) << ", h=" << Height(V));
getchar();
return 0;
}

Рис. 13. Результат работы программы

Проблемы Бинарное дерево не является сбалансированным деревом.


Например, при добавлении элементов, которые увеличиваются на единицу,
бинарное дерево вырождается в линейный список. Таким образом, теряются
все преимущества бинарного дерева – более быстрый поиск элементов.
Примерами сбалансированных деревьев являются АВЛ-деревья и
черно-красные деревья.

4.3. Обходы бинарного дерева Обход дерева – это способ


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

4.3.1. Обходы в глубину


Существует три способа обхода дерева в глубину:
1. прямой (корень-лево-право), Pre_order
2. поперечный (лево-корень-право), In_order
3. обратный (лево-право-корень), Post_order.

Рекурсивный алгоритм прямого обхода.


Шаг 1. Посетить корень и вывести значение.
Шаг 2. Обойти левое поддерево.
Шаг 3. Обойти правое поддерево.

Рис. 14. Порядок обхода вершин при прямом обходе

Для дерева, соответствующего множеству {3,7,2,9,11,4,33,88,34,6},


результат прямого обхода 3,2,7,4,6,9,11,11,88,34.
Рекурсивный алгоритм поперечного обхода.
Шаг 1. Обойти левое поддерево.
Шаг 2. Посетить корень и вывести значение.
Шаг 3. Обойти правое поддерево.
Рис. 15. орядок обхода вершин при обратном обходе

Для дерева, соответствующего множеству {3,7,2,9,11,4,33,88,34,6},


результат обратного обхода 2,6,4,3,88,33,11,9,7,3.

4.3.2. Удаление бинарного дерева


Процедуру обратного обхода можно использовать для удаления дерева.
Действительно, узлы посещаются слева направо и снизу вверх.
Сначала посещаются листья, и их можно удалять не нарушая
целостности дерева.
void DelTree(Node **v)
{
if(*v)
{
DelTree(&((*v)->left));
DelTree(&((*v)->right));
free(*v);
}
}

4.3.3. Обход в ширину


Алгоритм обхода дерева в ширину основан на структуре данных
очередь.
Шаг 0. Поместить в очередь корень дерева.
Шаг 1. Изъять из очереди очередную вершину. Поместить в очередь ее
дочерние вершины по порядку слева направо (справа налево).
Шаг 2. Если очередь пуста, то конец обхода, иначе перейти на Шаг 1.
Рис. 16. Порядок обхода вершин при обходе в ширину
Для дерева, соответствующего множеству {3,7,2,9,11,4,33,88,34,6},
результат прямого обхода 3,2,7,4,9,6,11,33,88,34.

Рис. 17. Результат работы программы обходов дерева, соответствующего рис.


9

4.4 Пример реализации бинарного дерева


#include <iostream>
#include <stdlib.h>
#include <time.h>

using namespace std;

struct TNode;

typedef TNode* PNode;

//описание структуры узла дерева


struct TNode
{
int Data;
PNode Left;
PNode Right;
};

//корень дерева
PNode Tree = NULL;

//вставка элемента в дерево


void BuildTree(PNode& Tr, int Data)
{
if (!Tr)
{
Tr = new TNode;
Tr->Data = Data;
Tr->Left = NULL;
Tr->Right = NULL;
}
else
if (Data<Tr->Data)
BuildTree(Tr->Left, Data);
else
BuildTree(Tr->Right, Data);
}

//печать дерева
void PrintTree(PNode Tr, int H)
{
if (Tr)
{
PrintTree(Tr->Right, H+1);
for (int i = 1;i <= H;i++)
cout<<' ';
cout<<Tr->Data<<endl;
PrintTree(Tr->Left, H+1);
}
}

//прямой обход
void PreTrave(PNode Tr)
{
if (Tr)
{
cout<<Tr->Data<<' ';
PreTrave(Tr->Left);
PreTrave(Tr->Right);
}
}

//симметричный обход
void InTrave(PNode Tr)
{
if (Tr)
{
InTrave(Tr->Left);
cout<<Tr->Data<<' ';
InTrave(Tr->Right);
}
}

//обратный обход
void PostTrave(PNode Tr)
{
if (Tr)
{
PostTrave(Tr->Left);
PostTrave(Tr->Right);
cout<<Tr->Data<<' ';
}
}
//поиск элемента в дереве
bool Find(PNode Tr, int key)
{
PNode p = Tr;
while (p)
{
if (key == p->Data)
return true;
if (key < p->Data)
p = p->Left;
else
p = p->Right;
}
return false;
}

//поиск со вставкой
void FindIns(PNode Tr, int key)
{
if (Find(Tr, key))
cout<<"There is such node!\n";
else
BuildTree(Tr, key);
}

//вспомогательная функция удаления вершины,


//у которой более одного потомка
void Del(PNode& Tr,PNode& p)
{
if (!Tr->Left)
{
p->Data = Tr->Data;
p = Tr;
Tr = Tr->Right;
}
else
Del(Tr->Left, p);
}

//поиск с удалением
void Delete(PNode& Tr, int Data)
{
PNode p;
if (!Tr)
cout<<"There is no such element!\n";
else
if (Data < Tr->Data)
Delete(Tr->Left, Data);
else
if (Data > Tr->Data)
Delete(Tr->Right, Data);
else
{
p = Tr;
if (!p->Left)
Tr = p->Right;
else
if (!p->Right)
Tr = p->Left;
else Del(p->Right, p);
}
}

void main()
{
srand((unsigned)time(NULL));
int x, n = 10;
for (int i = 1; i <= n; i++)
{
x = rand() % 100;
cout<<x<<' ';
BuildTree(Tree, x);
}
cout<<"\nBuilt tree:\n";
PrintTree(Tree,0);

cout<<"Up-down\n";
PreTrave(Tree);
cout<<"\nLeft-Right\n";
InTrave(Tree);
cout<<"\nDown-Up\n";
PostTrave(Tree);

cout<<"\nInput number to insert: ";


cin>>x;
FindIns(Tree,x);
PrintTree(Tree, 0);

cout<<"\nInput number to delete: ";


cin>>x;
Delete(Tree, x);
PrintTree(Tree, 0);
}

5 Варианты заданий для самостоятельного решения

Разработать приложение на языке С++ для обработки рекурсивных


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

1. Описать функцию, которая:


а) вставляет узел с записью Е в дерево, если ранее такой не было;
b) удаляет ее, если она уже существует.
2. Описать функцию, которая:
a) определяет, входит ли вершина с записью Е в дерево Т;
b) если входит, то определить количество вхождений;
c) если такая вершина не найдена, то она добавляется.
3. Описать функцию, которая:
a) присваивает параметру Е запись из самого левого листа непустого
дерева Т (лист-вершина, из которого не выходит ни одной ветви);
b) определяет число вхождений записи Е в дерево Т.
4. Элементы вершин дерева – вещественные числа. Описать функцию,
которая
a) удаляет все вершины с отрицательными значениями;
b) находит вершину с максимальным по модулю значением.
5. Элементы вершин дерева – вещественные числа. Описать функцию,
которая:
a) находит максимальное и минимальное значение записей вершин
непустого дерева;
b) печатает записи из всех листьев дерева, за исключением
найденных.
6. Описать функцию, которая формирует из исходного дерева строго
бинарное дерево путем удаления из него листьев, которые являются
единственными сыновьями своего родителя.
7. Описать функцию, которая:
a) удаляет k раз корень дерева (k вводится с клавиатуры);
b) распечатывает получившееся дерево и определяет, является ли оно
строго бинарным.
8. Описать функции Copy(Т,Т1), которая строит Т1 – копию дерева Т, и
Reflect(Т,Т2), которая строит Т2 – зеркальную копию дерева Т.
9. Описать функцию ЕQUAL(T1,T2), проверяющую на равенство
деревья Т1 и Т2 (деревья равны, если ключи и записи вершин одного дерева
равны соответственно ключам и записям другого дерева).
10. Описать функцию, которая определяет, является ли дерево идеально
сбалансированным.
11. Описать функцию, которая:
a) печатает узлы непустого дерева при обходе слева направо;
b) удаляет все листья исходного дерева;
c) печатает модифицированное дерево.
12. Описать функцию, которая:
a) находит в непустом дереве Т длину (число ветвей) пути от корня
до ближайшей вершины с записью Е; если Е не входит в Т, то за ответ
принять -1.
b) определяет максимальную глубину непустого дерева Т, т.е. число
ветвей в самом длинном из путей от корня дерева до листьев.
13. Описать функцию, которая:
a) присваивает переменной b типа char значение:
К - если вершина - корень,
П - если вершина - промежуточная вершина,
Л - если вершина - лист;
b) распечатывает атрибуты всех вершин дерева.
14. Описать функцию, которая:
а) определяет, сколько раз встречается в дереве заданная запись;
b) удаляет все вхождения этой записи.
15. Описать функцию, которая определяет, является ли дерево строго
бинарным или полностью бинарным. Если оно полностью бинарное, то
определить, какого уровня.
16. Описать функцию, которая:
а) заменяет значения всех нетерминальных узлов на среднее
арифметическое их непосредственных потомков;
b) определяет, является ли полученное дерево деревом бинарного
поиска.
17. Описать функцию, которая:
a) создает строго бинарное дерево;
b) определяет в нем общее количество узлов и количество листьев;
c) проверяет справедливость свойства – строго бинарное дерево с n
листьями всегда содержит 2n-1 узлов.
18. Описать функцию, которая определяет, входит ли поддерево Т1 в
дерево Т.
19. Распечатать все элементы заданного непустого бинарного дерева по
уровням: сначала из корня дерева, затем (слева направо) из вершин, дочерних
по отношению к корню, затем (слева направо) из вершин, дочерних по
отношению к этим вершинам, и т.д.
20. Вершины дерева вещественные числа. Описать функцию, которая:
a) вычисляет среднее арифметическое всех вершин дерева;
b) добавляет в дерево вершину со значением, вычисленным в
предыдущей функции.
21. Описать функцию, которая:
a) определяет среднее квадратическое всех узлов дерева;
b) удаляет все узлы, у которых информационное поле больше
найденного значения.
22. Описать функцию, которая вычисляет суммы элементов правого и
левого поддеревьев исходного дерева.
23. Написать программу объединения двух исходных бинарных деревьев
в одно. Объединение происходит обходом деревьев в прямом порядке.
24. Описать функцию обхода бинарного дерева справа налево.
Применить данную функцию для сортировки массива чисел в обратном
порядке и нахождения максимального элемента массива.
25. Написать программу объединения двух исходных бинарных деревьев
в одно. Объединение происходит обходом деревьев в обратном порядке.
26. Написать программу объединения двух исходных бинарных деревьев
в одно. Объединение происходит обходом деревьев в симметричном порядке.
27. В заданном бинарном дереве подсчитать число его листьев и
напечатать их значения:
a) при прямом обходе дерева;
b) при обратном обходе дерева;
c) при симметричном обходе дерева.
28. В заданном бинарном дереве найти первое вхождение заданного
элемента и напечатать пройденные при поиске узлы дерева:
a) при прямом обходе дерева;
b) при обратном обходе дерева;
c) при симметричном обходе дерева.
29. В заданном непустом бинарном дереве найти длину (число ветвей)
пути от корня до ближайшей вершины со значением равным заданному:
a) при прямом обходе дерева;
b) при обратном обходе дерева;
c) при симметричном обходе дерева.
30. В заданном непустом бинарном дереве подсчитать число вершин на
n-ом уровне, считая корень вершиной 0-го уровня.
31. Задано бинарное дерево. Определить, есть ли в этом дереве хотя бы
два одинаковых элемента.
32. Описать функцию, которая определяет количество нетерминальных
узлов дерева и их долю от общего числа узлов.

Вам также может понравиться