Sunteți pe pagina 1din 23

Curs 7 Arbori

Exemplu de structura de arbore:

a
/ \
b c
/ / \\
d e f g
/ \ \
h i j
Exemple de arbori:

poligoane
/ \
triunghi patrulatere
/ \ \
dreptunghi romb oarecare
Definitie
Se numeste arbore cuplul format din V si E : T= (V,E) cu V o multime de noduri
si E VxV o multime de arce, cu proprietatile:

1) nodul r V (nodul radacina) astfel încѓt j V, (j, r) E


2) x V\{r} , y V unic , astfel încѓt (y, x) E (Cu alte cuvinte, pentru
toate nodurile radacina, un singur arc ce intra în nodul respectiv)
3) y V, un drum { r = x0, x1, x2, ... ,xn= y} , cu xi V si (xi, xi+1)
E (Sau arborele trebuie sa fie un graf conex: nu exista noduri izolate sau
grupuri de noduri izolate).

Proprietate a arborelui

Daca T, T= (V, E) este un arbore si r V este radacina arborelui, atunci


multimea T\{r} = (V', E'), cu
V' = V -{r} si E' = E -{ (r, x)/(r, x) E } poate fi partitionata astfel
încѓt sa avem mai multi arbori, a caror reuniune sa fie T\{r}, si oricare
ar fi doi arbori intersectati, sa dea multimea vida:
T\{r}= T1 T2 ... Tk , Ti Tj = .

Definitii

1) Daca avem (x, y) E , x predecesorul lui y (tata), y succesorul


lui x (fiu)
x
/
y

2) Fie E(x) = { y, (x, y) E } multimea succesorilor lui x.

Definim gradul lui x: degree(x) = € E(x) € = numarul de succesori


Definim gradul arborelui : degree(T) = max{ degree(x)}, x V unde y se mai
numeste nod terminal, sau frunza, daca degree(y) = 0, adica daca nu are
descendenti.
Stramosii unui nod sunt asa-numitii ancestors(x) : ancestors(x) =
{r = x0, x1, x2, ..., xk} cu proprietatea ca
(xi, xi+1) E , i= {0,k - 1} si (xk, x) E
Nivelul unui nod : level(x) = € ancestors(x) € + 1
Adѓncimea arborelui : depth(T) = max { level (x) pentru x V
Exemplu:
A
/ | \
B C D
/ \ | /|\
E F G H I J
/ \ |
K L M

predecesor(E) = B
succesor(C) = G
E(D) = {H, I, J}
degree(D) = 3
degree(B) = 2
degree(F) = 0
degree(T) = 3
ancestors(L) = {A, B, E}
level(L) = 4 , level(B) = 2 , level(A) = 1
depth(T) = 4

Reprezentarea arborilor

Reprezentarea prin liste generalizate

Se considera ca nodurile terminale sunt elemente atomice, iar nodurile de grad


1 sunt subliste. Deci, fie arborele de mai sus scris sub forma :

A( B (E (K, L), F), C (G), D (H (M), I, J)) cu reprezentarea:

Reprezentarea prin structuri înlantuite

Aceasta reprezentare are calitatea ca, atunci cѓnd conteaza ordinea


descendentilor, ea poate surprinde structura diferita.

De exemplu:
structura x este diferita de structura x
/ | \ /| \
vid y vid y vid vid

Metodele de reprezentare expuse permit sa putem identifica legatura


nod-descendent (succesor). Dar, exista aplicatii în care este nevoie de
legatura nod-predecesor. Asadar, pare utila reprezentarea arborelui sub forma
nodului (data, parent):

Avѓnd adresa unui nod, se gasesc toti predecesorii, obtinѓndu-se o lista


înlantuita:

(Reprezentarea TATA):

Arbori binari

Un arbore binar este un arbore de grad maxim 2. In cazul acestor arbori,


se pot defini aplicatii, instrumente în plus de operare. Arborii binari pot
avea deci gradele 2, 1, 0:
A A A
/ \ / / \
B C B B C
/ \ \ / / \ / \
D E F C D E F G

Observatie: Arborele A este diferit de A


/ \ / \
B vid vid B

Structura de baza a unui arbore binar:

rad
/ \
/ \
/ \
/ \
\/ / \
/ SAS \ / SAD \
/______ \ /_______\
SAS subarborele stѓng (binar)
SAD subarborele drept (binar)

Definitii

1) Se numeste arbore binar strict arborele pentru care oricare ar fi un nod


x V degree(x) = 2 , sau
degree(x) = 0.

Exemplu:
a
/ \
c b
/ \ / \
d e f g
/ \
h i

2) Se numeste arbore binar complet un arbore binar strict pentru care y cu:
degree(y) = 0 (frunza) level(y) = depth(T)
Cu alte cuvinte, nodurile terminale apartin ultimului nivel din arbore.

Exemplu:
a
/ \
b c
/ \ / \
d e f g
/ \ / \ / \ / \
h i j kl m n o

Relatii între numarul de noduri si structura unui arbore binar

Lema 1

Numarul maxim de noduri de pe nivelul i al unui arbore binar este egal cu 2i-1.
Demonstratia se face prin inductie:
La nivelul 1 avem 20 = 1 nod = rad (radacina A). Presupunem conform metodei
P(n): pe nivelul n avem 2n-1 noduri. Demonstram pentru P(n+1): se observa ca
toate nodurile de pe nivelul n+1 sunt noduri descendente de pe nivelul n.
Notѓnd niv(i) numarul de noduri de pe nivelul i,
niv(n+1) 2ъniv(n) 22n-1 = 2n .

Lema 2

Numarul maxim de noduri ale arborelui binar de adѓncime h este egal cu 2h -1.

Demonstratie:
Numarul total de noduri este egal cu:
(progresie geometrica)

Observatie: Numarul maxim de noduri pentru arborele binar se atinge în


