- •Очередь
- •Двусвязные списки
- •Пример использования стеков: постфиксная, префиксная и инфиксная записи
- •Бинарныедеревья
- •Общие деревья
- •Реализация деревьев
- •Представление графов
- •Задача нахождения кратчайшего пути
- •Нахождение кратчайших путей между парами вершин
- •Транзитивное замыкание
- •Нахождение центра ориентированного графа
- •Обход ориентированных графов
Бинарныедеревья
Бинарное дерево– это конечное множество элементов, которое или пусто, или содержит один элемент, называемыйкорнемдерева, а остальные элементы множества делятся на два непересекающихся подмножества, каждое из которых само является бинарным деревом. Эти подмножества называютсялевымиправым поддеревьями исходного дерева. Каждый элемент бинарного дерева называетсяузломдерева иливершиной.
Е
сли
А – корень бинарного дерева и В – корень
его левого или правого поддерева, то
говорят, что А –отецВ, а В –левыйилиправый
сынА. Узел, не имеющий
сыновей, называетсялистом.
Узелn1 –предокузлаn2 (аn2
–потомокn1), еслиn1
– либо отецn2, либо отец
некоторого предкаn2.
Например, в дереве, изображенном на
рисунке 1, А – предокG,H– потомок С, а Е не является ни предком,
ни потомком С. Узелn2 –левый
потомокузлаn1,
еслиn2 является либо левым
сыномn1, либо потомком
левого сынаn1. Подобным
образом может быть определенправый
потомок. Два узла называютсябратьями,
если они сыновья одного и того же отца.
Е
сли
каждый узел бинарного дерева, не
являющийся листом, имеет непустые правые
и левые поддеревья, то дерево называетсястрого
бинарным деревом(рис. 2).
Строго бинарное дерево сnлистами всегда содержит 2n-1
узлов.
У
ровеньузла в бинарном дереве может быть
определен следующим образом. Корень
дерева имеет уровень 0, уровень любого
другого узла дерева имеет уровень на 1
больше уровня своего отца. Например, в
бинарном дереве на рис. 1 узел Е – узел
уровня 2, а узел Н – уровня 3.Глубинаиливысотабинарного дерева – это максимальный
уровень листа дерева, что равно длине
самого длинного пути от корня к листу
дерева. Таким образом, глубина дерева
на рис. 1 равна 3.Полное
бинарное дерево уровня n–
это дерево, в котором каждый узел уровняnявляется листом и каждый
узел уровня меньшеnимеет
непустые левое и правое поддеревья
(рис. 3).
Почти полное бинарное дерево– это бинарное дерево, для которого существует такое натуральноеk, при котором
каждый лист в дереве имеет уровень kилиk+1;
если узел дерева имеет правого потомка уровня k+1, тогда все его левые потомки, являющиеся листами, также имеют уровеньk+1 (рис. 4).


