Struktura_danikh_Ch1
.pdf2Чи порожній список?
3Чи порожній початок списку?
4Чи порожній кінець списку?
5Перейти до початку списку.
6Перейти до кінця списку.
7Перейти до наступного елемента.
8Перейти до попереднього елемента.
9Поточний елемент.
10Вставити елемент перед поточним.
11Вставити елемент після поточного.
12Видалити елемент.
Дії 1, 5, 6, 7, 8, 10, 11, 12 – інструкції; 2, 3, 4 – відношення; 9 - операція. Інструкція “Почати роботу” повертає порожній список.
Відношення “Чи порожній початок списку?” перевіряє, чи є елементи у списку перед поточним.
Відношення “Чи порожній кінець списку?” перевіряє, чи є елементи у списку після поточного.
“Перейти до початку списку” – зробити поточним перший елемент списку.
“Перейти до кінця списку” – зробити поточним останній елемент списку. “Перейти до наступного елемента” – зробити поточним наступний
елемент списку. Якщо кінець списку порожній, то нічого робити не потрібно. “Перейти до попереднього елемента” – зробити поточним попередній
елемент списку. Якщо початок списку порожній, то нічого робити не потрібно. “Поточний елемент” - повертає значення поточного елемента. Список при цьому не змінюється. Якщо список порожній, ця операція повинна давати
відмову.
“Вставити елемент перед поточним” – вставити новий елемент у список перед поточним. Якщо список був порожнім, то новий елемент стає поточним у списку.
“Вставити елемент після поточного” – вставити новий елемент у список після поточного. Якщо список був порожнім, то новий елемент стає поточним у списку.
“Видалити елемент” – видалити поточний елемент. Якщо це не останній елемент, то поточним стає наступний елемент. Якщо це останній елемент, поточним стає попередній елемент. Якщо список складається з одного елемента, список стає порожнім. Якщо список порожній, інструкція повинна давати відмову.
Двопов’язаний список є найбільш загальною з розглянутих рекурсивних структур даних. На базі двопов’язаного списку можна легко реалізувати будьяку з раніше розглянутих структур, окрім класичних списків.
Можлива реалізація двопов’язаного списку з використанням вказівників зображена на рис. 3.20. Елементи списку – це записи з трьох полів: поле даних
53
та вказівники на наступний та попередній елементи. За допомогою вказівників елементи списку з’єднані у ланцюг. У останньому елементі вказівник на наступний елемент дорівнює nil. У першому елементі вказівник на попередній елемент дорівнює nil. Сам список – це запис з трьох вказівників: вказівник на початок списку, вказівник на кінець списку та вказівник на поточний елемент. Якщо список порожній, то елементів немає, а три вказівники дорівнюють nil. На відміну від списків з поточним елементом, у двозв’язних списках вказівник на поточний елемент завжди знаходиться у межах списку і може дорівнювати nil тільки тоді, коли список порожній.
Рисунок 3.20 - Реалізація списку з поточним елементом
Для двопов’язаного списку інтерфейсна частину модуля мовою Паскаль має вигляд:
unit IntDList; |
|
|
interface |
|
|
type dlref = ^dlelem; |
{Вказівник на елемент списку} |
|
dlelem = record |
{Елемент списку} |
|
d: integer; |
|
|
next: dlref |
|
|
end; |
|
|
dlist = record |
{Список} |
|
bg, cur, en: dlref |
|
|
end; |
|
|
procedure Init(var dl: dlist); |
{ Почати роботу } |
|
function Empty(dl: dlist): boolean; |
{ Чи порожній список?} |
|
function EmpBeg(dl: dlist): boolean; |
{ Чи порожній початок списку?} |
|
function EmpEnd(dl: dlist): boolean; |
{ Чи порожній кінець списку?} |
|
procedure First(var dl: dlist); |
{ Встати до початку списку } |
|
procedure Last(var dl: dlist); |
{ Встати до кінця списку } |
|
procedure Next(var dl: dlist); |
{ Перейти до наступного елемента } |
|
procedure Previous(var dl: dlist); |
{ Перейти до попереднього елемента } |
|
function Current(dl: dlist): integer; |
{ Поточний елемент } |
|
procedure InsBefore(var dl: dlist; |
n: integer);{Вставити елемент перед |
|
поточним} |
|
|
procedure InsAfter(var dl: dlist; n: integer);{Вставити елемент після поточного}
54
procedure Delete(var dl: dlist); |
{ Видалити елемент } |
implementation |
|
................. |
|
end. |
|
На рис. 3.21 розглядаються три випадки вставки елемента у список: коли список порожній, коли вказівник на поточний елемент вказує на перший елемент списку та коли цей вказівник вказує у середину списку. Перший випадок зображено на рис. 3.21 а), б), другий - на рис. 3.21 в), г), третій - на рис. 3.21 д), е).
Видалення елемента списку зображено на рис. 3.22. Якщо список порожній, то функція дає відмову. Інакше може існувати чотири випадки: коли список складається з одного елемента, коли вказівник на поточний елемент вказує на перший елемент списку, коли цей вказівник вказує у середину списку та на останній елемент списку. Перший випадок зображено на рис. 3.22 а), б), другий - на рис. 3.22 в), г) ), третій - на рис. 3.22 д), е), четвертий - на рис. 3.22
є), ж).
Рисунок 3.21 - Вставка елемента у двопов’язаний список перед поточним
55
Рисунок 3.22 - Видалення елемента з двопов’язаного списку
56
Питання для самоконтролю
1Дайте визначення лінійного списку.
2Які операції можна виконувати над лінійними списками?
3Як визначається класичний лінійний список?
4Яким чином класичні списки розташовуються у пам’яті?
5Дайте визначення стеку.
6Які операції можна виконувати над стеками?
7Опишіть функції додавання та видалення елемента у стеку.
8Дайте визначення черги.
9Які операції можна виконувати над чергами?
10Опишіть функції додавання та видалення елемента у черзі.
11Дайте визначення деку.
12Які операції можна виконувати над деком?
13Дайте визначення списку з поточним елементом.
14Які операції можна виконувати над списком з поточним елементом?
15Опишіть функції додавання та видалення елемента у списку з поточним елементом.
16Дайте визначення кільцевого списку.
17Які операції можна виконувати над кільцевим списком?
18Опишіть функції додавання та видалення елемента у кільцевому
списку.
19Дайте визначення двопов’язаного списку.
20Які операції можна виконувати над двопов’язаним списком?
21Опишіть функції додавання та видалення елемента у двопов’язаному списку.
57
РОЗДІЛ 4 ДЕРЕВА
Стеки, черги та деки, списки є лінійними або одновимірними структурами даних. Дерева є прикладом плоских або двовимірних структур.
Деревом називають пов’язаний граф з одним джерелом та напівступенем входу всіх вершин не більше 1. Дерева зображують, починаючи від джерела, вниз. Стрілки у дугах, як правило, опускають (рис. 4.1).
Рисунок 4.1 - Дерево
Єдине джерело називають коренем дерева. Стоки у дереві називають листям дерева. Будь-який шлях у дереві називають гілкою дерева. Будь-яка частина дерева, яка сама є деревом, називається піддеревом. Вершини, які безпосередньо виходять з даної, називаються синами даної вершини, а сама ця вершина – їх батьком. Синами називають також не тільки самі вершини, що безпосередньо виходять з даної, але й піддерева, для яких ці вершини є коренями. Будь-які два сини однієї вершини називають братами. Дерево, яке не містить вершин, називається порожнім деревом. Висотою дерева називають найдовший шлях (найдовшу гілку) у дереві.
4.1 Бінарне дерево
Бінарним деревом називається дерево з напівступенем виходу всіх вершин не більше 2. Впорядкованим бінарним деревом називається бінарне дерево, кожна вершина якого завжди має 2 сини: лівий син та правий син, які можуть бути порожніми або непорожніми деревами (рис. 4.2). Далі розглядаються тільки впорядковані бінарні дерева.
58
Рисунок 4.2 - Впорядковане бінарне дерево
Операції, відношення та інструкції для бінарних дерев:
1Почати роботу.
2Чи порожнє дерево?
3Створити дерево.
4Корінь дерева.
5Лівий син.
6Правий син.
Дії 1, 3, 4, 5, 6 – операції; 2 – відношення.
Операція “Почати роботу” повертає порожнє дерево.
“Створити дерево” – за двома деревами t1, t2 та даними n створити бінарне дерево з коренем з навантаженням n, лівим сином t1 та правим сином t2.
“Корінь дерева” повертає навантаження кореня дерева. Дерево при цьому не змінюється. Для порожнього дерева ця операція повинна давати відмову.
“Лівий син” повертає піддерево, яке є лівим сином дерева. Лівий син порожнього дерева за означенням – порожнє дерево.
“Правий син” повертає піддерево, яке є правим сином дерева. Правий син порожнього дерева за означенням – порожнє дерево.
Можлива реалізація бінарного дерева з використанням вказівників зображена на Рис. 4.3. Вершини дерева – це записи з трьох полів: поле даних та вказівники на лівого та правого синів. За допомогою вказівників вершини дерева з’єднані між собою. У листі дерева вказівники на лівого та правого синів дорівнюють nil. Саме дерево – це вказівник на корінь. Якщо дерево порожнє, то вершин немає, а вказівник дорівнює nil.
59
Рисунок 4.3 - Реалізація бінарного дерева
Реалізація бінарних дерев мовою Паскаль наведена у модулі IntBTree. Треба зазначити, що всі дії реалізовані за допомогою функцій. Функції Init, Empty, Root, LeftSon залишаються для самостійної роботи. Функція MakeTree (рис. 4.4) схожа на функцію додавання елемента в класичний список. Функція 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
60
end;
.................
function RightSon(t: btree): btree; begin
if t=nil then RightSon:=nil else RightSon:=t^.rs
end;
end.
Рисунок 4.4 - Створення дерева
Така реалізація бінарних дерев, як і реалізація класичних списків, є дуже простою, але не економить динамічну пам’ять.
Стандартною задачею для бінарних дерев є обхід дерева (обхід всіх вершин дерева) та обробка кожної вершини. Такий обхід, як і більшість задач з використанням дерев, виконується рекурсивними підпрограмами. Виділяють декілька способів обходу бінарних дерев: КЛП (корінь, лівий син, правий син),
61
ЛКП (лівий син, корінь, правий син) та ЛПК (лівий син, правий син, корінь). Під час КЛП-обходу спочатку обробляють корінь дерева, потім проходять лівого сина, а потім – правого сина. У разі ЛКП-обходу спочатку проходять лівого сина, потім обробляють корінь дерева, а потім проходять правого сина. Під час ЛПК-обходу спочатку проходять лівого сина, потім проходять правого сина, а потім обробляють корінь дерева. Послідовність проходження вершин для бінарного дерева, що зображене на рис. 4.2, для різних типів обходу наведена нижче:
КЛП – 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 показують порядок проходу вершин для КЛП-, ЛКПта ЛПК-обходів. Основна програма створює дерево, яке зображено на рис. 4.5, та показує результати.
Рисунок 4.5 - Бінарне дерево, що створює програма 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),',');
62