situatia unui arbore binar complet.
2h -1 = numarul de noduri în arborele binar complet de adѓncime h

Lema 3

Notam cu:
n2 numarul de noduri de grad 2 din arborele binar;
n1 numarul de noduri de grad 1 din arborele binar;
n0 numarul de noduri terminale (frunze) din arborele binar;
In orice arbore binar, n0 = n2 +1 (nu depinde de n1).

Demonstratie: fie n = n0 + n1 + n2 (numarul total de noduri); conform


definitiei, fiecare nod are un singur predecesor numarul de
muchii € E € = n - 1. Acelasi numar de muchii € E € = 2 n2 + n1.
Deci, n - 1 = 2n2 + n1 , înlocuind, n0 + n1 +n2 -1 = 2n2 + n1 n0 = n2 + 1
ceea ce trebuia de demonstrat.
Rezulta ca într-o expresie numarul de operatori binari si unari este egal
cu numarul de operanzi + 1.

Lemele se folosesc pentru calcule de complexitate.

Operatii asupra arborilor binari

Operatii curente:
selectia cѓmpului de date dintr-un nod si selectia descendentilor;
inserarea unui nod;
stergera unui nod.

Traversarea arborilor binari (A.B.)


Traversarea consta în "vizitarea" tuturor nodurilor unui arbore într-un scop
anume, de exemplu, listare, testarea unei conditii pentru fiecare nod, sau
alta prelucrare. O traversare realizeaza o ordonare a nodurilor arborelui
(un nod se prelucreaza o singura data).
Strategii de traversare:

traversare în preordine: prelucrare în ordinea: rad, SAS, SAD;


traversare în inordine: prelucrare în ordinea: SAS, rad, SAD;
traversare în postordine: prelucrare în ordinea: SAS, SAD, rad.

rad
/ \
SAS SAD
Exemplu de traversare: a
/ \
b c
/ \ \
d e f
/ \
g h

preordine : A B D F G H C F
inordine : D B G E H A C F
postordine : D G H E B F C A

Functii de parcurgere (in pseudocod)


Facem notatiile:
p pointer la un nod
lchild(p) pointer la succesorul stѓng (p stg)
rchild(p) pointer la succesorul drept (p drt)
data(p) informatia memorata în nodul respectiv (p data)

In C++ avem:

struct Nod{
Atom data;
Nod* stg;
Nod* dr;
};
Nod* p;

Procedurile de parcurgere sunt:

preorder(t)
{
if(t==0) return
else |Ѕ print (data(t)) // vizitarea uni nod
| preorder (lchild(t)) // parcurgerea |
// subarborilor
|_ preorder (rchild(t))
}

inorder(t)
{
if(t!=0) |Ѕ inorder (lchild(t))
| print (data(t))
|_ inorder (rchild(t))
}

postorder(t)
{
if(t!=0) |Ѕ postorder (lchild(t))
| postorder (rchild(t))
|_ print(data(t))
}

Binarizarea arborilor oarecare

Lema 1
Daca T este un arbore de grad k cu noduri de dimensiuni egale (k pointeri în
fiecare nod), arborele avѓnd n noduri reprezentarea va contine n (k - 1) + 1
pointeri cu valoare zero (nuli).

Demonstratie: Numarul total de pointeri utilizati în reprezentare este nk


Numarul total de pointeri nenuli este egal cu numarul de arce
nk - (n - 1) = n(k - 1) + 1

nr.poineri nuli n(k-1)+1 n-1


------------------- = --------- = 1 - --------
nr.total de pionteri nk nk

raportul este maxim pentru k = 2.


Rezulta ca cea mai eficienta reprezentare (în structura înlantuita) este
reprezentarea în arbori binari.

Curs 8 Arborele binar de cautare (BST)


Arborele binar de cautare reprezinta o solutie eficienta de implementare a
structurii de date numite "dictionar". Vom considera o multime "atomi".
Pentru fiecare element din aceasta multime avem: a atomi, este definita
o functie numita cheie de cautare: key(a)a partine lui k cu proprietatea ca
doi atomi distincti au chei diferite de cautare: a1!=a2=> key(a1)!=key(a2).

Exemplu: (abreviere, definitie) ("BST","Binary Search Tree")


("LIFO","Last In First Out") key(a) = a.abreviere

Un dictionar este o colectie S de atomi pentru care se definesc operatiile:


insert(S,a) insereaza atomul a în S daca nu exista deja;
delete(S,k) sterge atomul cu cheia k din S daca exista;
search(S,k) cauta atomul cu cheia k în S si-l returneaza sau determina daca
nu este.

O solutie imediata ar fi retinerea elementelor din S într-o lista înlantuita,


iar operatiile vor avea complexitatea O(n).

Tabelele Hashing
Acestea sunt o alta solutie pentru a retine elementele din S. Complexitatea
pentru arborele binar de cautare în cazurile:
cel mai defavorabil: O(n);
mediu: O(log n).
Un arbore binar de cautare este un arbore T ale carui noduri sunt etichetate
cu atomii continuti la un moment dat în dictionar.
T = (V, E) , €V€ = n. (n atomi în dictionar)

Considerѓnd r V (radacina arborelui), Ts subarborele stѓng al radacinii si


Td subarborele drept al radacinii, atunci structura acestui arbore este
definita de urmatoarele proprietati:
1) un nod x Ts atunci key(data(x)) < key(data(r));
2) x Td atunci key(data(x)) > key(data(r));
3) Ts si Td sunt BST.

Observatii:
1) Consideram ca pe multimea k este definita o relatie de ordine (de exemplu
lexico-grafica);
2) Pentru oricare nod din BST toate nodurile din subarborele stѓng sunt mai
mici decѓt radacina si toate nodurile din subarborele drept sunt mai mari decѓt
radacina.
Exemple: 15 15
/ \ / \
7 25 10 25
/ \ / \ /\ /
2 13 17 40 2 17 13
/ / \
9 27 99
este BST nu este BST.

