Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Алгоритмы и структуры данных / методичка структуры данных_задания.docx
Скачиваний:
54
Добавлен:
12.05.2015
Размер:
381.84 Кб
Скачать

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:

  1. Б-дерево складається з сторінок.

  2. Для заданого n кожна сторінка містить не більше 2*n елементів.

  3. Кожна сторінка, крім кореневої, вміщує не менше 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}

Порівнюючи АВЛ – збалансовані дерева і Б – дерева можна дійти висновку, що в збалансованих деревах перебудова дерева виконується складніше і частіше, ніж в Б – деревах.Тому АВЛ – збалансовані дерева доцільно використовувати, коли пошук виконується частіше, ніж включення або вилучення.Якщо це співвідношення помірне, краще використовувати Б – дерева.