[ЗМІСТ] [Далі] [Назад] [Початок розділу]
10.5. Дерева та графи
Стеки, черги та деки, списки є лінійними або одновимірними структурами
даних. Дерева та графи є прикладами плоских або двовимірних структур. Дамо
декілка означень.
Орієнтованим графом G називають пару множин V, U
G = (V, U),
де V – множина вершин, а U –
множина дуг. Дуга з’єднує дві вершини графа.
Далі орієнтовані
графи будемо називати просто графами. Приклад графа зображено на Рис. 10.29.
Цей граф має 4 вершини з номерами від 1 до 4. Дуги з’єднують вершини 1 та 2, 2
та 1, 2 та 3, 4 та 4. Вершини графа також називають вузлами.
Рис. 10.29 – Граф
Якщо
дуга u виходить з вершини v1 та входить у вершину v2, то кажуть, що v2 безпосередньо слідує з v1. Позначати це будемо так:
u
v1 ú¾® v2 або просто, v1 ú¾® v2
Шлях
у графі G з вершини v0 у вершину vn – це послідовність вершин v0, v1, v2, ... vn-1, vn така, що
v0 ú¾® v1, v1 ú¾® v2, ..., vn-1 ú¾® vn
Шлях будемо позначати v0 ¾® vn. Якщо існує шлях між вершинами v0 та vn, кажуть,
що vn слідує
з v0. На Рис. 10.29
існують шляхи з 1 до 3, з 1 до 1, з 4 до 4 тощо. Довжина шляху у графі –
це кількість вершин, які входять у шлях.
Шлях
між вершинами v0 та vn
називається циклом, якщо v0 = vn. Граф на Рис. 10.29 має цикли з 1 до 1, з 2 до 2
та з 4 до 4.
Граф G називають незв’язним, якщо існує розбиття множини вершин V на дві множини V1, V2 такі, що:
V1 È V2 = V, V1 Ç V2 = Æ,
u1 u2
"v1 Î V1, v2 Î V2 не існує u1, u2 Î U таких, що v1 ú¾® v2 або v2 ú¾® v1
Іншими словами, граф називають незв’язним, якщо
всі його вершини можна розбити на дві підмножини вершин, між яким не проходить
жодна дуга. Граф на Рис. 10.30 є незв’язним, бо існує розбиття на дві
підмножини вершин {1, 2, 3} та {4}, між якими не проходить жодна дуга.
Граф G називають зв’язним, якщо він не є
незв’язним.
Рис. 10.29 – Розбиття вершин незв’язного графа на дві підмножини
Напівстепінь
входу вершини графа v –
це кількість дуг, які входять у дану вершину. Напівстепінь виходу
вершини графа v – це
кількість дуг, які виходять з даної вершини. Вершина з напівстепінню входу 0 називається джерелом,
а вершина з напівстепінню виходу 0 – стоком.
Часто
вважають, що у вершині графа зберігаються дані. Такі дані називають навантаженням
вершини.
Деревом
називають зв’язний граф з одним джерелом та напівстепінню входу всіх вершин не
більше 1. Дерева зображують, починаючи від джерела, вниз. Стрілки у дугах, як
правило, опускають (Рис. 10.30).
Рис. 10.30 – Дерево
Єдине
джерело називають коренем дерева. Стоки у дереві називають листям
дерева. Будь-який шлях у дереві називають гілкою дерева. Будь-яка
частина дерева, яка сама є деревом, називається піддеревом. Вершини, які
безпосередньо слідують з даної, називаються синами даної вершини, а сама
ця вершина – їх бітьком. Синами називають також не тільки самі вершини,
що безпосердньо слідують з даної, але й піддерева, для яких ці вершини є
коренями. Будь-які два сини однієї вершини називають братами. Дерево,
яке не вміщує вершин, називається порожнім деревом. Висотою дерева
називають довжину найдовшого шляху (найдовшої гілки) у дереві.
Бінарним
деревом називається дерево з напівстепінню виходу всіх вершин не більше 2. Впорядкованим
бінарним деревом називається бінарне дерево, кожна вершина якого завжди має
2 сини: лівий син та правий син, які можуть бути порожніми або
непорожніми деревами (Рис. 10.31). Далі будемо розглядати тільки впорядковані
бінарні дерева.
Рис. 10.31 – Впорядковане бінарне дерево
10.5.1 Бінарне
дерево
Визначимо
операції, відношення та інструкції для бінарних дерев:
1.
Почати
роботу.
2.
Чи
порожнє дерево?
3.
Створити
дерево.
4.
Корінь
дерева.
5.
Лівий
син.
6.
Правий
син.
Дії 1,
3, 4, 5, 6 – операції; 2 – відношення.
Операція
“Почати роботу” повертає порожнє дерево.
“Створити
дерево” – за двома деревами t1, t2 та даними n створити бінарне
дерево з коренем з навантаженням n, лівим сином t1 та правим сином t2.
“Корінь дерева” повертає навантаження кореня дерева. Дерево при
цьому не змінюється. Для порожнього дерева ця операція повинна давати відмову.
“Лівий син”
повертає піддерево, яке є лівим сином дерева. Лівий син порожнього дерева за
означенням – порожнє дерево.
“Правий
син” повертає піддерево, яке є правим сином дерева. Правий син порожнього
дерева за означенням – порожнє дерево.
Можлива
реалізація бінарного дерева з використанням вказівників зображена на Рис.
10.32. Вершини дерева – це записи з трьох полів: поле даних та вказівники на
лівого та правого синів. За допомогою вказівників вершини дерева з’єднані між
собою. У листі дерева вказівники на лівого та правого синів дорівнюють nil. Саме дерево – це вказівник на корінь.
Якщо дерево порожнє, то вершин немає, а вказівник дорівнює nil.
Рис. 10.32 – Реалізація бінарного дерева
Розглянемо
модуль реалізації бінарних дерев у Паскалі. Треба зазначити, що всі дії
реалізовані за допомогою функцій. Функції Init, Empty, Root, LeftSon залишаються для самостійної роботи.
Функція MakeTree (Рис. 10.33)
схожа на функцію додавання елемента у класичний список. Функція RightSon повертає nil для порожнього дерева або вказівник на правого
сина непорожнього.
unit IntBTree;
interface
type btree
= ^telem; {Дерево}
telem
= record {Елемент
дерева}
d: integer;
rs, ls: btree
end;
function Init: btree; { Почати роботу }
function
Empty(t: btree): boolean; { Чи порожнє дерево? }
function
MakeTree(n: integer; t1, t2: btree): btree;{ Створити дерево }
function
Root(t: btree): integer; { Корінь дерева }
function
LeftSon(t: btree): btree; { Лівий син }
function
RightSon(t: btree): btree; { Правий син }
implementation
.................
function
MakeTree(n: integer; t1, t2: btree): btree;
var p:
btree;
begin
new(p);
p^.d:=n;
p^.ls:=t1; p^.rs:=t2;
MakeTree:=p
end;
.................
function
RightSon(t: btree): btree;
begin
if
t=nil then RightSon:=nil
else
RightSon:=t^.rs
end;
end.
Рис. 10.33 – Створення дерева
Наша
реалізація бінарних дерев, як і реалізація класичних списків, є дуже простою,
але не економить динамічну пам’ять.
Стандартною
задачею для бінарних дерев є обхід дерева (обхід всіх вершин дерева) та обробка
кожної вершини. Такий обхід, як і більшість задач з використанням дерев,
виконується рекурсивними підпрограмами. Виділяють декілька способів обходу
бінарних дерев: КЛП (корінь, лівий син, правий син), ЛКП (лівий син, корінь,
правий син) та ЛПК (лівий син, правий син, корінь). При КЛП-обході спочатку
обробляють корінь дерева, потім проходять лівого сина, а потім – правого сина.
При ЛКП-обході спочатку проходять лівого сина, потім обробляють корінь дерева,
а потім проходять правого сина. При ЛПК-обході спочатку проходять лівого сина,
потім проходять правого сина, а потім обробляють корінь дерева. Послідовність
проходу вершин для бінарного дерева, що зображене на Рис. 10.31, для різних типів обходу наведена нижче:
КЛП – 1, 2, 4, 8, 9, 5, 3, 6, 10, 7
ЛКП – 8, 4, 9, 2, 5, 1, 6, 10, 3, 7
ЛПК – 8, 9, 4, 5, 2, 10, 6, 7, 3, 1
Як
приклад використання бінарних дерев наведемо програму, яка підраховує кількість
вузлів дерева а також друкує навантаження вершин дерева у порядку проходу для
різних типів обходу дерева. Функція NodeCount підраховує кількість вузлів
дерева. Процедури PrtKLP, PrtLKP та PrtLPK показують
порядок проходу вершин для КЛП-, ЛКП- та ЛПК-обходів. Основна програма створює
дерево, яке зображено на Рис. 10.34, та показує результати.
Рис. 10.34 – Бінарне дерево, що створює програма BtreeTst
program BtreeTst;
uses IntBTree;
function NodeCount(t: btree): word;
begin
if
Empty(t) then NodeCount:=0
else
NodeCount:=1+NodeCount(LeftSon(t))+NodeCount(RightSon(t))
end;
procedure PrtKLP(t: btree);
begin
if
Empty(t) then write(' ,')
else
begin
write(Root(t),',');
PrtKLP(LeftSon(t));
PrtKLP(RightSon(t))
end
end;
procedure PrtLKP(t: btree);
begin
if
Empty(t) then write(' ,')
else
begin
PrtLKP(LeftSon(t));
write(Root(t),',');
PrtLKP(RightSon(t))
end
end;
procedure PrtLPK(t: btree);
begin
if
Empty(t) then write(' ,')
else
begin
PrtLPK(LeftSon(t));
PrtLPK(RightSon(t));
write(Root(t),',');
end
end;
var t: btree;
begin
t:=MakeTree(1,MakeTree(2,MakeTree(3,Init,Init),MakeTree(4,Init,Init)),
MakeTree(5,MakeTree(6,Init,Init),Init));
writeln;
writeln('Число вузлів ',NodeCount(t));
write('КЛП='); PrtKLP(t); writeln;
write('ЛКП='); PrtLKP(t); writeln;
write('ЛПК='); PrtLPK(t); writeln;
end.
В результаті роботи програми BtreeTst на екрані
буде показано:
Число
вузлів 6
КЛП=1,2,3,
, ,4, , ,5,6, , , ,
ЛКП=
,3, ,2, ,4, ,1, ,6, ,5, ,
ЛПК= ,
,3, , ,4,2, , ,6, ,5,1,
На
завершення розгляду бінірних дерев наведемо файл заголовку модуля бінарного
дерева у мові Сі.
/* Бінарне
дерево btree.h */
typedef
struct telem *btree; /* Дерево */
struct
telem { /* Елемент
дерева */
int d;
btree ls, rs;
};
extern
btree init(); /* Почати роботу */
extern
int empty(btree t); /* Чи порожнє
дерево? */
extern
btree make(int n, btree t1, btree t2); /* Створити дерево */
extern
int root(btree t); /* Корінь
дерева */
extern
btree leftson(btree t); /* Лівий син */
extern
btree rightson(btree t); /* Правий син */
[ЗМІСТ] [Далі] [Назад] [Початок розділу]