
- •1. Сортування
- •1.1. Сортування масивів
- •1. Сортування простими включеннями;
- •2. Сортування простим вибором;
- •1.1.1. Сортування простими включеннями
- •1.1.2. Сортування простим вибором
- •1.1.3. Сортування простим обміном (засіб “бульбашки”)
- •1.1.4. Шейкер-сортування
- •1.1.5. Швидке сортування
- •1.2. Сортування масива рядків
- •1.3. Сортування файлів
- •2. Рекурсивні алгоритми
- •2.1. Алгоритми з поверненням
- •2.1.1. Шахова задача про хід коня
- •2.1.2. Шахова задача про вісім ферзів
- •3. Динамічні інформаційні структури
- •3.1. Динамічні змінні. Вказівники
- •3.1.1. Засоби створення та використання динамічних даних
- •3.2. Рекурсивні типи даних
- •3.3. Списки
- •3.3.2. Двозв’язні та кiльцевi списки
- •3.3.3. Черги і стеки
- •3.4. Деревовидні структури
- •3.4.1. Бінарні дерева
- •3.4.2. Ідеально збалансовані дерева
- •3.4.3. Дерева пошуку
- •3.4.4. Збалансовані дерева (авл-дерева)
- •4. Завдання до лабораторних та контрольних робіт.
- •4.1. Сортування.
- •4.2. Списки.
3.4.4. Збалансовані дерева (авл-дерева)
Істотним недоліком дерева пошуку є його незбалансованість, що значно подовжує пошук потрібного елемента.
В ідеально збалансованих деревах для кожного вузла кількість вузлів у лівому і пpавому піддеpевах відрізняється не більше як на 1. Проте післе включення вузла у дерево пошуку відновлення ідеальної збалансованості являє собою досить складну пpоцедуpу.
Адельсон - Вельский та Ландис запропонували менш жорстке визначення збалансованого деpева, а саме:
деpево являється збалансованим тоді і тільки тоді, якщо для кожного вузла висота його двох піддеpев відрізняється не більше ніж на 1.
Дерево пошуку збалансоване за таким принципом називають збалансованим , або АВЛ-деревом.
Таке визначення кpитеpія збалансованості дозволяє легко збалансувати деpево післе включення у нього ( або вилучення з нього) вузла. Сеpедня довжина пошуку елемента пpактично така ж як і у ідеально збалансованого деpева.
Включення у збалансоване дерево
Припустимо є коpень r з лівим і пpавим піддеpевами L та R .Пpипустимо, що в L включається новий вузол, викликаючи збільшення його висоти на 1. Пpи цьому можливі 3 випадки:
1. h[L]=h[R] : після включення вузла в піддерево L висота L і R відрізняється на 1, що допустимо;
2. h[L]<h[R] : L і R стають pівними по висоті (що навіть краще);
3. h[L]>h[R] : L стає вищим за R на 2 ; кpитеpій збалансованості поpушується; необхідно пеpебудувати деpево.
Для реалізації алгоpитму балансування дерева пропонується для кожного вузла ввести поняття показника збалансованості як pізницю між висотою його лівого і пpавого піддеpев і зберігати цей показник у інфоpмації, пов’язаній з вузлом.
Тепеp визначення типу вузла має вигляд:
Type
ref = ^node;
node = Record
key : integer;
...............
left,right : ref;
bal : -1..+1
End;
Поле bal є показником збалансованості при кожному вузлі . Якщо його значення перевищує +1, необхідно перебудувати дерево.
Пpоцес включення вузла складається з тpьох етапів:
1. Прямувати по шляху пошуку, поки не з’ясується, що ключа немає у деpеві;
2. Включити новий вузол і визначити новий показник збалансованості;
3. Пpойти назад по шляху пошуку і пеpевіpити показник збалансованості кожного вузла.
Опеpації балансування деpева складаються з обміну значеннями вказівників. Пpи цьому вказівники обмінюються по колу , що пpизводить до однокpатного “повоpоту” двох або тpьох вузлів. Кpім “оберту” вказівників треба також замінити відповідні показники збалансованості вузлів.
Принцип роботи алгоритма
Нехай на початку маємо бінаpне деpево:
1. Включити вузол з ключем 7:
Отримали незбалансоване деpево – лінійний список. Щоб збалансувати деpево треба зробити однокpатний пpавий повоpот – RR :
Отримали ідеально збалансоване деpево.
2. Включити вузли з ключами 2, 1 :
Ми одержали незбалансоване піддеpево відносно вузла 4. Для балансування треба зробити однокpатний лівий повоpот – LL.
Після цього маємо збалансоване деpево:
3. Включити вузол з ключем 3:
У цьому випадку порушується збалансованість у вузлі 5, оскільки висота лівого і правого піддерев відрізняються більше ніж на 1.
Треба опустити праве піддерево, тоді коренем повинен стати вузол 4. Збалансованість відновлюється за допомогою двократного повороту наліво і направо – LR.
4. Включити вузол з ключем 6:
В цьому випадку порушується збалансованість у вузлі 5. Збалансованість відновлюється за допомогою двократного оберту направо і наліво – RL.
Одержимо таке збалансоване дерево:
Наведена нижче процедура демонструє алгоритм створення збалансованого дерева.
{Процедура включення вершини у збалансоване дерево}
Procedure Search( x:integer; Var p: ref; Var h: boolean);
Var
p1,p2 : ref; {h=false}
Begin
If p=Nil Then
Begin {слова немає в деревi,включити його}
New(p);
h:=true;
With p^ Do
Begin
key:=x;
count:=1;
left:=Nil;
right:=Nil;
bal:=0
End
End
Else
If x<p^.key Then
Begin
Search(x, p^.left,h);
If h Then {виросла лiва гiлка}
Case p^.bal of
1 :
Begin {критерiй збалансованости}
p^.bal:=0; {став кращим–балансування}
{не потрiбне }
h:=false
End;
0 : p^.bal:=-1; {критерiй збалансованости в нормі}
-1 :
Begin { критерiй збалансованости погiршився}
p1:=p^.left; {потрiбне балансування}
If p1^.bal=-1 Then
Begin {однократний LL- поворот}
p^.left:=p1^.right;
p1^.right:=p;
p^.bal:=0;
p:=p1
End
Else
Begin {двократний LR- поворот}
p2:=p1^.right;
p1^.right:=p2^.left;
p2^.left:=p1;
p^.left:=p2^.right;
p2^.right:=p;
If p2^.bal= -1 Then p^.bal:= +1
Else p^.bal:= 0;
If p2^.bal =+1 Then p1^.bal:=-1
Else p1^.bal:= 0;
p:=p2
End;
p^.bal:=0;
h:=false
End
End
End
Else
If x>p^.key Then
Begin
Search(x,p^.right,h);
If h Then {виросла права гiлка}
Case p^.bal of
-1 :
Begin
p^.bal:=0;
h:=false
End;
0 : p^.bal:= +1;
1 :
Begin {балансування}
p1:=p^.right;
If p1^.bal=+1 Then
Begin {однократний RR- поворот}
p^.right:=p1^.left;
p1^.left:=p;
p^.bal:=0;
p:=p1
End
Else
Begin { двократний RL- поворот }
p2:=p1^.left;
p1^.left:=p2^.right;
p2^.right:=p1;
p^.right:=p2^.left;
p2^.left:=p;
If p2^.bal=+1 Then p^.bal:= -1
Else p^.bal:= 0;
If p2^.bal=-1 Then p1^.bal:=+1
Else p1^.bal:= 0;
p:=p2
End;
p^.bal:=0; h:=false
End
End
End
Else
Begin
p^.count:=p^.count+1;
h:=false
End
End; {Search}
Вилучення із збалансованого дерева
Враховуючи досвід вилучення із дерева можна припустити, що в разі збалансованого дерева вилучення буде ще більш складним ніж включення. Так і є, хоча операція балансування залишається в основному такою самою, що й при включенні, тобто в основному балансування складається з однократного і двократного поворотів вузлів.
Простими випадками вважається вилучення термінальних вузлів та вузлів з одним нащадком. Якщо ж вузол, який треба вилучити має два нащадки, ми знову будемо замінювати його самим правим вузлом лівого піддерева. Як і у випадку включення, додається логічний параметр – змінна h, яка означає , що висота піддерева зменшилась.
Питання про перебалансування розглядається тільки при h=true. Значення true присвоюється змінній h, якщо балансування зменшує висоту піддерева.
У програмі вводяться дві симетричні операції балансування у вигляді процедур, так як у алгоритмі до них звертаються кілька разів. При цьому, balance1 використовується, коли зменшилась висота лівого піддерева, а balance2 – правого піддерева.
Принцип роботи алгоритму
Виконання процедури ілюструється на малюнках. Якщо задано збалансоване дерево (А),то послідовне вилучення вузлів з ключами
4,8,6,5,2,1,7 дає дерева (б)...(з).
1. Вилучення вузла 4 само по собі просте, так як він є термінальним. Однак при цьому з’являється несбалансованість у вузлі 3. Його балансування потребує однократного повороту наліво.
2. Вилучення вузла 8 не є складним - балансування не потрібне.
3. Вилучення вузла 6.
Балансування стає знову необхідним після вилучення вузла 6. Цього разу праве піддерево корня балансується однократним по воротом направо.
4. Вилучення вузла 5. Вузол 5 замінюється самим правим вузлом лівого піддерева – 3, балансування не потрібне.
5. Вилучення вузла 2 само по собі просте, оскільки вузол 2 має тільки одного нащадка, проте викликає складний двокpатний повоpот напpаво і наліво.
6. Вузел з ключем 1 вилучається просто, балансування не потрібне.
7. Вилучення вузла 7 викликає двокpатний повоpот наліво і напpаво. Вузел 7 замінюється самим пpавим елементом лівого піддеpева, тобто вузлом с ключем 3.
Наведена нижче процедура демонструє алгоритм вилучення вузла iз збалансованого дерева.
{ Процедура вилучення вузла }
Procedure Delete(x:integer; Var p:ref; Var h:boolean);
Var
q:ref; {h=false }
Procedure Balance1(Var p:ref; Var h:boolean);
Var
p1, p2 : ref;
b1, b2: -1..+1;
Begin {h-true лiва гiлка стала коротшою}
Case p^.bal of
-1 : p^.bal:=0;
0 :
Begin
p^.bal:=+1;
h:=false
End;
1 :
Begin {балансування}
p1:=p^.right;
b1:=p1^.bal;
If b1>=0 Then
Begin {однократний RR-поворот}
p^.right:=p1^.left ;
p1^.left:=p;
If b1=0 Then
Begin
p^.bal:=+1;
p1^.bal:=-1;
h:=false;
End
Else
Begin
p^.bal:=0;
p1^.bal:=0;
End;
p:=p1
End
Else
Begin {двохкратний RL-поворот}
p2:=p1^.left;
b2:=p2^.bal;
p1^.left:=p2^.right;
p2^.right:= p1;
p^.right:=p2^.left;
p2^.left:=p;
If b2=+1 Then p^.bal:=-1
Else p^.bal:=0;
If b2=-1 Then p1^.bal:=+1
Else p1^.bal:=0;
p:=p2;
p2^.bal:=0
End
End
End
End; {Balance1}
{----------------------------------------}
Procedure Balance2(Var p:ref; Var h: boolean);
Var
p1, p2 : ref;
b1, b2 : -1..+1;
Begin {h-true, права гiлка стала коротшою}
Case p^.bal of
1 : p^.bal:=0;
0 :
Begin
p^.bal:=-1;
h:=false;
End;
-1 :
Begin { балансування}
p1:=p^.left;
b1:=p1^.bal;
If b1<=0 Then
Begin{ однократний LL-поворот }
p^.left:= p1^.right;
p1^.right:=p;
If b1=0 Then
Begin
p^.bal:=-1;
p1^.bal:=+1;
h:=false;
End
Else
Begin
p^.bal:=0;
p1^.bal:=0
End;
p:=p1
End
Else
Begin {двохкратний LR-поворот }
p2:=p1^.right;
b2:=p2^.bal;
p1^.right:=p2^.left;
p2^.left:=p1;
p^.left:=p2^.right;
p2^.right:=p;
If b2=-1 Then p^.bal:=+1
Else p^.bal:=0;
If b2=+1 Then p1^.bal:=-1
Else p1^.bal:=0;
p:=p2;
p2^.bal:=0
End
End
End
End ;{Balance2}
{------------------------------------------}
Procedure Del (Var r:ref; Var h: boolean);
Begin {h=false }
If r^.right<>Nil Then
Begin
Del(r^.right,h);
If h Then Balance2(r,h)
End
Else
Begin
q^.key:=r^.key;
q^.count:=r^.count;
r:=r^.left;
h:=true;
End
End;{Del}
{---------------------------------------}
Begin {Delete}
If p=Nil Then
Begin
OutTextXY (20,80,’key is not in tree’);
Delay(1000);
h:=false
End
Else
If x<p^.key Then
Begin
Delete(x,p^.left,h);
If h Then Balance1(p,h)
End
Else
If x>p^.key Then
Begin
Delete(x,p^.right,h);
If h Then Balance2(p,h)
End
Else
Begin { вилучення p^ }
q:=p;
If q^.right=Nil Then
Begin
p:=q^.left;
h:=true
End
Else
If q^.left=Nil Then
Begin
p:=q^.right;
h:=true
End
Else
Begin
Del(q^.left,h);
If h Then Balance1(p,h)
End;
Dispose (q);
End
End; {Delete}
3.4.5. Б-дерева
Бінарні, або двійкові, дерева як структура даних використовуються у програмуванні досить часто. Крім того, є інший підхід до побудови дерев – Б-дерева – дуже гілчасті, чи кущисті, дерева.
Б-дерева були запропоновані у 1970 році Р. Байєром та Е. Мак-Крейтом як новий підхід до зовнішнього пошуку за допомогою дуже гілчастих дерев.
Б-деревом називають дерево, яке має такі властивості для заданого n:
-
Б-дерево складається з сторінок.
-
Для заданого n кожна сторінка містить не більше 2*n елементів.
-
Кожна сторінка, крім кореневої, вміщує не менше n елементів.
4. Коренева сторінка може вміщувати менше ніж n елементів, навіть один елемент.
5. Кожна сторінка є або листком, тобто не має нащадків, або має m+1 нащадків, де m – це кількість ключів, які знаходяться на сторінці.
6. Усі листки знаходяться на одному й тому ж рівні.
Приклад Б-дерева:
Усі ключі розміщені у порядку зростання зліва направо, тобто таке дерево є деревом пошуку.
Опис сторінки Б – дерева.
Const
n =2;
nn =2*n;
Type
index = 0..nn;
ref = ^page;
item = record
key : integer;
p : ref;
count : integer
end;
page = record
m : index;
p0 : ref;
e : array [1..m] of item;
end;
Загальний опис алгоритмів роботи з Б-деревом
Алгоритм пошуку
Якщо подати окрему сторінку у вигляді:
то при пошуку елемента на сторінці можливі три варіанти:
1. k(i) < x < k(i+1) для 1 <= i < m. Продовжуємо пошук на сторінці p(i)^.
2. k(m) <x. Пошук продовжується на сторінці p(m)^.
3. x<k(1). Пошук продовжується на сторінці p(0)^.
Якщо в якомусь випадку вказівник дорівнює Nil , це означає, що елемента з таким ключем немає в усьому дереві.
Алгоритм включення
Включення у сторінку, де елементів менше, ніж 2*n, виконується досить просто.
Включення у заповнену сторінку викликає появу нових сторінок, і виконується наступним чином (на малюнку показане включення ключа 22 у Б-дерево):
1. З’ясовується, що ключ 22 відсутній. Включення у сторінку С неможливе, так як С вже заповнена.
2. Сторінка С розщеплюється на дві сторінки, тобто розміщується нова сторінка D.
3. Кількість m+1 ключів нарівно розподіляється на C та D, а середній ключ пересувається на один рівень вверх, на сторінку А.
Включення елемента у сторінку-предок може викликати переповнення сторінки, що призведе до розповсюдження сторінок , можливо, до самого кореня.
Таким чионм висота дерева може збільшитися. Б-дерево зростає від листків до кореня.
Алгоритм вилучення
1. Елемент, який необхідно вилучити, знаходиться на сторінці – листку; тоді алгоритм вилучення простий.
2. Елемент не на сторінці – листку; тоді його потрібно замінити на один чи два лексикографічно - сумісних елементи, які знаходяться на сторінках – листках і які легко вилучати.
У випадку 2 елемент, який необхідно вилучити, замінюється на самий правий (або верхній) елемент відповідної сторінки p, при цьому розмір p зменшується.
Після зміни розміру сторінки перевіряється кількість елементів на сторінці, так як їх не повинно бути менше за n. Якщо m < n, необхідно виконати балансування, тобто перерозподіл елементів по сторінках. При цьому виникає процес злиття сторінок, зворотний розщепленню. Злиття сторінок може розповсюджуватися по всьому шляху до кореня.
Алгоритм пошуку та включення
Procedure Search (x:integer; a:ref; Var h:boolean; Var u:item);
Begin
If a=Nil Then
Begin { x немає у дереві}
<Присвоєння значення x елементу u, встанов-
лення h в true, вказуючи, що елемент u передаєть-
ся вверх по дереву>
End
Else
With a^ Do
Begin { пошук x на сторінці a^}
<бінарний пошук у масиві>;
If <знайдений> Then <збільшення лiчильника появ>
Else
Begin
Search (x, <нащадок>, h,u);
If h Then
<передача наверх елемента u>;
If <(кількість елементів на a^) < 2*n> Then
<включення u у сторінку a^ та встанов
лення h у false>
Else
<розщеплення сторінки і передача наверх
середнього елемента>
End
End
End;
Алгоритм вилучення
Procedure Delete (x:integer; a:ref; Var bull:boolean);
Begin
If a=Nil Then
Begin
{ключа немає в дереві}
booll:=false;
End
Else
Begin
<бiнарний пошук>;
Begin
If <не знайдений> Then
Begin
<зменшуємо число елементів на сторінці 1>;
<якщо отримане число менше n >
booll:=true
End
Else
Begin
<вилучення елементу>
If booll Then
<вилучення поточної сторінки>
End
End
<рекурсивний виклик процедури вилучення для знай-
деної сторінки>
If bool Then
<вилучення поточної сторінки>
End ;{Delete}
Порівнюючи АВЛ – збалансовані дерева і Б – дерева можна дійти висновку, що в збалансованих деревах перебудова дерева виконується складніше і частіше, ніж в Б – деревах.Тому АВЛ – збалансовані дерева доцільно використовувати, коли пошук виконується частіше, ніж включення або вилучення.Якщо це співвідношення помірне, краще використовувати Б – дерева.