3) Inordine: viziteaza nodurile în ordine crescatoare a cheilor:


2 7 9 13 15 17 25 27 40 99
Functii:
1)Search:
search(rad,k) // rad pointer la radacina arborelui
{ // k cheia de cautare a arborelui cautat
if (rad = 0) then return NULL
else
if key (data (rad)) > k then
return search (lchild (rad))
else
if key (data (rad)) < k then
return search (rchild (rad))
else return rad
}
2)Insert:
Se va crea un nod în arbore care va fi plasat la un nou nod terminal.
Pozitia în care trebuie plasat acesta este unic determinata în functie de
valoarea cheii de cautare.
Exemplu: vom insera 19 în arborele nostru:

15
/ \
7 25
/ \ / \
2 13 17 40
/ \ / \
9 19 27 99

insert(rad,a) // rad referinta la pointerul la radacina // arborelui


{
if (rad= 0) then rad= make_nod(a)
else if key (data (rad)) > key(a) then
insert(lchild(rad),a)
else if key (data(rad)) < key(a)then
insert (rchild (rad),a)
}
Functia make_nod creaza un nou nod:

make_nod(a)
{
p= get_sp() // alocare de memorie pentru un nod nou
data(p)= a
lchild(p)= 0
rchild(p)= 0
return(p)
}

Observatie:
1)La inserarea unui atom deja existent în arbore, functia insert nu modifica
structura arborelui. Exista probleme în care este utila contorizarea numarului
de inserari a unui atom în arbore.
2)Functia insert poate returna pointer la radacina facѓnd apeluri de forma
p= insert(p,a).
3)Delete:

delete(rad,k) // rad referinta la pointer la radacina


{ // k cheia de cautare a atomului care trebuie sters de noi
if rad = 0 then return // nodul cu cheia k nu se afla // în arbore
else if key(data(rad)) > k then
delete(lchild(rad),k)
else if key(data(rad)) < k then delete(rchild(rad),k)
else delete_root(rad)
}
Stergerea radacinii unui BST.:
1) rad=>arbore vid
2)a) rad sau b) rad => a) SAS sau b) SAD
/ \
SAS SAD

delete_root(rad) // rad referinta la pointer la radacina


{
if lchild(rad)=0 then
| p= rchild(rad)
| ret_sp(rad)
|_ rad= p
else if rchild(rad)= 0 then
| p= lchild(rad)
| ret_sp(rad)
|_ rad= p
else | p= remove_greatest(lchild(rad))
| lchild(p)= lchild(rad)
| rchild(p)= rchild(rad)
| ret_sp(rad)
|_ rad= p
}
15
/ \
7 25
/\ / \
2 13 17 40
/ /
9 27
/ \
26 33

Detasarea din structura arborelui BST a celui mai mare nod (remove_greatest):
Pentru a gasi cel mai mare nod dintr-un arbore binar de cautare, se înainteaza
în adѓncime pe ramura dreapta pѓna se gaseste primul nod care nu are descendent
dreapta. Acesta va fi cel mai mare.
Vom trata aceasta procedura recursiv:
Caz1: rad=>se returneaza pointer la radacina si arborele rezultat va fi vid.
Caz2: rad=> se returneaza pointer la radacina si arborele rezultat va fi
format doar din SAS subarborele stѓng (SAS).
Caz3: rad=>functia returneaza pointer la cel mai mare nod din SAD, iar
rezultatul va fi SAS arborele care este fomat din radacina,SAS si SAD
cel mai mare nod.
remove_greatest(rad) //rad -referinta la pointer la //radacina: un pointer la
radacina de poate //fi modificat de catre functie
{
if rchild (rad)= 0 then | p= rad
| rad= lchild (rad)
|_ return(p)
else return (remove_greatest (rchild(rad)))
}

Observatie:
Functia remove_greatest modifica arborele indicat de parametru, în sensul
eliminarii nodului cel mai mare, si întoarce pointer la nodul eliminat.

Demonstratia eficientei (complexitate)

Complexitatea tuturor functiilor scrise depinde de adѓncimea arborelui.


In cazul cel mai defavorabil, fiecare functie parcurge lantul cel mai lung
din arbore. Functia de cautare are, în acest caz, complexitatea O(n).
Structura arborelui BST este determinata de ordinea inserarii.
De exemplu, ordinea 15 13 12 11 este alta decѓt 15 12 11 13 .
Studiem un caz de complexitate medie:

Crearea unui BST pornind de la secventa de atomi (a1 a2 ... an)

gen_BST (va fi în programul principal)


| rad= 0
| for i= 1 to n
| insert (rad, ai)

Calculam complexitatea medie a generarii BST:


Complexitatea în cazul cel mai defavorabil este:
Notam cu T(k) numarul de comparatii mediu pentru crearea unui BST pornind de
la o secventa de k elemente la intrare. Ordinea celor k elemente se considera
aleatoare.
Pentru problema T(n) avem de creat secventa (a1 a2 ... an) cu observatia
ca a1 este radacina arborelui. Ca rezultat, în urma primei operatii de
inserare pe care o facem, rezulta:

a1
/ \
a1 ai
(ai<a1) (ai>a1)

Nu vom considera numararea operatiilor în ordinea în care apar ele, ci


consideram numarul de operatii globale. Dupa ce am inserat a1, pentru
inserarea fiecarui element în SAS sau SAD a lui a1, se face o comparatie cu a1.
Deci:
T(n)= (n - 1) + val.med.SAS + val.med.SAD
val.med.SAS = valoarea medie a numarului de comparatii necesar pentru a
construi subarborele stѓng SAS
val.med.SAD = valoarea medie a numarului de comparatii necesar pentru a
construi subarborele drept SAD