Рис. 1. Почти полные бинарные деревья
Узлы почти полного бинарного дерева могут быть занумерованы так, что корню назначается номер 1, левому сыну – удвоенный номер его отца, а правому – удвоенный номер отца плюс единица. При такой схеме нумерации каждому узлу почти полного бинарного дерева присвоен уникальный номер, который определяет позицию узла внутри дерева. Почти полное строго бинарное дерево с nлистами имеет, как и любое другое строго бинарное дерево сnлистами, 2n-1 узлов. Почти полное бинарное дерево сnлистами, не являющееся строго бинарным, имеет 2nузлов. Существуетдваразличных почти полных бинарных деревас n листами: одно из них строго бинарное, а другое – нет. Например, оба дерева с рисунка 4 почти полные и имеют по пять листьев, но левое строго бинарное, а правое – нет. Существует толькооднопочти полное бинарное деревос n узлами. Оно является строго бинарным, еслиnнечетно. Таким образом, дерево на рисунке 4 слева – единственное почти полное бинарное дерево с девятью узлами, оно является строго бинарным, потому что 9 – нечетное число. Дерево на рисунке 4 справа – единственное почти полное бинарное дерево с десятью узлами, оно не является строго бинарным, потому что число 10 четное.
Идеально сбалансированное бинарное дерево– это дерево, у которого число вершин в его левых и правых поддеревьях отличается не более чем на 1. Получить идеально сбалансированное бинарное дерево можно, размещая приходящие значения поровну слева и справа от каждой вершины.
Бинарное дерево – полезная структура данных в тех случаях, когда в каждой точке процесса должно быть принято одно из двух возможных решений.
П
римерс проверкой на дубликаты и построением
упорядоченного бинарного дерева
При вводе последовательности чисел 14 15 4 9 7 18 3 5 16 4 20 17 9 14 5 будет создано дерево, указанное на рисунке 5 и отпечатано, что числа 4, 9, 14 и 5 – дубликаты.
Прохождение (обход)дерева – это посещение каждого из узлов дерева один раз в определенном порядке. В различных случаях используют различные способы обхода. Определим три метода прохождения. В каждом из них не требуется никаких действий для прохождения пустого бинарного дерева. Методы определяются рекурсивно так, что прохождение бинарного дерева требует посещения корня и прохождения его левого и правого поддеревьев. Методы отличаются только порядком, в котором выполняются эти три действия.
Чтобы пройти непустое бинарное дерево в прямом порядке(сверху вниз или просмотр в глубину), нужно выполнить следующие три действия:
попасть в корень;
пройти в прямом порядке левое поддерево;
пройти в прямом порядке правое поддерево.
Для прохождения непустого бинарного дерева в симметричном порядке(слева направо):
пройти в симметричном порядке левое поддерево;
п
опасть
в корень;пройти в симметричном порядке правое поддерево.
Для прохождения непустого бинарного дерева в обратном порядке(снизу вверх):
пройти в обратном порядке левое поддерево;
пройти в обратном порядке правое поддерево;
попасть в корень.
На рисунках 6 и 7 приведены два бинарных дерева и показано их прохождение различными методами.
Многие алгоритмы и процессы, использующие бинарные деревья, распадаются на две фазы. В первой фазе строится бинарное дерево, а во второй оно проходится.

