Académique Documents
Professionnel Documents
Culture Documents
_________________________________________________________________________
_________________________________________________________________________
1
Structuri de date si algortitmi
_________________________________________________________________________
Curs de Bârsan M.
BIBLIOGRAFIE
_________________________________________________________________________
2
Structuri de date si algortitmi
_________________________________________________________________________
Curs 1
Structuri de date
Structurile de date erau definite în limbajul C drept organizarea datelor primare. În limbajul C++,
acestea reprezinta o colectie de date împreuna cu operatiile lor (data obiect).
De exemplu, prin multimea N a numerelor naturale se va întelege si elementele multimii N, dar si
operatiile ce se pot efectua cu acestea: 1, 2, 3, ..., +, -, *, /. Sau prin multimea numerelor complexe:
C: {z = a + bi/a si bR, i = sqrt(-1)}, -, +, *, /, etc.
Algoritmul se defineste ca o metoda de rezolvare a unei probleme într-un numar de pasi, metoda
efectiva (pas cu pas), finita (are un numar finit de pasi) si cu o intrare si o iesire (I/O).
Un algoritm poate avea un limbaj natural (o specificatie), un limbaj matematic (alta specificatie),
un limbaj de programare (alta specificatie), s.a.m.d. Între limbajul natural si cel în C++, de exemplu, vom
folosi
un pseudolimbaj (de trecere).
Modele de calcul
Masina este un model de calcul care se constituie din Unitate Centrala (U.C.), Memorie (M), I/O.
Exemple de modele de calcul:
_________________________________________________________________________
3
Structuri de date si algortitmi
_________________________________________________________________________
Masina TURING
1. MODELUL functional - bazat pe teoria - calcul.
Limbajele în acest model sunt LISP, ML, MIRANDA, etc. iar programarea este în acest caz programare
functionala.
2. MODELUL logic - bazat pe predicate de ordin I.
Un exemplu de limbaj în acest model este PROLOG.Iar programarea se numeste programare logica.
În cele ce urmeaza ne vom limita la modelul Von Newman.
Asadar limbajul C++ se constituie din:
· variabile;
· identificatori;
· constante;
· operatori numerici obisnuiti;
· operatori relationali;
· structuri de control a executiei: if/else, while, do/while, for, etc.
Requirmens
Design
Testing
Implement
_________________________________________________________________________
4
Structuri de date si algortitmi
_________________________________________________________________________
Programul rezultat se compara cu cerintele, si daca nu corespunde, se reia ciclul ori de câte ori este
nevoie.
Analiza performantelor presupune renuntând la acuratete estimarea timpului de lucru si a
spatiului de stocare, nestiind înca limbajul care va fi folosit si calitatea programului ce se va obtine.
Presupunând ca modelul RAM de masina pe care lucram executa instructiuni pseudocod, si ca
fiecare instructiune pseudocod consuma acelasi timp de executie,rezulta ca timpul estimat pentru executia
unui algoritm este proportional cu numarul instructiunilor executate de acel algoritm.
Dimensiunea datelor de intrare este o functie f(n) care calculeaza, pentru un n dat, numarul de
instructiuni al algoritmului respectiv.
f (n) c log 2 n
_________________________________________________________________________
5
Structuri de date si algortitmi
_________________________________________________________________________
Curs 2
Structuri de date
Tablouri
Tabloul este o colectie de date în care fiecare element poate fi identificat pe baza unui index,
colectia asigurând timp de acces constant pentru fiecare element. Prin reprezentarea tabloului se intelege
plasarea elementelor în locatii succesive de memorie:
Locatiile de memorie pot fi numerotate, putând accesa direct orice element. Timpul de accesare al
elementului numar, de exemplu, fiind acelasi cu timpul de accesare al elementului n.
Liste
O lista este o multime de obiecte, numite atomi, pentru care este definita o ordine:
a1 a 2 a 3 a 4 a 5 ... a n
Operatiile principale care se pot se face în cadrul listei:
· inserare: introducerea unui nou element într-o anumita pozitie;
· stergere: scoaterea unui element dintr-o anumita pozitie;
· consultare: accesul asupra fiecarui element din lista;
· parcurgere.
Tipuri speciale de liste
_________________________________________________________________________
6
Structuri de date si algortitmi
_________________________________________________________________________
Stive
O stiva este o lista în care operatiile de inserare, stergere si consultare se efectueaza asupra unui
capat al listei. Stiva se poate asemana cu un recipient în care se pun si se pot scoate diferite obiecte.
Operatia care pune obiectele în stiva se numeste push, iar cea care scoate obiecte din stiva se numeste pop.
Capatul accesibil pentru stiva se numeste vârful stivei:
Asadar:
push insereaza un element în vârful stivei;
pop sterge un element din vârful stivei;
top consulta (citeste) elementul din vârful stivei;
top(S) citeste vârful stivei.
push pop
Cozi
O coada este o lista în care inserarea se face la un capat (la sfârsit), iar stergerea se face de la
celalalt capat al cozii (de la început). Partea din fata a cozii (a primului element) se numeste front, iar
partea din spate (a ultimului element) se numeste end.
Operatia de inserare în coada add (put)
Operatia de stergere din coada del (get)
add del
Implementari de liste
O lista poate fi realizata ca: lista ordonata sau
lista înlantuita
Lista ordonata tine cont de o ordine a pozitiilor elementelor listei, nu de continutul elementelor.
Inserarea într-o lista de forma:
a1 a 2 a3 ... an //////
se face cu deplasare de o pozitie la stânga din punctul în care dorim sa inseram (pentru a face acest loc
noului element).
Deplasarea se face înspre zona de memorie libera (cea hasurata) presupunem ca dorim sa
inseram pe a în pozitia i):
_________________________________________________________________________
7
Structuri de date si algortitmi
_________________________________________________________________________
/////////////////////////////
////////// /////////////////
///////////////////////////////////
Stergerea: deplasarea cu o pozitie la stânga din acel punct.
a1 a2 a3
cap de lista
Capul de lista este un pointer separat care duce la primul element din lista, iar 0 este pointer-ul nul
(NULL) cu valoare zero. La implementarea listei înlantuite concentrarea se face la fluxul instructiunilor, nu
la declaratiile de variabile.
struct Element {
Atom data;
Element* link;
};
Se va scrie:
_________________________________________________________________________
8
Structuri de date si algortitmi
_________________________________________________________________________
tipul lui x ---------------- Element* x
data(x) ---------------- x data
link(x) ---------------- x link
y = get_sp() ---------------- y = new Element
ret_sp() ---------------- delete x
1. Inserarea
Inserarea se face: în fata, sau
în interior (la mijloc ori la sfârsit)
a) Inserarea în fata
a1 a2
1
1
2
a0
x
1 - Prima atribuire: link(x) = l
2 - A doua atribuire: l = x
Observatie: daca lista este vida, l are valoarea 0 (capatul listei) iar atribuirile de mai sus ramân valabile:
1 0
a0 0
1
a0 0
x
b) Inserarea la mijloc
_________________________________________________________________________
9
Structuri de date si algortitmi
_________________________________________________________________________
ai-1 ai ai+1
2
1
a
y
x
Analog pentru inserarea la sfârsit.
1 - Prima atribuire: link(x) = link(y)
2 - A doua atribuire: link(y) = x
1
a1 a2
2
1 - Prima atribuire: p = l
2 - A doua atribuire: l = link(l)
3 - ret_sp(p)
Situatia finala:
P
1
cap
0
(1) p = cap;
(2) cap = cap link;
_________________________________________________________________________
10
Structuri de date si algortitmi
_________________________________________________________________________
delete p ; // Elibereaza zona de memorie
Elementul care a fost izolat de lista trebuie sa fie procesat în continuare, cel putin pentru a fi
eliberata zona de memorie pe care o ocupa, de aceea adresa lui trebuie salvata (sa zicem în variabila pointer
p).
2.b) Stergerea de la mijloc sau de la sfârsit
Situatia initiala:
Situatia finala:
(1) p = q link;
(2) q link = p link; // sau q link = q link link;
delete p;
Observatii:
Atunci când q indica penultimul element dintr-o lista, atribuirile de mai sus functioneaza corect si
sterg ultimul element din lista.
Nu se poate face stergerea elementului indicat de q fara parcurgerea listei de la capat.
_________________________________________________________________________
11
Structuri de date si algortitmi
_________________________________________________________________________
Curs 3
Presupune adaugarea unui element într-o pozitie specificata în lista. Exista posibilitati diferite de a
specifica pozitia în care vrem sa inseram elementul:
· Situatia în care pozitia de inserat este data printr-un numar care sa indice al câtelea element trebuie sa
fie în lista elementul inserat;
· Situatia în care pozitia de inserat este data prin valoarea atomului dupa care sau înainte de care se face
inserarea;
· Situatia în care pozitia de inserat poate fi data implicit prin valoarea atomului de inserat.
insert (l, a, b)
l=p;
}
else
{
q=l;
while ((link(q)!=0)and (data(link(q)!=b))
do q=link(q);
link(p)=link(q);
link(q)=p;
}
}
_________________________________________________________________________
12
Structuri de date si algortitmi
_________________________________________________________________________
Operatia de stergere dintr-o lista înlantuita
Operatia delete sterge un atom dintr-o lista. Deci vom avea în pseudocod, o functie de forma:
delete(l, a)
// l lista
// a valoarea atomului care trebuie sters
{
if l=0 then eroare ("Atomul nu se afla în lista")
else if data(l)=a then |¯ p=1
| l=link(l)
|_ ret_sp(p)
else |¯ q=l
| while(link(q)!=0)and(data(link(q))!=a) do
| q=link(q)
| if link(q=0) then eroare("S-a ajuns la sfârsitul
| listei si atomul nu a fost gasit")
| else |¯ p=link(q)
| | link(q)=link(p)
|_ |_ ret_sp(p)
}
Operatia de parcurgere a listei înlantuite consta dintr-o secventa de instructiuni care se foloseste de
fiecare data când dorim sa prelucram elementele listei într-un anumit scop.
O parcurgere a listei presupune o prelucrare efectuata asupra fiecarui element din lista (asadar nu o
functie, ci o secventa de instructiuni):
Fie p pointer-ul care indica pe rând fiecare element al listei, si consideram ca p începe cu l:
Stive ordonate
O stiva este o structura de date de tip "container" (depoziteaza obiecte de un anumit tip) organizata
dupa principiul LIFO (Last In First Out).
Operatiile de acces la stiva (push - adauga un element in stiva si pop - scoate un element din stiva)
sunt create astfel încât pop scoate din stiva elementul introdus cel mai recent.
O stiva este un caz particular de lista, si anume este o lista pentru care operatiile de acces (inserare,
stergere, accesare element) se efectueaza la un singur capat al listei.
Daca STACK este de tip stiva si ATOM tipul obiectelor continute în stiva atunci operatiile care
definesc tipul structura de stiva pentru tipul STACK sunt:
· CREATE() STACK
Operatia CREATE nu primeste parametri, creeaza o stiva care pentru început este vida (nu contine
nici un obiect).
· PUSH(STACK, ATOM) STACK
_________________________________________________________________________
13
Structuri de date si algortitmi
_________________________________________________________________________
Operatia PUSH primeste ca parametri o stiva si un obiect si produce stiva modificata prin
adaugarea obiectului în stiva.
· POP(STACK) STACK, ATOM
Operatia POP primeste ca parametri o stiva pe care o modifica scotând un obiect. De asemenea
produce ca rezultat obiectul scos din stiva.
· TOP(STACK) ATOM
Operatia TOP întoarce ca rezultat obiectul din vârful stivei pe care o primeste ca parametru.
· ISEMPTY(STACK) boolean
Operatia ISEMPTY este folosita pentru a testa daca stiva este vida.
Facem notatiile:
S stiva
S.vect vectorul în care se reprezinta elementele stivei S
S.sp indicele vârfului stivei
Elementele sunt memorate asadar unul dupa altul în vectori, nefiind neaparat în ordine crescatoare.
Zona de memorat trebuie sa contina aceste doua informatii: S.vect si S.sp grupate într-o structura:
struct Stack {
int sp;
Atom vect [DIMMAX]
};
push(S,a)
{
if S.sp >=DIMMAX then eroare("Stiva plina")
else |¯ S.sp=S.sp+1
|_ S.vect[S.sp]=a //atomul este pus pe prima
//pozitie
}
pop(S)
{
if S.sp=0 then eroare ("Stiva vida")
else S.sp=S.sp-1
}
Observatie: Se obisnuieste ca pe lânga stergerea elementului, functia pop sa returneze elementul scos din
lista.
top(S)
{
if S.sp=0 then eroare("Stiva vida")
else return(S.vect[S.sp])
}
_________________________________________________________________________
14
Structuri de date si algortitmi
_________________________________________________________________________
Stive înlantuite
O stiva poate fi implementata ca o lista înlantuita pentru care operatiile de acces se fac numai
asupra primului element din lista. Deci, operatia PUSH va însemna inserare în prima pozitie din lista (în
fata) iar POP va însemna stergerea primului element din lista. Pentru a manevra o stiva vom avea nevoie de
un pointer la primul element din înlantuire, deci, vom echivala tipul Stack cu tipul "pointer la element de
lista", iar functiile care implementeaza operatiile de acces vor avea aceleasi prototipuri cu cele date mai
sus.
struct Element {
Atom data;
Element* link; //legatura
};
typedef Element* Stack;
Fie S pointer-ul la primul element din înlantuire, se echivaleaza tipul Stack cu typedef Element*
Stack, iar conditia de stiva vida este S=0 :
push(S,a)
{
p=get_sp()
data(p)=a
link(p)=S
S=p
}
pop(S)
{
if S=0 then eroare("Stiva vida")
else |¯ p=S;
| S=link(S)
|_ ret_sp(p)
}
top(S)
{
if S=0 then eroare("Stiva vida")
else return(data(S))
}
isEmpty(S)
{
return(S==0)
}
_________________________________________________________________________
15
Structuri de date si algortitmi
_________________________________________________________________________
Stivele sunt cele mai simple structuri de date, ele având si operatiile imediate.
Cozi ordonate
O coada este o lista în care operatiile de acces sunt restrictionate la inserarea la un capat si
stergerea de la celalat capat.
coada
PUT GET
ultimul primul
Pricipalele operatii de acces sunt:
· PUT(Coada, Atom) Coada
Adauga un element la coada.
· GET(Coada) Coada, Atom
Scoate un Atom din coada.
Returneaza atomul scos.
head tail
Conditia "coada vida" este echivalenta cu: head = tail. Initial indicatorii vor fi initializati astfel
încât sa indice ambii primul element din vector.
Operatia PUT înseamna:
- V[tail] primeste Atomul adaugat;
- incrementeaza tail.
Operatia GET înseamna:
- întoarce V[head];
- incrementeaza head
Se observa ca adaugari si stergeri repetate în coada deplaseaza continutul cozii la dreapta, fata de
începutul vectorului. Pentru a evita acest lucru ar trebui ca operatia GET sa deplaseze la stânga continutul
cozii cu o pozitie. Primul element care urmeaza sa fie scos va fi întotdeauna în prima pozitie, indicatorul
head pierzându-si utilitatea. Dezavantajul acestei solutii consta în faptul ca operatia GET necesita o
parcurgere a continutului cozii.
Facem notatiile:
C coada
C.vect vectorul în care sunt memorate elementele cozii
C.head indicele elementului ce va fi scos din coada la urmatoarea operatie get
C.tail indicele (pozitia) în care va fi memorat urmatorul element adaugat la coada.
_________________________________________________________________________
16
Structuri de date si algortitmi
_________________________________________________________________________
Conditia coada vida este C.head=C.tail.
Functia put pune în coada C un atom a:
put(C,a)
{
if C.tail>DIMMAX then eroare("Coada plina")
else |¯ C.vect [C.tail]=a
|_ C.tail=C.tail+1
}
get(C)
{
if C.head=C.tail then eroare("Coada vida")
else |¯ C.head=C.head+1
|_ return C.vect [C.head-1];
}
isEmpty(C)
{
return(C.head==C.tail)
}
1 2 3 4 5 6 7 8
head tail
_________________________________________________________________________
17
Structuri de date si algortitmi
_________________________________________________________________________
sau asa:
5 6 7 8 1 2 3 4
head tail
head tail
head tail
Observatie:
În coada circulara de dimensiune DIMMAX pot fi memorate DIMMAX elemente.
_________________________________________________________________________
18
Structuri de date si algortitmi
_________________________________________________________________________
În cazul cozilor circulare se modifica doar operatiile put si get:
put(C,a)
{
if C.head=inc(C.tail) then eroare("Coada plina")
else |¯ C.vect[C.tail]=a
|_ C.tail=inc(C.tail)
}
get(C)
{
if C.head=C.tail then eroare("Coada vida")
else |¯ a=C.vect [C.head]
| C.head= inc (C.head)
|_ return(a)
}
_________________________________________________________________________
19
Structuri de date si algortitmi
_________________________________________________________________________
Curs 4
Cozi înlantuite
O coada poate fi implementata printr-o lista înlantuita la care operatiile de acces sunt restrictionate
corespunzator.
nil
head tail
nil
head tail
_________________________________________________________________________
20
Structuri de date si algortitmi
_________________________________________________________________________
Operatiile cozii înlantuite
put(C,a)
{
p= get_sp();
data(p)=a;
link(p)= 0;
if C.head=0 then |¯ C.head= p
|_ C.tail= p
else |¯ link(C.tail)= p
|_ C.tail= p
}
get(C,a)
{
if C.head= 0 then eroare("Coada goala")
else |¯ a= data(C.head)
| p= C.head
| C.head= link(C.head)
| ret_sp(p)
|_ return(a)
}
Functia front returneaza elementul din fata cozii, fara a-l scoate din coada.
front(C)
{
if C.head=0 then eroare("Coada vida")
else return data(C.head)
}
isEmpty(C)
{
return(C.head=0)
}
_________________________________________________________________________
21
Structuri de date si algortitmi
_________________________________________________________________________
Cozi înlantuite circulare
Daca reprezentam coada printr-o structura înlantuita circulara va fi nevoie de un singur pointer prin
intermediul caruia se pot face ambele operatii de adaugare si stergere din coada:
primul ultimul
.....
tail
Fie:
C pointer la primul () element din coada
link(C) pointer la ultimul() element din coada
Operatiile de plasare si de scoatere din coada, sunt:
put(C,a)
{
p= get_sp()
data(p)=a
if C=0 then |¯ C= p
|_ link(C)= p
else |¯ link(p)= link(C)
| link(C)= p
|_ C= p
}
get(C)
{
if C= 0 then eroare("Coada vida")
else if C=link(C) then |¯ a= data(C)
| ret_sp(C)
| C= 0
|_ return(a)
else |¯ {p= link(C)
| link(C)= link(p)
| a= data(p)
| ret_sp(p)
|_ return(a)
}
Complexitatea algoritmilor
_________________________________________________________________________
22
Structuri de date si algortitmi
_________________________________________________________________________
Definitie
Fie f : N N si g : N N doua functii.
Spunem ca f O(g) si notam f = O(g) daca si numai daca o constanta c R si un numar n0 N
astfel încât pentru n n0 f(n) cg(n)
Observatie:
f : N N este o functie f(n) cu n dimensiunea datelor de intrare.
f(n) reprezinta timpul de lucru al algoritmului exprimat în "pasi".
Lema 1
Daca f este o functie polinomiala de grad k, de forma:
f(n) = ak nk + ak-1nk-1 + ... + a1 n + a0, atunci f = O(nk).
maxsir(A,n)
{
max = A[1]
for i= 2 to n do
if A[i] > max then
max = A[i]
return (max)
}
Exprimam:
T(n) timpul de executie în pasi al acestui algoritm;
T(n)= 1 + 2(n-1) = numarul de atribuiri si comparatii
Cazul cel mai defavorabil: situatia în care vectorul este ordonat crescator (pentru ca de fiecare data
se face si comparatie si atribuire).
_________________________________________________________________________
23
Structuri de date si algortitmi
_________________________________________________________________________
Putem spune ca T(n) = O(n), este o functie polinomiala de gradul I. Conteaza doar Ordinul
polinomului, nu coeficientul termenului de grad maxim. Iar la numararea pasilor ne concentram asupra
numarului buclelor, nu asupra pasilor din interiorul buclei.
Complexitate: O(n)
insertion_sort(A,n)
{
for k= 2 to n do
|¯temp = A[k]
|i=k-1;
|while (i>=1) and (A[i] > temp) do |¯ A[i+1] = A[i]
| |_ i=i-1
|_ A[i+1] = temp
}
Cazul cel mai defavorabil: situatia în care deplasarea (la dreapta cu o pozitie în vederea înserarii)
se face pâna la începutul vectorului, adica sirul este ordonat descrescator.
Exprimarea timpului de lucru:
T(n) = 3·(n - 1) + (1 + 2 + 3+ ... + n - 1) = 3(n-1) + 3n (n - 1)/2
Rezulta complexitatea: T(n) = O(n2) functie polinomiala de gradul II.
Observatie: Când avem mai multe bucle imbricate, termenii buclei celei mai interioare dau gradul
polinomului egal cu gradul algoritmului.
prod_mat(A,B,C,n)
{
for i = 1 to n do
for j = 1 to n do
|¯ C[i,j] = 0
|for k = 1 to n do
|_ C[i,j] = C[i,j] + A[i,k] * B[k,j]
}
Rezulta complexitatea O(n3).
_________________________________________________________________________
24
Structuri de date si algortitmi
_________________________________________________________________________
Fie A, de ordin n, un vector ordonat crescator. Se cere sa se determine daca o valoare b se afla
printre elementele vectorului. Limita inferioara se numeste low, limita superioara se numeste high, iar
mijlocul virtual al vectorului, mid (de la middle).
Binary_search(A,n,b)
{
low = 1;
high = n;
while low <= high do
|¯ mid = [(low + high)/2] //partea întreaga
| if A[mid]=b then return (mid)
| else if A[mid]>b then high=mid-1 //restrâng
| //cautarea la partea stânga
|_ else low = mid+1 //restrâng cautarea la dreapta
return(0)
}
Proprietati:
1) Fie f, g : N N.
Daca f = O(g) | k f = O(g)
| f = O(k g) , k R constant.
2) Fie f, g, h : N N.
si: f = O(g) |
g = O(h) | f = O(h)
_________________________________________________________________________
25
Structuri de date si algortitmi
_________________________________________________________________________
Aceasta proprietate permite ca, atunci când avem doua bucle imbricate (de complexitati diferite),
complexitatea totala sa se obtina înmultindu-se cele doua complexitati. Cele doua complexitati se aduna,
daca buclele sunt succesive.
Teorema:
Oricare ar fi doua constante c > 0, a > 1, si f : N N, o functie monoton strict crescatoare, atunci:
(f(n))c= O(af(n))
Demonstratia se bazeaza pe limita:
xp
lim ( a , p )
x a x
Între clasa functiilor logaritmice, si cea a functiilor polinomiale exista relatia: O(nc) O(an).
Au loc urmatoarele incluziuni:
O(1) O(log n) O(n) O(nlog n) O(n2) O(nklog n) O(nk+1) O(2n)
Pentru calculul complexitatii se va încerca încadrarea în clasa cea mai mica de complexitate din acest sir:
O(1) clasa algoritmilor constanti;
O(log n) clasa algoritmilor logaritmici;
O(n) clasa algoritmilor liniari;
O(nlog n) clasa algoritmilor polilogaritmici;
O(n2) clasa algoritmilor patratici;
O(nklog n) clasa algoritmilor polilogaritmici;
O(nk+1) clasa algoritmilor polinomiali;
O(2n) clasa algoritmilor exponentiali.
n
n(n 1) (2n 1)
i
i 1
2
6
O(n 3 )
n
n 2 ( n 1) 2
i3
i 1 4
O(n 4 )
2
i 0
1
2 n 1 - 1
n
Sa calculam, de exemplu, suma: i 2
i 1
1
n
Se noteaza: G (n) i 21
i 1
_________________________________________________________________________
26
Structuri de date si algortitmi
_________________________________________________________________________
n n n n
G (n) 2 G (n) 2i 21 i 21 i 211 i 21
i 1 i 1 i 1 i 1
n n
n 2 n 1 2 (i 1 i ) 21 n 2 n 1 2 21 (n 1) 2 n 1 2
i 2 i 2
n
Prin aceeasi tehnica se calculeaza suma: (n 1) 2
i 1
1
_________________________________________________________________________
27
Structuri de date si algortitmi
_________________________________________________________________________
Curs 5
n
Am vazut ca: i 2
i 1
n 1
2 n 1 2 n
cazul n=3
Se cere sa se deplaseze aceasta stiva pe o alta tija, folosind ca manevra o tija auxiliara, respectându-se
conditia << Un disc nu poate fi plasat decât peste un disc mai mare >>.
Problema P(n) a deplasarii a n discuri, se rezolva prin deplasari succesive ale discurilor de pe o tija
pe alta. Deplasarea de pe o tija pe alta este echivalenta cu deplasarea a n-1 discuri de pe tija intiala (ti) pe
tija de manevra, apoi plasarea celui mai lung disc pe tija finala, pentru ca la sfârsit sa se aduca de pe tija de
manevra (tm), pe tija finala (tf), cele n-1 discuri deplasate.
Primele miscari s-ar figura astfel:
ti tf t
m
ti tf tm
Procedura Hanoi:
_________________________________________________________________________
28
Structuri de date si algortitmi
_________________________________________________________________________
f (n 1) T ( n 1) x0 , cu x0 const.
2 n 1 2 T (n) 1 T(n) = 2 n 1
Facând identificarea: Ordinul este O(2n),
f(1) x 0
adica o complexitate exponentiala.
1. f ( n ) a f ( n 1) b
f(n) = af(n - 1) + b
x0 = ax0 + b
Prin scaderea celor doua relatii, rezulta un algoritm exponential cu baza a: f(n) - x0 = a (f(n-1) - x0)
2. f ( n ) a f ( n 1) b f ( n 2 )
f(n) = tn tn = atn-1 + btn-2
Facând n = 2, t2 = at + b , cu urmatoarele cazuri:
a) t1 , t2 R solutia ecuatiei este de forma: f ( n ) t1n t2n iar si se calculeaza din conditiile
initiale:
f (1) x t t x
cu x1 si x2 constante.
1 1 2 1
f ( 2) x t t x
2 2
2 1 2 2
_________________________________________________________________________
29
Structuri de date si algortitmi
_________________________________________________________________________
_________________________________________________________________________
30
Structuri de date si algortitmi
_________________________________________________________________________
Pentru a rezolva problema de dimensiune n, se rezolva pentru a probleme de dimensiune n/b, iar
combinarea rezultatelor celor a prbleme, duce la g(n) operatii.
Se demonstreaza, analog, si relatia de recurenta:
T ( n) a T ( n / b) C n k
Solutia acestei ecuatii de recurenta este:
O ( n lo g b a
), a b k
T ( n) O(n k
log n ), a b k
O(n k
), a b k
l h
_________________________________________________________________________
31
Structuri de date si algortitmi
_________________________________________________________________________
}
Algoritmul considera pivotul ca fiind: A[low]. Indicele l parcurge vectorul de la stânga la dreapta,
iar indicele h parcurge vectorul de la dreapta la stânga. Ei se apropie pâna se întâlnesc (l = h). Deci, l lasa
în urma numai elemente A[i] pivot, iar h lasa în urma numai elemente A[i] pivot.
Ciclul I while înseamna ca înainteaza l cât timp A[l] pivot. Acest ciclu se opreste pe conditia
A[h] pivot, fixându-se aici.
Ciclul II while însemna ca înainteaza h cât timp A[h] pivot. Acest ciclu se opreste pe conditia
A[h] pivot, fixându-se aici.
Cele doua pozitii se schimba, astfel încât sa se permita înaintarea indicilor mai departe.
low h
Pentru aflarea complexitatii, cercetam cazul cel mai defavorabil. Fie cazul în care vectorul este
ordonat descrescator. Pivotul gasit, la primul pas, este elementul maxim din vector, rezulta ca trebuie plasat
în ultima pozitie. Pivotul va fi maximul dintre elementele secventei, deci, va fi plasat în ultima pozitie din
secventa.
Problema se împarte în 2 subprobleme: P(n) P(n-1) , P(0).
Numarul de comparatii pentru functia Partition este (n-1). Vectorul se parcurge în doua directii,
dar o singura data.
Rezulta ca timpul de functionare al algoritmului Quick_Sort este: T ( n ) ( n 1) T ( n 1)
Rezolvând aceasta ecuatie, avem:
T ( n) n 1 T ( n 1) n 1 n 2 T ( n 2) ... n 1 n 2 n 3... 1 T (1)
unde: T(1) este 0 (nu se partitioneaza). Rezulta:
n 1
T ( n) i n(n 1) / 2
i 1
Pentru complexitatea medie trebuie considerata probabilitatea tuturor aparitiilor datelor de intrare.
Consideram ca orice configuratie de date la intrare este egal probabila. Probabilitatea ca pivotul sa fie
plasat în pozitia k este egala pentru k low, high . Asadar, pivotul va fi plasat în pozitia k prin partitionare,
cu o probabilitate egala cu 1/n, pentru k low, high ( k 1, n ) .
Suma tuturor probabilitatilor este 1. Evenimentul este plasarea pivotului în pozitia k. Consideram
Ti(n) timpul de executie al algoritmului Quick_Sort atunci când pivotul este plasat în pozitia i:
i
Rezulta: Ti ( n ) n 1 T (i 1) T ( n i )
_________________________________________________________________________
32
Structuri de date si algortitmi
_________________________________________________________________________
n
1 1 n
Timpul mediu va fi o medie aritmetica: T (n) Ti (n) Ti (n)
i 1 n n i 1
Dezvoltând,
1 n 1 1 n 1 n
T (n) ((n 1) T (i 1) T (n i )) n(n 1) T (i 1) T (n i )
n i 1 n n i 1 n i 1
n 1
1 n
1 n
2
n 1 T (i 1) T ( j 1) n 1 T (i )
n i 1 n j 1 n i 0
(Facând schimbarea de variabila j = n - i + 1)
Rezulta relatia de recurenta cu istorie completa:
2 n 1
T (n) n 1 T (i)
n i 0
n 1
Aceasta se rezolva astfel: înmultind relatia cu n rezulta: n T ( n) n( n 1) 2 T (i )
i 0
n
Scriem acum relatia înlocuind pe n cu n+1: (n 1) T (n 1) (n 1) n 2 T (i )
i 0
Si scazându-le acum membru cu membru rezulta:
(n 1) T (n 1) nT (n) n(n 1) n(n 1) 2T (n)
(n 1) T (n 1) 2n (n 2) T (n)
1 T (n 1) 2n T ( n)
care se înmulteste cu: ,
( n 1)(n 2) n2 (n 1)(n 2) n 1
T ( n) 2n 2(n 1)
Notam: F ( n) , F (n 1) F (n), F (n) + F (n - 1)
n 1 (n 1)(n 2) n(n 1)
2
Facând o majorare: F ( n)
n
F ( n 1)
2 22 22 2 22 2 2
F(n) F(n 1) F(n )2 F(n 3) . . F i)(
n n n1 n n1 n2 n n1 n 2 2
_________________________________________________________________________
33
Structuri de date si algortitmi
_________________________________________________________________________
Curs 6
Algoritmi recursivi
Algoritmii recursivi consuma memorie suplimentara pentru simularea recursivitatii.
Fie urmatoarea procedura recursiva:
Asadar, variabilele globale (statice) sunt memorate într-o zona de memorie fixa, mai exact în
segmentele de date. Variabilele automate (locale) se memoreaza în stiva, iar variabilele dinamice în "heap"-
uri (cu malloc în C, si cu new în C++).
_________________________________________________________________________
34
Structuri de date si algortitmi
_________________________________________________________________________
if(low < high)
|¯ k = Partition(A, low, high)
| Quick_Sort(A, low, k-1)
|_Quick_Sort(A, k+1, high)
}
low k high
Avem în acest algoritm doua apeluri recursive.
Cazul cel mai defavorabil:
k
low
high
Consideram consumul de memorie în stiva : M(n) = c + M (n - 1) M(n) = O(n) un ordin de
complexitate mare.
Pentru reducerea consumului de memorie, se concepe un alt algoritm la Quick_Sort, astfel încât un
apel sa fie rezolvat recursiv, iar celalalt apel iterativ.
k
secventa mica rezolvata recursiv secventa mare rezolvata iterativ
Quick_Sort(A, low, high)
{
while (low < high)
|¯ k = Partition(A, low, high)
| if( k-low > high-k)
| |¯ Quick_Sort(A, k+1, high)
| |_high = k-1
| else
| |¯ Quick_Sort(A, low, k-1)
|_ |_low = k-1
}
Necesarul de memorie pentru aceasta este M(n) c + M(n/2), însemnând ca oricare ar fi secventa
mai mica, ea este decât jumatatea M(n) = O(log n) am redus ordinul de complexitate.
Liste generalizate
Definitie:
_________________________________________________________________________
35
Structuri de date si algortitmi
_________________________________________________________________________
Data o multime de elemente (atomi), se numeste lista generalizata o secventa finita (1, 2, ... , n), în care
i sunt atomi.
Exemplu: A = (a, b, c)
B = (x, A, (a, c), ( ))
| | | \
atom lista lista lista vida
Observatie: Listele generalizate pot sa aiba elemente comune. Se permite definirea de liste recursive.
0 b 0 c 0
B: 1 1 0 1 0
C:
0 a 1 0
Ne propunem sa dam nume unei subliste, deci daca tag = 1, adica tag(p) = 1 data(p) va fi
adresa unei structuri ce contine:
_________________________________________________________________________
36
Structuri de date si algortitmi
_________________________________________________________________________
nume lista
D: D 0
A:
A
0 a 1 0
0 b 0 c 0
B: B
1 1 1 0
C: C
0 a 1 0
_________________________________________________________________________
37
Structuri de date si algortitmi
_________________________________________________________________________
· functia del ( ) trebuie sa tina seama de existenta unor liste comune. Deci, este necesara pastrarea în
elementul ce contine numele listei A si a unui indicator care sa contorizeze numarul de referinte ale lui.
Exemplu:
A 2
Exemplu: O functie de copiere si o functie de test de egalitate a doua liste generalizate, realizate recursiv si
iterativ. Functia returneaza o copie a listei. Copie mai întâi primul element, si apoi recursiv restul listei:
Varianta recursiva:
_________________________________________________________________________
38
Structuri de date si algortitmi
_________________________________________________________________________
{
p1 = l1; p2 = l2
while(p1 0 and p2 0)
|¯ if (tag(p1) tag(p2)) then return (FALSE)
| if (tag(p1) = 0 and data(p 1) data(p 2)) then |
return (FALSE)
| if (tag(p1) = 1 and not isEqual |
(data(p 1),data(p 2))then return FALSE) | p1 = link(p1)
|_ p2 = link(p2)
return (p1 == p2)
}
_________________________________________________________________________
39
Structuri de date si algortitmi
_________________________________________________________________________
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 (nici un arc nu intra in radacina);
2) x V\{r} , y V unic , astfel încât (y, x) E (Cu alte cuvinte, pentru toate nodurile minus
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
_________________________________________________________________________
40
Structuri de date si algortitmi
_________________________________________________________________________
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 :
_________________________________________________________________________
41
Structuri de date si algortitmi
_________________________________________________________________________
A( B (E (K, L), F), C (G), D (H (M), I, J)) cu reprezentarea:
1 1 1 0
B C D
1 0 F 0 0 G 0 1 0 I 0 J 0
E H
0 K 0 L 0 M 0
_________________________________________________________________________
42
Structuri de date si algortitmi
_________________________________________________________________________
data link1 link2 ...... link k k=degree(T)
B D
C
0
0 0
E F G H I J
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
K L M
0 0 0 0 0 0 0 0 0
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
_________________________________________________________________________
43
Structuri de date si algortitmi
_________________________________________________________________________
A 0
B C D
E F G H I J
K L M
Având adresa unui nod, se gasesc toti predecesorii, obtinându-se o lista înlantuita:
(Reprezentarea TATA):
M H D A 0
Arbori binari
Un arbore binar este un arbore de grad maxim 2. În 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
Definitii
_________________________________________________________________________
44
Structuri de date si algortitmi
_________________________________________________________________________
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
/ \
b c
/ \ / \
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 mn o
Lema 1
Numarul maxim de noduri de pe nivelul i al unui arbore binar este egal cu 2i-1.
Lema 2
Numarul maxim de noduri ale arborelui binar de adâncime h este egal cu 2h -1.
Demonstratie:
h h
2h 1
Numarul total de noduri este egal cu: niv (i ) 2i 1 20 21 ... 2h 1
2 1
2h 1
i 1 i 1
(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
_________________________________________________________________________
45
Structuri de date si algortitmi
_________________________________________________________________________
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;
În 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.
rad
/ \
SAS SAD
Exemplu de traversare: a
/ \
b c
/ \ \
d e f
/ \
g h
· preordine : A B D E G H C F
· inordine : D B G E H A C F
· postordine : D G H E B F C A
_________________________________________________________________________
46
Structuri de date si algortitmi
_________________________________________________________________________
În C++ avem:
struct Nod{
Atom data;
Nod* stg;
Nod* dr;
};
Nod* p;
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
_________________________________________________________________________
47
Structuri de date si algortitmi
_________________________________________________________________________
nr. pointeri nuli n ( k 1) 1 n 1
1
nr. total pointeri nk nk
raportul este maxim pentru k = 2.
Rezulta ca cea mai eficienta reprezentare (în structura înlantuita) este reprezentarea în arbori binari.
_________________________________________________________________________
48
Structuri de date si algortitmi
_________________________________________________________________________
Curs 8
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)
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.
_________________________________________________________________________
49
Structuri de date si algortitmi
_________________________________________________________________________
Exemple: 15 15
/ \ / \
7 25 10 25
/ \ / \ /\ / \
2 13 17 40 2 1 7 13
/ / \
9 27 99
este BST nu este BST.
15
/ \
7 25
/ \ / \
2 13 17 40
/ \ / \
9 19 27 99
make_nod(a)
{
p= get_sp() // alocare de memorie pentru un nod nou
data(p)= a
lchild(p)= 0
_________________________________________________________________________
50
Structuri de date si algortitmi
_________________________________________________________________________
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:
15
/ \
7 25
/ \ / \
2 13 17 40
/ /
9 27
/ \
26 33
_________________________________________________________________________
51
Structuri de date si algortitmi
_________________________________________________________________________
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.
Observatie:
Functia remove_greatest modifica arborele indicat de parametru, în sensul eliminarii nodului cel mai mare,
si întoarce pointer la nodul eliminat.
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
/ \
_________________________________________________________________________
52
Structuri de date si algortitmi
_________________________________________________________________________
a1 ai
(a i<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
n n
T ( n) ( n 1) (1 / n) T (i 1) (1 / n) T (n i)
i 1 i 1
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:
Ti ( n ) ( n 1) T ( i 1) T ( n i )
n
T ( n) (1 / n) Ti (n)
i 1
Deci:
n
T (n) (1 / n) ((n 1) T (i 1) T (n i ))
i 1
n n
(n 1) (1 / n) T (i 1) (1 / n) T (n i )
i 1 i 1
n
(n 1) (2 / n) T (i 1)
i 1
Deci:
n 1
T ( n) n 1 ( 2 / n) T (i )
i 0
Complexitatea acestei functii este: O(nln n) (vezi curs 5 complexitatea medie a algoritmului
Quick-Sort)
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
_________________________________________________________________________
53
Structuri de date si algortitmi
_________________________________________________________________________
/ \ / \ / \ \
d e f g d e f
/ \
g h
arbore binar arbore binar
complet echilibrat echilibrat
Transformarea structurii arborelui dupa inserare pentru a conserva proprietatea de arbore binar
echilibrat
Modificarile care se vor face se vor numi rotatii.
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
/ \ / \
T 1 T2 T 2 T1
Caz 2: Inserarea se face prin rotatii duble:
A A
/ \ / \
B T3 T3 B
/ \ / \
T 1 T2 T 2 T1
rotatie dubla rotatie dubla
la dreapta la stânga
Fie primul caz:
A
/ \
B T3
/ \
T 1 T2 este BST: T1 < B < T2 < A < T3
Toti arborii care respecta în continuare aceasta conditie vor fi BST.
_________________________________________________________________________
54
Structuri de date si algortitmi
_________________________________________________________________________
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(T 3) = h
depth(T2S) = depth(T 2D) = h - 1
În urma inserarii, unul dintre arborii T2S si T2D îsi mareste adâncimea. Aplicam aceiasi tehnica:
T1 < B < T2S < C < T2D < A < T3
Începem 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
_________________________________________________________________________
55
Structuri de date si algortitmi
_________________________________________________________________________
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.
_________________________________________________________________________
56
Structuri de date si algortitmi
_________________________________________________________________________
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
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.
1. Nodul critic va fi perfect balansat (bf = 0), daca dezechilibrul creat de nodul inserat anuleaza
dezechilibrul initial al nodului. În 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). În 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.
_________________________________________________________________________
57
Structuri de date si algortitmi
_________________________________________________________________________
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.
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
În 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
/ / \ / \
_________________________________________________________________________
58
Structuri de date si algortitmi
_________________________________________________________________________
3 9 15 30 90
a
/ \
b c
/
\ / \
d e f g
/ \ / \ / \ / \
h i j k l m n o
a b c d e f g h i j k l m n o
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
indici
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]
_________________________________________________________________________
59
Structuri de date si algortitmi
_________________________________________________________________________
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.
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
_________________________________________________________________________
60
Structuri de date si algortitmi
_________________________________________________________________________
Curs 10
Coada cu prioritati
Operatia de inserare
/ \
heap: / \
/ 50 \
/ / \ \
/ 40 30 \
/ / \ / \ \
/ 33 37 12 2 \
/ / \ / _________\
/ 10 15 7 | 42
-----------------|
50 40 30 33 37 12 2 10 15 7 42
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.
insert:
_________________________________________________________________________
61
Structuri de date si algortitmi
_________________________________________________________________________
1) In reprezentarea implicita: V [N + 1] = a
N=N+1
În 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 schimba 42 cu 37)
3) Înaintare î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:
_________________________________________________________________________
62
Structuri de date si algortitmi
_________________________________________________________________________
/ \ / \ \
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)
}
_________________________________________________________________________
63
Structuri de date si algortitmi
_________________________________________________________________________
nr. de noduri nr. de noduri
ale arborelui ale arborelui
complet de complet de
adâncime k adâncime k+1
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)
}
max min
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 opera_ii 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:
_________________________________________________________________________
64
Structuri de date si algortitmi
_________________________________________________________________________
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:
k
T ( n) (i 1) 2i 1
i2
k 1
Sa aratam: T (n) i 21
i 1
cu tehnica T ( n ) 2 T ( n ) T ( n) . Asadar:
k 1 k 1 k 1 k 1
T (n) 2 i 2i i 2i i 2i 1 i 2i
i 1 i 1 i 1 i 1
k 1
1 2 2 2 3 2 ... ( k 2) 2
2 3 4
(k 1) 2 k 1 21 2 2 2 3 23 ... (k 1) 2 k 1
k 1 k 1
2 (k 1) 2 k 2i (k 1) 2 k 2 2 0 21 2i
i 2 i 0
(k 1) 2 1 (2 1) (k 2) 2 2
k k k
Rezulta: T (n) (k 2) 2 k 2 ( k 2) ( 2 k 1) k 2 2 n (k 2) k
iar: k log 2 ( n 1) ,
din n 2k 1
Rezulta: T (n) n (log 2 (n 1) 2) log 2 (n 1)
------------------------
termen dominant
_________________________________________________________________________
65
Structuri de date si algortitmi
_________________________________________________________________________
II
noduri terminale
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
}
heap_gen
{
for i = [N/2] downto 1 step -1 do
retrogradare(V, n, i)
}
_________________________________________________________________________
66
Structuri de date si algortitmi
_________________________________________________________________________
nivel k : nu se fac operatii
nivel k-1 : 2k-2 noduri o operatie de comparatie
nivel k-2 :2 k-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
k 1
Se aduna, si rezulta: T ( n) ( k i ) 2i 1
i 1
Tehnica de calcul este aceeasi: T ( n ) 2 T ( n ) T ( n)
k 2 k 2
T (n) (k 1) 2i (k i ) 2i 1 (k 1) 21 (k 2) 2 2 (k 3) 23 ... 3 2 k 3 2 2 k 2
i 1 i 1
k 3 k 3
(k 1) 20 (k 2) 21 (k 3) 2 2 ... 2 2 k 3 2 2 k 2 (k 1) 21 2 k 1 (k 1) 20 21
i 1 i 0
k 1 k 2 k 2 k 2
2 2 2 (k 1) 2 (2 1) k 1 3 2 k 1
k 2
Rezulta: T (n) 3 2 k 1 3 (2 1) k 1
k
T (n) 3 n log2 (n 1) 1
-------
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
_________________________________________________________________________
67
Structuri de date si algortitmi
_________________________________________________________________________
Curs 11
Definitii:
· i se numeste predecesor al lui j;
· j se numeste succesor al lui i;
· (i, j) este adiacent cu i si j;
· i, j adiacente;
· Ei = { k, (k, i) E} multimea de vecini la intrare;
· E'i = { j, (i, j) E} multimea de vecini la iesire;
· Ei E'i multimea vecinilor.
grad_intrare(i) = in_degree(i) = │Ei│
grad_ie_ire(i) = out_degree(i) = │ E'i│
Pentru un graf neorientat (G - neorientat), Ei E'i si degree(i)= │Ei│.
Definitie
Se numeste drum orientat într-un graf de la x la y secventa de noduri D = (i1 = x, i2, ... , in = y),
(ik, ik+1) E, k 1, n .
Drumul D este neorientat, daca (ik, ik+1) E sau (ik+1, ik) E
Definitie
Se numeste graf conex graful pentru care doua noduri (x, y) V, D un drum de la x la y.
Un graf este complet daca fiecare nod este conectat cu oricare din celelalte noduri: E = VV \ {(i, i), i V}
Definitie
Fie G = (V, E) un graf. Se numeste subgraf al grafului G, un graf G' = (V', E'), astfel încât V' V,
E' (V'V') E
Reprezentari
_________________________________________________________________________
68
Structuri de date si algortitmi
_________________________________________________________________________
1) Matrice de adiacenta
Fie G = (V, E) ,│V│ = n. Se determina matricea de adiacenta
1 (i , j ) E
astfel: Mn n , M (i , j )
0 (i , j ) E
0 1 1 0 0
0 0 0 0
1
0 0 0 0 0
0 1 0 0 1
1 0 0 0 0
Un graf este etichetat daca o functie definita pe E R, adica fiecarui arc i se asociaza o valoare.
Astfel de grafuri se reprezinta prin matrici de colectivitate:
0 (i , j ), (i , j ) E
C (i , j )
, (i , j ) E
Complexitate:
O(1), (i, j) E
O(n), Ei, E'i, indegree(i), outdegree(i)
O(n2), spatiu
Definitie
Fie G = (V, E) un graf, │V│ = n , V = {1, 2, ... , n}. Se numeste lista de adiacenta asociata acestui graf o
colectie de n pointeri (1, 2, ... , n), fiecare pointer continând adresa unei liste dupa regula urmatoare: L(i)
da adresa listei înlantuite care contine toti succesorii lui i.
L(i) Ei
Exemplu:
1 2 3 0
3 0
2
3 0
2 5 0
4
5 1 0
_________________________________________________________________________
69
Structuri de date si algortitmi
_________________________________________________________________________
Ei: O(│E│) sau O(maxout_degree)
E'i: O(│E│)
spatiu: O(│E│ + n)
bfs(G, i)
{ // mark(1... n) initializat cu (0 ... 0) ext
// o coada q isempty, a= pop(p), put(q) ext
procesare(i)
mark(i) = 1
put(q, i)
while(isEmpty(q) = N0)) do
|¯ k = pop(q)
| for fiecare j V , (k,j) E
| |¯ if(mark(j)=0)
| | |¯ procesare(j)
| | | mark(j) = 1
|_ |_ |_ put(q,j)
}
1) Matrice adiacenta
dfs(M, i)
{
procesare(i)
mark(i) = 1
for k = 1 to n
|¯ if (M(i,k) = 1)
| |¯ if mark(k) = 0
|_ |_ dfs(k)
}
_________________________________________________________________________
70
Structuri de date si algortitmi
_________________________________________________________________________
dfs(L, i)
{
procesare(i)
mark(i) = 1
p = P(i)
while p 0
|¯ if mark(data(p)) 0
| dfs(data(p))
|_ p = link(p)
}
_________________________________________________________________________
71