Notam:
Ti(n) = numarul mediu de comparatii necesar pentru construirea unui BST cu n
noduri atunci cѓnd prima valoare inserata (a1) este mai mare decѓt i-1 dintre
cele n valori de inserat. Putem scrie:
Deci:

Deci:

Complexitatea acestei functii este: O(nln n) (vezi curs 5 complexitatea medie


a algoritmului Quick-Sort)
Arbori binari de cautare dinamic echilibrati (AVL)
Definitie
Un arbore binar este echilibrat daca si numai daca, pentru fiecare nod din arbore, diferenta dintre adѓncimile SAS si
SAD în modul este 1.

Exemple:
a a
/ \ / \
b c b c
/ \ / \ / \ \
d e f g d e f
/ \
g h
arbore binar arbore binar
complet echilibrat echilibrat

Adѓncimea unui arbore echilibrat cu n noduri este O(ln n).


Se completeaza operatiile insert si delete cu niste prelucrari care sa pastreze
proprietatile de arbore binar echilibrat pentru arborele binar de cautare.
Arborele binar echilibrat este un BST echilibrat, proprietatea de echilibrare
fiind conservata de insert si delete. Efortul, în plus, pentru completarea
operatiilor insert si delete nu schimba complexitatea arborelui binar
echilibrat.
Transformarea structurii arborelui dupa inserare pentru a conserva proprietatea
de arbore binar echilibrat
Modificarile care se vor face se vor numi rotatii.

Caz 1: Fie arborele echilibrat


A
/ \
B T3 h = depth(T1) = depth(T2) = depth(T3)
/ \
T1 T2

Consideram arborii T1, T2, T3 echilibrati. Inserѓnd un nod prin rotatie simpla,
rezulta structurile rotit simplu la dreapta si rotit simplu la stѓnga imaginea
oglinda a rotatiei dreapta:
A A
/ \ / \
B T3 T3 B
/ \ / \
T1 T2 T2 T1

Caz 2: Inserarea se face prin rotatii duble:


A A
/ \ / \
B T3 T3 B
/ \ / \
T1 T2 T2 T1
rotatie dubla rotatie dubla
la dreapta la stѓnga
Fie primul caz:
A
/ \
B T3
/ \
T1 T2 este BST: T1 < B < T2 < A < T3

Toti arborii care respecta în continuare aceasta conditie vor fi BST.


Ridicѓnd pe B în sus, si notѓnd cu // legaturile neschimbate, rezulta:
B
// \
// A
T1 / \\
____________T2___T3_________ pe aceeasi linie

care este un BST , deci este arbore echilibrat, si are aceeasi adѓncime (!!!)
cu arborele initial (de dinainte de inserare). Nodurile superioare nu sunt
afectate. Rationament analog pentru imaginea oglinda.

Fie cazul 2: Pentru rotatia dubla se detaliaza structura arborelui T2.


Nu se poate sparge arborele initial ca în cazul 1.
A
/ \\
B \\
// \ T3
// C
// / \
T1 T2S T2D
depth(T1) = depth(T3) = h
depth(T2S) = depth(T2D) = h - 1

In urma inserarii, unul dintre arborii T2S si T2D îsi mareste adѓncimea.
Aplicam aceiasi tehnica:
T1 < B < T2S < C < T2D < A < T3

Incepem cu C:
C
/ \
B A
// \ / \\
// T2S T2D \\
______T1___________________ T3_____________________
la acelasi nivel
Rezulta un BST echilibrat, de aceeaai adѓncime cu arborele initial. Rotatiile
sunt duble, în sensul ca
s-a facut o rotatie simpla B la stѓnga cu o rotatie simpla A la dreapta.
Operatiile care trebuiesc facute în cazul 1 (rotatie simpla la dreapta):
r- pointer la nodul radacina (A)
a- pointer la radacina
p = lchild(r) b = lchild(a)
lchild(r) = rchild(p) lchild(a) = rchild(b)
rchild(p) = r rchild(b) = a
r=p a=b
Operatiile care trebuiesc facute în cazul 2 (rotatie dubla)
b = lchild(a)
c = rchild(b)
lchild(a) = rchild(c)
rchild(b) = lchild(c)
rchild(c) = a
lchild(c) = b
a = c // se schimba radacina arborelui.

Curs 9 Asadar, în inserarea prin rotatie se obtine un arbore echilibrat cu adѓncimea


egala cu adѓncimea arborelui de dinainte de inserare. La inserarea unui nod
terminal într-un arbore AVL este necesara aplicarea a cel mult o rotatie asupra
unui nod. Trebuie, deci sa gasim nodul asupra caruia trebuie aplicata rotatia.
Reprezentam ramura parcursa de la radacina la nodul inserat:

x bf = 1
/
y bf = 0
\
z bf = - 1 (bf = -2 dupa inserare)
\
w bf = 0 (bf = 1 dupa inserare)
/
v bf = 0 (bf = -1 dupa inserare)
\
nodul
inserat

S-a notat pentru fiecare nod bf balance factor (factor de dezechilibrare):


bf(nod) = depth (lchild (nod)) depth (rchild (nod))
adica este diferenta dintre adѓncimea subarborelui stѓng si adѓncimea
subarborelui drept.
Calculam factorii de balansare dupa inserare.
Observatie: Pentru nodul terminal s-a schimbat adѓncimea si factorul de
balansare; bf = -2 dupa inserare devine nod dezechilibrat. Trebuie aplicata,
deci, echilibrarea.

Definitie:
Se numeste nod critic primul nod cu bf 0 întѓlnit la o parcurgere de jos
în sus a ramurii care leaga nodul inserat de radacina.

Observatie: Toate nodurile din ramura care sunt pe nivele inferioare nodului
critic vor capata bf = 1 sau
bf = -1.