Рис. 2. Прохождение бинарного дерева
Примерсортировки списка чисел в возрастающем порядке.
П
о
мере считывания числа помещаются в
бинарное дерево в следующем порядке:
при сравнении числа с содержимым узла
дерева выбирается левое поддерево, если
данное число меньше содержащегося в
узле, и правое, если оно больше или
равняется содержащемуся в узле. Таким
образом, при вводе последовательности
чисел 14 15 4 9 7 18 3 5 16 4 20 17 9 14 5 будет создано
дерево, указанное на рисунке 8.
Такое бинарное дерево обладает тем свойством, что содержимое каждого узла в левом поддереве узла nменьше, чем содержимое узлаn, и содержимое каждого узла в правом поддереве узлаnбольше или равно содержимому узлаn. Таким образом, при обходе дерева в симметричном порядке числа печатаются в порядке возрастания.
Примерпредставления выражения, содержащего операнды и бинарные операторы в виде строго бинарного дерева.
Корень такого дерева содержит оператор, который должен быть применен к результату вычисления выражений, представленных левым и правым поддеревьями. Узел, представляющий оператор, имеет два непустых поддерева, а узел, представляющий операнд,– два пустых поддерева. Прохождение такого дерева в прямом порядке дает префиксную форму выражения, прохождение в обратном порядке – постфиксную, а в симметричном – почти инфиксную, так как оно не содержит скобок.
Примерсортировки массива с помощью бинарного дерева, реализованного с помощью указателей
//файл tree.h (заголовки)
typedef int elementtype;
struct node
{
elementtype inf;
node *left, *right;
};
node * new_node (elementtype x); //создание нового узла
node * add_node(elementtype x, node *pn); //добавление узла к дереву
voidprint_sim(node*pn);//обход дерева в симметричном порядке
voiddel_tree(node*pn);//удаление всего дерева
//файл tree.cpp (реализация)
#include <iostream.h>
#include "tree.h"
node * new_node (elementtype x)
{
node *ptr;
ptr = new node;
ptr->inf = x;
ptr->left = ptr->right = NULL;
return ptr;
}
node * add_node(elementtype x, node *pn)
{
node *ptr = pn;
if(pn==NULL)returnnew_node(x); //если дерева не существует,
//то создаем его
if(x<pn->inf)//если добавляемое значение меньше
//информационного поля узла
pn->left=add_node(x,pn->left); //добавляем его в левое //поддерево
else//иначе добавляем его в правое поддерево
pn->right=add_node(x,pn->right);
returnptr;
}
void print_sim (node *pn)
{
if(pn->left)//если левое поддерево существует
print_sim(pn->left);//то обходим его
cout<<pn->inf<<' ';//попадаем в корень
if(pn->right)//если существует правое поддерево
print_sim(pn->right);//то обходим и его
}
void del_tree (node *pn)
{
if(pn->left)//если левое поддерево существует
del_tree(pn->left); //удаляем его
if(pn->right)//если существует правое поддерево
del_tree(pn->right);//его тоже удаляем
delete pn;//удаляем корень
}
//файл sort.cpp (использование)
#include <iostream.h>
#include <conio.h>
#include "tree.h"
int main ()
{
int mas[10] = {5, 4, 8, 2, 9, 6, 4, 1, 7, -2};
node *tree = NULL;
int i;
for(i=0; i<10; i++)
tree = add_node (mas[i], tree);
print_sim (tree);
del_tree (tree);
getch();
return 0;
}
Примерсортировки массива с помощью бинарного дерева, реализованного с помощью узлового массива
//файл tree.h (заголовки)
#define MAXLENGHT 100
typedef int elementtype;
struct node //структура узла:
{ elementtype info; //информационное поле
intleft,right;//индексы левого и правого сыновей
};
struct ttree
{ node nodes[MAXLENGHT]; //массив узлов
intptr;//индекс последнего занятого элемента массива
};
void add_node(elementtype x, ttree *pt); //добавление узла к дереву
voidprint_sim(ttree*pt);//обход дерева в симметричном порядке
//файл tree.cpp (реализация)
#include “tree.h”
void add_node(elementtype x, ttree *pt)
{
inttemp= 0;//текущее значение индекса. начинаем с корня дерева
if(pt->ptr< 0)
pt->ptr= 0;//то заполняем первый элемент
else//если массив не пуст
while(temp<=pt->ptr)//просматриваем заполненные элементы
//массива (существующие узлы дерева)
{
if(x<pt->nodes[temp].info) //если добавляемое значение
//меньше инф. поля узла, то
if(pt->nodes[temp].left> 0)//если у него есть лев. сын
temp=pt->nodes[temp].left;//переходим к нему
else//если левого сына у узла нет, присваиваем полю left
//индекс первого незанятого элемента массива
{ pt->nodes[temp].left = ++(pt->ptr);
temp=pt->ptr+1;//переходим к незанятому элементу
}
else//если добавляемое значение не меньше инф. поля узла, то
if(pt->nodes[temp].right> 0) //если есть правый сын
temp=pt->nodes[temp].right;//переходим к нему
else//если правого сына у узла нет, присваиваем полю right
//индекс первого незанятого элемента массива
{ pt->nodes[temp].right = ++(pt->ptr);
temp=pt->ptr+1;//переходим к незанятому элементу
}
}
pt->nodes[pt->ptr].info=x;//заполняем информационное поле
pt->nodes[pt->ptr].left= -1;//поскольку сыновей у нового узла нет,
pt->nodes[pt->ptr].right= -1;//присваиваем ссылочным полям -1
}
void print_sim (ttree *pt)
{ //чтобы можно было вернуться к пройденным вершинам,
//будем записывать их в стек
int i=0, temp = 0;
intstack[MAXLENGHT];//массив для стека
while(1)//цикл для обхода дерева; выход из цикла прописан в теле цикла
{
while(temp>=0)//пока не выйдем за пределы дерева
{
stack[i++] =temp;//записываем индекс узла в стек
temp=pt->nodes[temp].left;//переходим к левому сыну
}
temp=stack[--i];//возвращаемся к корню
if(i<0)//если стек пуст (узлов больше нет),
break; //обход дерева закончен
cout<<pt->nodes[temp].info<<' ';//выводим значение узла
temp=pt->nodes[temp].right;//переходим к правому сыну
}
}
//файл sort.cpp(использование)
#include<iostream.h>
#include <conio.h>
#include “tree.h”
int main()
{
ttree tree;
int mas[MAXLENGHT] = {5, 4, 8, 2, 9, 6, 4, 1, 7, -2};
int i, n;
tree.ptr = -1; //дерево пустое
for (i=0; i<MAXLENGHT; i++)
add_node (mas[i], &tree);
cout<<'\n';
print_sim (&tree);
cout<<'\n';
getch();
return 0;
}
Пример сортировки массива с помощью почти полного бинарного дерева, реализованного с помощью массива
//файл tree.h (заголовки)
#define MAXLENGHT 1000
typedef int elementtype;
structnode//структура узла:
{
elementtypeinfo;//информационное поле
intflag;//поле признака: 1 – узел занят, 0 – узел свободен
};
voidmakenull(node*pt);//обнуление массива – создание пустого дерева
voidadd_node(elementtypex,node*pt);//добавление узла к дереву
voidprint_sim(node*pt);//обход дерева в симметричном порядке
//файл tree.cpp(реализация)
#include “tree.h”
void makenull (node *pt)
{
int i=0;
for(;i<MAXLENGHT;i++)//все узлы дерева свободны
pt[i].flag = 0;
}
void add_node(elementtype x, node *pt)
{
int temp = 1;
while(pt[temp-1].flag)//пока не доберемся до свободного узла
{
if(x<pt[temp-1].info)//если добавляемое значение
//меньше инф. поля узла, то
temp=temp<<1;//переходим к левому сыну
else//иначе
temp= (temp<<1)+1;//переходим к правому сыну
}
pt[temp-1].flag= 1;//устанавливаем флаг занятости
pt[temp-1].info=x;//заполняем информационное поле
}
void print_sim (node *pt)
{
inttemp= 1;// номер текущего узла
while(temp)//пока номер текущего узла не равен нулю
{
while(pt[temp-1].flag) //пока не выйдем за пределы дерева
{
temp=temp<<1;//переходим к левому сыну
}
while(temp%2)//если текущий узел является правым сыном
temp=temp>>1;//поднимаемся к корню поддерева
temp=temp>>1;//возвращаемся к корню
if(temp==0)//если вышли за переделы дерева (номер узла = 0)
break;//обход дерева закончен
cout<<pt[temp-1].info<<' ';//выводим значение узла
temp = (temp<<1)+1; //переходим к правому сыну
}
}
//файл sort.cpp(использование)
#include <iostream.h>
#include <conio.h>
#include “tree.h”
int main()
{
node tree[MAXLENGHT];
int mas[MAXLENGHT] = {5, 4, 8, 2, 9, 6, 4, 1, 7, -2};
int i;
makenull (tree);
for (i=0; i<10; i++)
{
add_node (mas[i], tree);
}
cout<<'\n';
print_sim (tree);
cout<<'\n';
getch();
return 0;
}
Дерево с узлами, один из указателей которого указывает на родителя (например, для обхода дерева в симметричном порядке при построении дерева выражения по инфиксной форме).
Вертикальная печать дерева: поперечное прохождение (прохождение уровней):
инициализация очереди – корень помещается в очередь;
пока очередь не пуста
удалить элемент из очереди
распечатать его
поместить в очередь указатели на левого и правого сыновей, если они есть
Для того, чтобы дерево выглядело деревом, нужно при помещении сыновей в очередь еще рассчитывать их местоположение на экране (сохраняя координаты в той же очереди или в параллельной).