La nodul critic exista doua situatii:

1.Nodul critic va fi perfect balansat (bf = 0), daca dezechilibrul creat de


nodul inserat anuleaza dezechilibrul initial al nodului.In acest caz nu este
nevoie de rotatie (el completeaza un gol în arbore).

2.Factorul de balansare devine bf = 2 sau bf = -2 atunci cѓnd nodul inserat


mareste dezechilibrul arborelui (s-a inserat nodul în subarborele cel mai mare)
In acest caz, se aplica o rotatie în urma careia se schimba strucutra
subarborelui, astfel încѓt noua radacina capata bf = 0, conservѓndu-se
adѓncimea.
Concluzie: Problema conservarii proprietatii de echilibrare a arborelui se
rezolva aplicѓnd o rotatie asupra nodului critic numai atunci cѓnd inserarea
dezechilibreaza acest nod.
Costul suplimentar care trebuie suportat se materializeaza prin necesitatea
ca în fiecare nod sa se memoreze factorul de dezechilibrare bf. Acesti factori
de dezechilibrare vor fi actualizati în urma operatiilor de rotatie si inserare
Operatia de stergere într-un AVL implica mai multe rotatii, ea nu se studiaza
în acest curs.

Exemplu: Se da arborele cu urmatoarea structura:


55
/ \
20 80
/ \ \
10 35 90
/ /
5 30

Sa se insereze nodurile 15, apoi 7 si sa se echilibreze arborele.


Inseram prima valoare 15. Comparam mai întѓi cu 55 : e în stѓnga lui,
s.a.m.d. pѓna cѓnd gasim locul ei în pozitia de mai jos. Pentru a doua valoare
de inserat, 7, nodul critic este 55. El este dezechilibrat stѓnga. Deci, va fi
echilibrat la valoarea 2. Este necesara aplicarea unei rotatii asupra radacinii

55
/ \
20 80
/ \ \
10 35 90
/ \ /
5 15 30
\
7

Facem o identificare cu unul din desenele de echilibrare prezentate în cursul


anterior. Se asociaza nodurile:
55->A
20->B etc. =>

20 ----------> noduri implicate


/ \ în
10 55 ------> rotatie
/ \ / \
5 15 35 80
\ / \
7 30 90

In urma unei rotatii simple, factorii de dezechilibru implicati în rotatie


devin zero.
Fie o a treia valoare de inserat 3, apoi a patra 9:

Nodul critic pentru 3 este 5, iar pentru 9 este este 10. Dupa rotatia aplicata
(T2D, T2S vizi), rezulta arborele:
20
/ \
7 55
/ \ / \
5 10 35 80
/ / \ / \
3 9 15 30 90

La rotatia dubla, daca nodul 9 a fost inserat în subarborele T2S,


B are bf = 0 |
A are bf = -1 | exceptie facѓnd nodul C nodul de inserat
La rotatia dubla, daca nodul 9 a fost inserat în subarborele T2D,
B are bf = 1 |
A are bf = 0 | exceptie facѓnd nodul C nodul de inserat

Reprezentarea implicita a arborilor binari


In acest mod de reprezentare se reprezinta arborele printr-un tablou. Fie un
arbore binar complet:
a
/ \
b c
/ \ / \
d e f g
/ \ / \ / \ / \
h i j k l m n o

care are 4 nivele, deci 24 - 1 = 15 noduri.


Asociem acestui arbore un vector V:

structurat astfel: radacina, nivelul 1 de la stѓnga la dreapta, nodurile


nivelului 2 de la stѓnga la dreapta, etc, despartite de linii duble.

Lema
Daca în vectorul V un nod x este reprezentat prin elementul de vector V(i), atunci:
1. left_child(x) este reprezentat în elementul de vector V [2ъi];
2. right_child(x) este reprezentat în elementul de vector V [2ъi + 1];
3. parent(x) este reprezentat în elementul de vector V [[i/2]]
cu observatia ca paranteza patrata interioara este partea întrega.
Demonstratie:
Se face inductie dupa i:
Pentru i = 1 => V[1] radacina
=> V[2] left_child(rad)
=> V[3] right_child(rad)
Presupunem adevarata lema pentru elementul V[i]=>V[2i] left_child
V[2i + 1] right_child
Elementul V[i + 1] este nodul urmator (de pe acelsi nivel sau de pe nivelul urmator) într-o parcurgere binara.
V[i + 1] => left_child în V[2i + 2] = V[2(i + 1)]
right_child în V[2i + 3] = V[2(i + 1) + 1]
Daca avem un arbore binar care nu este complet, reprezentarea lui implicita se
obtine completѓndu-se structura arborelui cu noduri fictive pѓna la obtinerea
unui arbore binar complet.

Arbori heap (heap gramada)

Definitie:
Se numeste arbore heap un arbore binar T = (V, E) cu urmatoarele proprietati:
1) functia key : V R care asociaza fiecarui nod o cheie.
2) un nod v V cu degree(v) > 0 (nu este nod terminal), atunci:
key(v) > key(left_child(v)), daca left_child(v)
key(v) > key(right_child(v)), daca right_child(v)
(Pentru fiecare nod din arbore, cheia nodului este mai mare decѓt cheile
descendentilor).

Exemplu: 99
/ \
50 30
/ \ / \
45 20 25 23
Observatie: De obicei functia cheie reprezinta selectia unui subcѓmp din cѓmpul
de date memorate în nod.
Aplicatii ale arborilor heap
Coada cu prioritate;
Algoritmul Heap_Sort

Arborii heap nu se studiaza complet în acest curs.

Curs 10 Coada cu prioritati

Este o structura de date pentru care sunt definite urmatoarele operatii:


insert (S,a)- insereaza un atom în structura,
remove (S) - extrage din structura atomul cu cheia cea mai mare.
O coada cu prioritati poate fi implementata printr-o lista. Implementarea cozii
cu prioritati prin lista permite definirea operatiilor insert si remove, în
cazul cel mai bun, una este de complexitate O(1) si cealalta este de
complexitate O(n).
Implementarea cozii cu prioritati prin heap face o echilibrare cu complexitatea
urmatoare: una este de complexitate 0(log n) si cealalta de complexitate
0(log n).

Operatia de inserare / \
heap: / \
/ 50 \
/ / \ \
/ 40 30 \
/ / \ /\ \
/ 33 37 12 2 \
/ / \ / _________\
/ 10 15 7 | 42
--------------|

Acelasi arbore în reprezentare implicita:

Operatiile insert si remove pentru arbori heap au o forma foarte simpla cѓnd
utilizeaza reprezentarea implicita. Consideram, în continuare, arbori heap în
reprezentare implicita.

Exemplu: un arbore cu ultimul nivel avѓnd toate nodurile aliniate la stѓnga:

Inseram valoarea 42 se adauga nodul la un nivel incomplet;


In reprezentarea implicita se adauga nodul la sfѓrsit.

insert:

1) In reprezentarea implicita: V [N + 1] = a
N=N+1
In continuare reorganizam structura arborelui astfel încѓt sa-si pastreze
structura de heap.
2) Se utilizeaza interschimbarile. Comparatii:
Iau 2 indici: child = N si
parent = [N/2]
(în cazul nostru N = 11 si [N/2] = 5)
Comparam V[child] cu V[parent]
Interschimbare daca V[child] nu este mai mic decѓt V[parent](se schimb
42 cu 37)
3)Inaintare în sus: child = parent
parent = [child/2]
4) Se reia pasul 2) pѓna cѓnd nu se mai face interschimbarea.
Structura S este un arbore heap. El se afla în reprezentare implicita în 2
informatii:
V vector
N dimensiune
Operatia insert:

insert(V, N, a) // V vectorul ce contine reprezentarea


// implicita a heapu-lui;
// N numarul de noduri din heap,
// ambele sunt plasate
// prin referinta (functia insert
// le poate modifica);
// a atomul de inserat;
{
N = N+1
V[N] = a
child = N
parent = [N/2]
while | parent<=1 do
| if key(V [child]) > key(V [parent]) then
| interchange (V [child],V [parent])
| | child = parent
| |_ parent = [child/2]
|_ else break // parasim bucla parent = 0
}
Operatia remove:
50
/ \
45 43
/ \ / \
33 40 40 20
/ \ / \ \
10 15 7 37 39

se scoate elementul cel mai mare care este radacina heap-ului; se initializeaza
cei 2 indici;
se reorganizeaza structura arborilor: se ia ultimul nod de pe nivelul incomplet
si-l aduc în nodul-radacina, si aceasta valoare va fi retrogradata pîna cѓnd
structura heap-ului este realizata.
parent
conditia de heap: / \
lchild rchild
parent = max(parent, lchild, rchild).
Exista trei cazuri:
1. conditia este îndeplinita deodata;
2. max este în stѓnga retrogradarea se face în stѓnga;
3. max este în dreapta retrogradarea se face în dreapta.

remove(V, N)
{
a= V[1]
N[1]= V[N]
N= N-1
parent= 1
child= 2
while child <= N do
| if child+1 N and key(V[child+1]) >
| > key(V[child]) then
| child= child+1
| if key(V[parent]) < key(V[child]) then
| | interchange(V[parent], V[child])
| | parent= child
| |_ child= 2*parent
| else break
|_ return(a)
}

Complexitatea celor doua operatii insert si remove:


In cazul cel mai defavorabil se parcurge o ramura care leaga radacina de un nod
terminal. La insert avem o comparatie, iar la remove avem doua comparatii.
Rezulta, complexitatea este data de adѓncimea arborelui. Daca N este numarul
de noduri din arbore, 2k N 2k+1 , si adѓncimea arborelui este k+1.

(2 la k)-1 < N<=(2 la k+1)-1 => k = [log2N]

| |
| |
| |
nr. de noduri nr. de noduri
ale arborelui ale arborelui
complet de complet de
adѓncime k adѓncime k+1

log2N <=> log in baza 2 din N


k <= log2N < k + 1 => adѓncimea arborelui este k = [log2N].
Deci complexitatea este O(log N).
A doua aplicatie a heap-urilor este algoritmul Heap_Sort.

Algoritmul Heap_Sort
Heap_Sort(V, n)
{
heap_gen(V, n) N = n
for i = n downto 2 step -1 do
|N=i
|_ V[i] = remove(V, N)
}

Aceasta procedura sorteaza un vector V cu N elemente: transforma vectorul V


într-un heap si sorteaza prin extrageri succesive din acel heap.

Procedura Heap_Sort prin inserari repetate

heap_sort
{
N = 1 //se considera pentru început un
// heap cu un singur
//element, dupa care toate celelalte
// elemente vor fi
//inserate în acest heap
for i = 2 to n do
insert(V, N, V[i])
}
Studiul complexitatii
Observatii:
Se fac mai multe operatii insert în heap-uri a caror dimensiune creste de la
1 la N;
Se fac n-1 operatii insert în heap-uri cu dimensiunea N<=n
Rezulta complexitatea acestor operatii nu depaseste O(nlog n). Facem un studiu
pentru a vedea daca nu cumva ea este mai mica decѓt O(nlog n).
Cazul cel mai defavorabil este situatia în care la fiecare inserare se parcurge
o ramura completa. De fiecare data inserarea unui element se face adaugѓnd un
nod la ultimul nivel. Pentru nivelul 2 sunt doua noduri. La inserarea lor se
va face cel mult o retrogradare (comparatie).
Pe ultimul exemplu de arbore, avem:
nivelul 2 : 2 noduri 1 comparatie
nivelul : 4 noduri 2 comparatii
nivelul 4 : 8 noduri 3 comparatii
--------------------------------------------------
nivelul i : 2i-1 noduri i-1 comparatii

Considerѓnd un arbore complet (nivel complet) N = 2k - 1 numarul total de


comparatii pentru toate nodurile este T(n) de la nivelul 2 la nivelul k.
Vom calcula:

Sa aratam:
cu tehnica . Asadar:

Rezulta:
iar: ,
din
Rezulta:
------------------------
termen dominant

Facѓndu-se majorari, rezulta complexitatea O(nlog n).


Prezentam acum alta strategie de obtinere a heap_gen cu o complexitate mai
buna:
Construim heap-ul de jos în sus (de la dreapta spre stѓnga). Cele mai multe
noduri sunt la baza, doar nodurile din vѓrf parcurg drumul cel mai lung.

Elementele V[i+1,N] îndeplinesc conditia de structura a heap-ului:


oricare ar fi j >i avem:V[j] > V[2*j] , daca 2*j<=N
V[j] > V[2*j +1] , daca 2*j + 1 <= N
Algoritmul consta în adaugarea elementului V[i] la structura heap-ului. El va
fi retrogradat la baza heap-ului (prelucrare prin retrogradare):

retrogradare(V, N, i)
{
parent = i
child = 2*i // fiu stѓnga al lui i
while child N do
| if child+1<=N and
| key(V[child+1]) > key(V[child]) then
| child = child+1
| if key(V[parent]) < key(V[child]) then
| | interchange(V[parent], V[child])
| | parent = child
| |_ child = 2*parent
|_ else break
}
In aceasta situatie, vom avea:

heap_gen
{
for i = [N/2] downto 1 step -1 do
retrogradare(V, n, i)
}

Complexitatea acestei operatii


(2k <=> 2 la puterea k;oricare ar fi k)
Fie un arbore complet cu n = 2k - 1. Cazul cel mai defavorabil este situatia
în care la fiecare retrogradare se parcurg toate nivelele:
nivel k : nu se fac operatii
nivel k-1 : 2k-2 noduri o operatie de comparatie
nivel k-2 : 2k-3 noduri 2 operatii
------------------------------------------------------------------
nivel i : 2i-1 noduri k-i operatii
-------------------------------------------------------------------
nivel 2 : 21 noduri k-2 operatii
nivel 1 : 20 noduri k-1 operatii
Se aduna, si rezulta:
Tehnica de calcul este aceeasi:

Rezulta:

-------
termen dominant

Rezulta complexitatea este O(n). Comparѓnd cu varianta anterioara,


în aceasta varianta (cu heap-ul la baza) am cѓstigat un ordin de complexitate.
Rezulta, complexitatea algoritmului Heap_Sort este determinata de functiile
remove ce nu pot fi aduse la mai putin de complexitate O(log n). Rezulta:
Heap_Sort = O(n) + O(nъlog n)
---------------
termen ce determina complexitatea

Rezulta complexitatea alg. Heap_Sort = O(nъlog n)

Curs 10

Coada cu prioritati

Este o structura de date pentru care sunt definite urmatoarele operatii:


insert (S,a)- insereaza un atom în structura,
remove (S) - extrage din structura atomul cu cheia cea mai mare.
O coada cu prioritati poate fi implementata printr-o lista. Implementarea cozii
cu prioritati prin lista permite definirea operatiilor insert si remove, în
cazul cel mai bun, una este de complexitate O(1) si cealalta este de
complexitate O(n).
Implementarea cozii cu prioritati prin heap face o echilibrare cu complexitatea
urmatoare: una este de complexitate 0(log n) si cealalta de complexitate
0(log n).

Operatia de inserare / \
heap: / \
/ 50 \
/ / \ \
/ 40 30 \
/ / \ /\ \
/ 33 37 12 2 \
/ / \ / _________\
/ 10 15 7 | 42
--------------|

Acelasi arbore în reprezentare implicita:

Operatiile insert si remove pentru arbori heap au o forma foarte simpla cѓnd
utilizeaza reprezentarea implicita. Consideram, în continuare, arbori heap în
reprezentare implicita.

Exemplu: un arbore cu ultimul nivel avѓnd toate nodurile aliniate la stѓnga:

Inseram valoarea 42 se adauga nodul la un nivel incomplet;


In reprezentarea implicita se adauga nodul la sfѓrsit.

insert:

1) In reprezentarea implicita: V [N + 1] = a
N=N+1
In continuare reorganizam structura arborelui astfel încѓt sa-si pastreze
structura de heap.
2) Se utilizeaza interschimbarile. Comparatii:
Iau 2 indici: child = N si
parent = [N/2]
(în cazul nostru N = 11 si [N/2] = 5)
Comparam V[child] cu V[parent]
Interschimbare daca V[child] nu este mai mic decѓt V[parent](se schimb
42 cu 37)
3)Inaintare în sus: child = parent
parent = [child/2]
4) Se reia pasul 2) pѓna cѓnd nu se mai face interschimbarea.
Structura S este un arbore heap. El se afla în reprezentare implicita în 2
informatii:
V vector
N dimensiune
Operatia insert:

insert(V, N, a) // V vectorul ce contine reprezentarea


// implicita a heapu-lui;
// N numarul de noduri din heap,
// ambele sunt plasate
// prin referinta (functia insert
// le poate modifica);
// a atomul de inserat;
{
N = N+1
V[N] = a
child = N
parent = [N/2]
while | parent<=1 do
| if key(V [child]) > key(V [parent]) then
| interchange (V [child],V [parent])
| | child = parent
| |_ parent = [child/2]
|_ else break // parasim bucla parent = 0
}
Operatia remove:
50
/ \
45 43
/ \ / \
33 40 40 20
/ \ / \ \
10 15 7 37 39

se scoate elementul cel mai mare care este radacina heap-ului; se initializeaza
cei 2 indici;
se reorganizeaza structura arborilor: se ia ultimul nod de pe nivelul incomplet
si-l aduc în nodul-radacina, si aceasta valoare va fi retrogradata pîna cѓnd
structura heap-ului este realizata.
parent
conditia de heap: / \
lchild rchild
parent = max(parent, lchild, rchild).
Exista trei cazuri:
1. conditia este îndeplinita deodata;
2. max este în stѓnga retrogradarea se face în stѓnga;
3. max este în dreapta retrogradarea se face în dreapta.

remove(V, N)
{
a= V[1]
N[1]= V[N]
N= N-1
parent= 1
child= 2
while child <= N do
| if child+1 N and key(V[child+1]) >
| > key(V[child]) then
| child= child+1
| if key(V[parent]) < key(V[child]) then
| | interchange(V[parent], V[child])
| | parent= child
| |_ child= 2*parent
| else break
|_ return(a)
}

Complexitatea celor doua operatii insert si remove:


In cazul cel mai defavorabil se parcurge o ramura care leaga radacina de un nod
terminal. La insert avem o comparatie, iar la remove avem doua comparatii.
Rezulta, complexitatea este data de adѓncimea arborelui. Daca N este numarul
de noduri din arbore, 2k N 2k+1 , si adѓncimea arborelui este k+1.

(2 la k)-1 < N<=(2 la k+1)-1 => k = [log2N]

| |
| |
| |
nr. de noduri nr. de noduri
ale arborelui ale arborelui
complet de complet de
adѓncime k adѓncime k+1

log2N <=> log in baza 2 din N


k <= log2N < k + 1 => adѓncimea arborelui este k = [log2N].
Deci complexitatea este O(log N).
A doua aplicatie a heap-urilor este algoritmul Heap_Sort.

Algoritmul Heap_Sort
Heap_Sort(V, n)
{
heap_gen(V, n) N = n
for i = n downto 2 step -1 do
|N=i
|_ V[i] = remove(V, N)
}

Aceasta procedura sorteaza un vector V cu N elemente: transforma vectorul V


într-un heap si sorteaza prin extrageri succesive din acel heap.

Procedura Heap_Sort prin inserari repetate

heap_sort
{
N = 1 //se considera pentru început un
// heap cu un singur
//element, dupa care toate celelalte
// elemente vor fi
//inserate în acest heap
for i = 2 to n do
insert(V, N, V[i])
}

Studiul complexitatii
Observatii:
Se fac mai multe operatii insert în heap-uri a caror dimensiune creste de la
1 la N;
Se fac n-1 operatii insert în heap-uri cu dimensiunea N<=n
Rezulta complexitatea acestor operatii nu depaseste O(nlog n). Facem un studiu
pentru a vedea daca nu cumva ea este mai mica decѓt O(nlog n).
Cazul cel mai defavorabil este situatia în care la fiecare inserare se parcurge
o ramura completa. De fiecare data inserarea unui element se face adaugѓnd un
nod la ultimul nivel. Pentru nivelul 2 sunt doua noduri. La inserarea lor se
va face cel mult o retrogradare (comparatie).
Pe ultimul exemplu de arbore, avem:
nivelul 2 : 2 noduri 1 comparatie
nivelul : 4 noduri 2 comparatii
nivelul 4 : 8 noduri 3 comparatii
--------------------------------------------------
nivelul i : 2i-1 noduri i-1 comparatii

Considerѓnd un arbore complet (nivel complet) N = 2k - 1 numarul total de


comparatii pentru toate nodurile este T(n) de la nivelul 2 la nivelul k.
Vom calcula:

Sa aratam:
cu tehnica . Asadar:
Rezulta:
iar: ,
din
Rezulta:
------------------------
termen dominant

Facѓndu-se majorari, rezulta complexitatea O(nlog n).


Prezentam acum alta strategie de obtinere a heap_gen cu o complexitate mai
buna:
Construim heap-ul de jos în sus (de la dreapta spre stѓnga). Cele mai multe
noduri sunt la baza, doar nodurile din vѓrf parcurg drumul cel mai lung.

Elementele V[i+1,N] îndeplinesc conditia de structura a heap-ului:


oricare ar fi j >i avem:V[j] > V[2*j] , daca 2*j<=N
V[j] > V[2*j +1] , daca 2*j + 1 <= N
Algoritmul consta în adaugarea elementului V[i] la structura heap-ului. El va
fi retrogradat la baza heap-ului (prelucrare prin retrogradare):

retrogradare(V, N, i)
{
parent = i
child = 2*i // fiu stѓnga al lui i
while child N do
| if child+1<=N and
| key(V[child+1]) > key(V[child]) then
| child = child+1
| if key(V[parent]) < key(V[child]) then
| | interchange(V[parent], V[child])
| | parent = child
| |_ child = 2*parent
|_ else break
}

In aceasta situatie, vom avea:

heap_gen
{
for i = [N/2] downto 1 step -1 do
retrogradare(V, n, i)
}

Complexitatea acestei operatii


(2k <=> 2 la puterea k;oricare ar fi k)
Fie un arbore complet cu n = 2k - 1. Cazul cel mai defavorabil este situatia
în care la fiecare retrogradare se parcurg toate nivelele:
nivel k : nu se fac operatii
nivel k-1 : 2k-2 noduri o operatie de comparatie
nivel k-2 : 2k-3 noduri 2 operatii
------------------------------------------------------------------
nivel i : 2i-1 noduri k-i operatii
-------------------------------------------------------------------
nivel 2 : 21 noduri k-2 operatii
nivel 1 : 20 noduri k-1 operatii
Se aduna, si rezulta:
Tehnica de calcul este aceeasi:

S-ar putea să vă placă și