You are on page 1of 68

Structuri de date si algortitmi

_________________________________________________________________________
Facultatea de automatica si
calculatoare
_________________________________________________________________________ 1
Structuri de date si algortitmi
_________________________________________________________________________
STRUCTURI DE DATE SI ALGORITMI
Curs de Brsan M.
BIBLIOGRAFIE
1. E. Horowitz, S. Sahni "Fundamentals of Computer Algorithms" - 1985
2. E. Horowitz, S. Sahni "Fundamentals of Data Structurs"
3. Livovschi Georgescu "Sinteza si analiza algoritmilor"
4. U. Mandber "Introduction to Algorithms"
5. S. Baase "Computer Algorithms"
6. M. Shapiro "Algorithms from P to NP"
7. N. Wirth "Data Structurs + Algorithms = Programs"
_________________________________________________________________________ 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:
Masina Von Newman - presupune executia pe baza modelului de calcul cu:
U C
M
I O
. .
.
/
Programarea este n acest caz programare imperativa procedurala.
Masina RAM (Random Acces Memory) cu:
P U C
M
S
( . .)
.
.(sir de instructiuni )
I / O
model bazat pe algebra booleana;
programarea este imperativa procedurala;
evolutia se face prin set redus de instruciuni;
viteza foarte mare de executie.
Masina TURING
_________________________________________________________________________ 3
Structuri de date si algortitmi
_________________________________________________________________________
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.
Analiza performantelor algoritmului
Analiza performantelor (estimarea algoritmului) se impune nca nainte de scrierea programelor.
Etapele de realizare a unui produs software (software engineering)
Aceasta stiinta pune n evidenta metodologii clare pentru modele.
Modelul initial:waterfall (cascada):
Requirmens
Design
Testing
Implement
Etapele de realizare ale unui produs software:
O prima faza:
se pleaca de la cerinte;
se obtin specificatii;
se face analiza specificatiilor;
A doua faza (DESIGN):
proiectare de ansamblu (se sparge modulul n submodule, etc);
proiectarea structurilor de date;
proiectarea algoritmilor;
analiza performantelor;
codarea (scrierea programului);
A treia faza:
testarea;
Ultima faza:
implementarea.
Programul rezultat se compara cu cerintele, si daca nu corespunde, se reia ciclul ori de cte ori este
nevoie.
Analiza performantelor presupune renuntnd 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.
_________________________________________________________________________ 4
Structuri de date si algortitmi
_________________________________________________________________________
Presupunnd 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.
Timpul de executie al algoritmului depinde de:
dimensiunea datelor de intrare
spatiul de memorie suplimentar ocupat
Dimensiunea datelor de intrare este o functie f(n) care calculeaza, pentru un n dat, numarul de
instructiuni al algoritmului respectiv.
n c n f
2
log ) (
Estimarea se face pna la o constanta c.
Spatiul de memorare suplimentar
Definitie: Date doua functii f, g : N N cu f = O(g) sau f(n) = O(g(n)),
f este ordinul de complexitate a lui g daca N N si const. c > 0
astfel incat > < n N f n c g n
0
( ) ( ) .
_________________________________________________________________________ 5
Structuri de date si algortitmi
_________________________________________________________________________
Curs 2
Structuri de date elementare
O structura de date presupune un mod de organizare a datelor (n tablouri, structuri, etc), si definirea
operatiilor acceptate. Deci, o structura de date reprezinta o colectie organizata de date si un set de operatii
definite.
Si notiunea de tip de date presupune: |--- reprezentare
|--- operatii acceptate
De exemplu, pentru tipul int: |--- reprezentare: pe doi octeti: cod
| complementar
|---operatii acceptate: +, -, *, /, &, |, etc.
Daca pentru un tip de date nu intereseaza reprezentarea, ci doar operatiile acceptate,nseamna ca tipul
de date este abstract.
Structuri de date
Tablouri
Tabloul este o colectie de date n care fiecare element poate fi identificat pe baza unui index, colectia
asigurnd 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, putnd 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:
a a a a a ... a
1 2 3 4 5 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 vrful stivei:
Asadar:
push insereaza un element n vrful stivei;
pop sterge un element din vrful stivei;
top consulta (citeste) elementul din vrful stivei;
top(S) citeste vrful stivei.
push pop
Cozi
O coada este o lista n care inserarea se face la un capat (la sfrsit), 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:
//////
a a a ... a
1 2 3
n
se face cu deplasare de o pozitie la stnga 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):
//////
a a a ... a
1 2
i+1
a
i-1
...
Presupunnd acum hasurat corpul de elemente din lista si nehasurata zona de memorie libera,
inserarea s-ar putea figura astfel:
_________________________________________________________________________ 7
Structuri de date si algortitmi
_________________________________________________________________________
/////////////////////////////
///////////////////////////////////
///////////////// //////////
Stergerea: deplasarea cu o pozitie la stnga din acel punct.
/////////////////////////////
////////// //////////////
//////////
Liste nlantuite
ntr-o lista nlantuita, ordinea din memorie nu mai corespunde cu ordinea din lista. Fiecare element al
listei nlantuite va avea urmatoarea structura:
(a(i) , succesor(a(i)))
unde a(i) este atomul listei nlantuite, iar informatia succesor(a(i)) ne permite sa identificam un nou element de
lista nlantuita. Ordinea din memorie a elementelor listei nu conteaza. Informatia care indica primul element al
listei se numeste "capul" listei. Informatiei succesor(a(i)) i se potriveste notiunea de pointer (identificator),
pointer-ul fiind o variabila care pastreaza o adresa din memorie. El indica pozitia elementului urmator. Cnd
informatiile de nlantuire sunt pointeri, putem utiliza urmatoarea reprezentare:
cap de lista
a a a
1 2 3
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.
n programe vom utiliza urmatoarele notatii:
x adresa unui element din lista, deci un pointer;
data(x) atomul memorat n elementul de lista indicat de x;
link(x) informatia de legatura memorata n elementul de lista indicat de x, adica adresa elementului
urmator;
y = get_sp() y (de acelasi tip cu x) primeste adresa unei zone de memorie n care se poate memora
un element din lista (get space sau alocare de memorie cnd este vorba de pointer);
ret_sp(x) elibereza memoria ocupata de elementul de lista indicat de x (din momentul respectiv
acolo se poate memora altceva).
Un element de lista va fi o astfel de structura:
struct Element {
Atom data;
Element* link;
};
Se va scrie:
tipul lui x ---------------- Element* x
data(x) ---------------- x data
_________________________________________________________________________ 8
Structuri de date si algortitmi
_________________________________________________________________________
link(x) ---------------- x link
y = get_sp() ---------------- y = new Element
ret_sp() ---------------- delete x
Deoarece lucram cu conceptul de lista vom face declaratia :
typedef Element* Lista;
Un pointer la un element de lista considerat aproximeaza lista ce porneste cu elementul indicat.
Operatii primitive pentru liste nlantuite
1. Inserarea
Inserarea se face: n fata, sau
n interior (la mijloc ori la sfrsit)
a) Inserarea n fata
a
a a
1 2
0
x
1
2
1
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 ramn valabile:
a
0
0
x
1
0
1
x
0
a
0
b) Inserarea la mijloc
_________________________________________________________________________ 9
Structuri de date si algortitmi
_________________________________________________________________________
a a a
i-1 i i+1
y
a
x
1
2
Analog pentru inserarea la sfrsit.
1 - Prima atribuire: link(x) = link(y)
2 - A doua atribuire: link(y) = x
2.a) Stergerea (stergerea din fata):
a a
1 2
2
1
1 - Prima atribuire: p = l
2 - A doua atribuire: l = link(l)
3 - ret_sp(p)
Sau, stergerea din fata s-ar mai putea reprezenta astfel:
Situatia initiala:
cap
0
Situatia finala:
cap
0
2
1
P
(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 sfrsit
Varibila q va indica elementul din fata celui care va fi sters.
Situatia initiala:
q
Situatia finala:
q
(1) p = q link;
(2) q link = p link; // sau q link = q link link;
delete p;
Observatii:
Atunci cnd 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
Operatia de inserare ntr-o lista nlantuita
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 ctelea 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.
Inserarea n fata unui element specificat
Functia nscrie un element n fata altui element dintr-o lista:
insert (l, a, b)
// l lista (pointer la primul element)
// a valoarea atomului de inserat
// b valoarea atomului n fata caruia se insereaza
{
p=get_sp();
data(p)=a;
if (l==0) or (data(l)==b) then
{
link(p)=l;
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;
}
}
Operatia de stergere dintr-o lista nlantuita
Operatia delete sterge un atom dintr-o lista. Deci vom avea n pseudocod, o functie de forma:
_________________________________________________________________________ 12
Structuri de date si algortitmi
_________________________________________________________________________
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 sfrsitul
| listei si atomul nu a fost gasit")
| else | p=link(q)
| | link(q)=link(p)
|_ |_ ret_sp(p)
}
Operatia de parcurgere a listei nlantuite
Operatia de parcurgere a listei nlantuite consta dintr-o secventa de instructiuni care se foloseste de
fiecare data cnd 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 rnd fiecare element al listei, si consideram ca p ncepe cu l:
while (p!=0) do | prelucrare (data(p)) // ex:afisarea
| //atomului
|_ p=link(p) // trece la urmatorul
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 nct 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
Operatia PUSH primeste ca parametri o stiva si un obiect si produce stiva modificata prin adaugarea
obiectului n stiva.
POP(STACK) STACK, ATOM
_________________________________________________________________________ 13
Structuri de date si algortitmi
_________________________________________________________________________
Operatia POP primeste ca parametri o stiva pe care o modifica scotnd un obiect. De asemenea
produce ca rezultat obiectul scos din stiva.
TOP(STACK) ATOM
Operatia TOP ntoarce ca rezultat obiectul din vrful 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 vrfului 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]
};
Conditia de stiva vida este: S.sp=0
Se scrie:
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
}
Functia pop scoate un element din stiva:
pop(S)
{
if S.sp=0 then eroare ("Stiva vida")
else S.sp=S.sp-1
}
Observatie: Se obisnuieste ca pe lnga 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])
}
Functia isEmpty(S) testeaza conditia stiva vida:
isEmpty(S)
{
return(S.sp==0)
_________________________________________________________________________ 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)
}
Stivele sunt cele mai simple structuri de date, ele avnd si operatiile imediate.
_________________________________________________________________________ 15
Structuri de date si algortitmi
_________________________________________________________________________
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
ultimul
primul
GET PUT
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
O coada poate fi organizata pe un spatiu de memorare de tip tablou (vector).
Sunt necesari doi indicatori:
head indica: primul element care urmeaza sa fie scos.
tail indica: locul unde va fi pus urmatorul element adaugat la coada.
Conditia "coada vida" este echivalenta cu: head = tail. Initial indicatorii vor fi initializati astfel nct 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 stnga continutul cozii
cu o pozitie. Primul element care urmeaza sa fie scos va fi ntotdeauna n prima pozitie, indicatorul head
pierzndu-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.
Conditia coada vida este C.head=C.tail.
Functia put pune n coada C un atom a:
put(C,a)
{
_________________________________________________________________________ 16
Structuri de date si algortitmi
_________________________________________________________________________
if C.tail>DIMMAX then eroare("Coada plina")
else | C.vect [C.tail]=a
|_ C.tail=C.tail+1
}
Functia get scoate un element din coada si-l returneaza:
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)
}
Cozi ordonate circulare
Pentru a obtine o coada circulara vom porni de la o coada liniara simpla (cu doi indicatori) si vom face
n asa fel nct la incrementarea indicatorilor head si tail, cnd acestia ating ultima pozitie din vector sa se
continue cu prima pozitie din vector.
Functia urmatoare poate realiza aceasta cerinta:
int nextPoz(int index)
{
if (index<DIMVECTOR-1) return index+1;
else return 0;
}
unde DIMVECTOR este dimensiunea vectorului n care se memoreaza elementele cozii.
Continutul cozii va arata asa:
head tail
1 2 3 4 5 6 7 8
sau asa:
_________________________________________________________________________ 17
Structuri de date si algortitmi
_________________________________________________________________________
head tail
1 2 3 4 5 6 7 8
Conditia "coada vida" ramne echivalenta cu: head = tail
Coada va fi plina daca: head=1 si tail=DIMVECTOR
head
tail
sau daca: tail+1 = head
head tail
Ambele situatii sunt continute n conditia:
nextPoz(tail) = head // conditia "coada plina"
Coada circulara ordonata asigura reutilizarea spatiului eliberat de get la urmatoarele inserari n coada.
Observatie:
n coada circulara de dimensiune DIMMAX pot fi memorate DIMMAX elemente.
"Coada plina" se realizeaza n 2 situatii:
a) C.head=1 si C.tail=DIMMAX
b) C.tail+1=C.head
Iar, conditia C.head=inc(C.tail) le contine pe amndoua.
n cazul cozilor circulare se modifica doar operatiile put si get:
put(C,a)
{
if C.head=inc(C.tail) then eroare("Coada plina")
_________________________________________________________________________ 18
Structuri de date si algortitmi
_________________________________________________________________________
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.
head
tail
nil
Este nevoie de doi indicatori (pointeri):
head indica primul element din coada (primul care va fi scos);
tail indica ultimul element din coada (ultimul introdus).
O coada vida va avea: head=tail=nil
n mod obisnuit, adaugarea unui element n coada modifica numai tail iar stergerea unui element numai head.
ntr-un mod special trebuie sa fie tratate cazurile:
adaugare ntr-o coada vida:
Initial: head=tail=nil
Final: Coada cu un element:
head tail
nil
stergere dintr-o coada cu un element:
Initial: Coada cu un element
Final: head=tail=nil
n aceste cazuri se modifica att head ct si tail.
Facem notatiile :
C.head pointer la primul element din coada;
C.tail pointer la ultimul element din coada;
C coada.
Conditia de coada vida este head=0.
_________________________________________________________________________ 20
Structuri de date si algortitmi
_________________________________________________________________________
Operatiile cozii nlantuite
Functia put insereaza un element n coada, n pozitia fata:
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
}
Functia get scoate un element din pozitia fata:
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)
}
Exista aici un element de redundanta: ar fi convenabil sa nu mai avem spatiu suplimentar de
memorare, ci, sa avem un singur pointer ca sa putem manevra coada. De aceea apar utile cozile nlantuite
circulare.
_________________________________________________________________________ 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:
tail
.....
primul ultimul
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)
}
front(C) returneaza data(link(C))
isEmpty(C) retuneaza conditia C=0.
Complexitatea algoritmilor
La evaluarea (estimarea) algoritmilor se pune n evidenta necesarul de timp si de spatiu de memorare
al lui.
_________________________________________________________________________ 22
Structuri de date si algortitmi
_________________________________________________________________________
Studierea complexitatii presupune analiza completa n cadrul algoritmului a urmatoarelor 3 puncte de vedere:
1. configuratia de date cea mai defavorabila (cazurile degenerate);
2. configuratia de date cea mai favorabila;
3. comportarea medie.
Punctul 3 presupune probabilitatea de aparitie a diferitelor configuratii de date la intrare.
Punctul 1 este cel mai studiat si este folosit, de obicei, pentru compararea algoritmului. Si n ceea ce
priveste timpul, se studiaza configuratia cea mai defavorabila a algoritmului.
Complexitatea unui algoritm se noteaza cu: O(f(n)).
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 n
0
N
astfel nct pentru n > n
0
f(n) < c g(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) = a
k
n
k
+ a
k-1
n
k-1
+ ... + a
1
n + a
0
, atunci f = O(n
k
).
Facndu-se majorari n membrul drept, obtinem rezultatul de mai sus:
f(n) = |a
k
| n
k
+ |a
k-1
| n
k-1
+ ... +|a
1
| n +|a
0
|< n
k
(|a
k
|+ |a
k-1
|+ |a
0
|) < n
k
c pentru n > 1 f(n) <
c n
k
, cu n
0
= 1.
Concluzie: f = O(n
k
), si ordinul O exprima viteza de variatie a functiei, functie de argument.
Exemplu: Calcularea maximului unui sir
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).
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.
_________________________________________________________________________ 23
Structuri de date si algortitmi
_________________________________________________________________________
Exemplu: Insertion Sort (algoritmul de sortare prin inserare)
Algoritmul INSERTION SORT considera ca n pasul k, elementele A[1k-1] sunt sortate, iar
elementul k va fi inserat, astfel nct, dupa aceasta inserare, primele elemente A[ k] sa fie sortate.
Pentru a realiza inserarea elementului k n secventa A[1k-1], aceasta presupune:
memorarea elementului intr-o varibila temporara;
deplasarea tuturor elementelor din vectorul A[1k-1] care sunt mai mari dect A[k], cu o pozitie la stnga
(aceasta presupune o parcurgere de la dreapta la stnga);
plasarea lui A[k] n locul ultimului element deplasat.
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 pna 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(n
2
) functie polinomiala de gradul II.
Observatie: Cnd avem mai multe bucle imbricate, termenii buclei celei mai interioare dau gradul
polinomului egal cu gradul algoritmului.
Bucla cea mai interioara ne da complexitatea algoritmului.
i O n
i
n

( )
2
1
Exemplu: nmultirea a doua matrici
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(n
3
).
Exemplu: Cautarea binara(Binary Search)
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).
_________________________________________________________________________ 24
Structuri de date si algortitmi
_________________________________________________________________________
low
middle high
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 //restrng
| //cautarea la partea stnga
|_ else low = mid+1 //restrng cautarea la dreapta
return(0)
}
Calculul complexitatii algoritmului consta n determinarea numarului de ori pentru care se executa
bucla while.
Se observa ca, la fiecare trecere, dimensiunea zonei cautate se njumatateste. Cazul cel mai defavorabil
este ca elementul cautat sa nu se gaseasca n vector. Pentru simplitate se considera n = 2
k
unde k este numarul
de njumatatiri.
Rezulta k = log2 n si facnd o majorare, T(n) log
2
n + 1 n, a.. 2
k
n < 2
k+1.
Rezulta complexitatea acestui algoritm: este O(log
2
n). Dar, baza logaritmului se poate ignora,
deoarece: log
a
x = log
b
x * log
a
b si log
a
b este o constanta, deci ramne O(log n), adica o functie logaritmica.
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)
3) Fie f
1
, f
2
, g
1
, g
2
: N N.
si: f
1
= O(g
1
) | | f
1
+ f
2
= O(g
1
+ g
2
)
f
2
= O(g
2
) | | f
1
f
2
= O(g
1
g
2
)
Aceasta proprietate permite ca, atunci cnd 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(a
f(n)
)
_________________________________________________________________________ 25
Structuri de date si algortitmi
_________________________________________________________________________
Demonstratia se bazeaza pe limita:
lim ( , )
x
p
x
x
a
a p


ntre clasa functiilor logaritmice, si cea a functiilor polinomiale exista relatia: O(n
c
) O(a
n
).
Au loc urmatoarele incluziuni:
O(1) O(log n) O(n) O(n log n) O(n
2
) O(n
k
log n) O(n
k+1
)
O(2
n
)
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(n log n) clasa algoritmilor polilogaritmici;
O(n
2
) clasa algoritmilor patratici;
O(n
k
log n) clasa algoritmilor polilogaritmici;
O(n
k+1
) clasa algoritmilor polinomiali;
O(2
n
) clasa algoritmilor exponentiali.
Tehnici de calcul a complexitatii
Se folosesc urmatoarele sume:
) O(n
2
) 1 (
2
1

+

n n
i
n
i
) O(n
6
) 1 2 ( ) 1 (
3
1
2

+ +

n n n
i
n
i
) O(n
4
) 1 (
4
2 2
1
3

n n
i
n
i
1 - 2 2
1
0
1 +

n
n
i
Sa calculam, de exemplu, suma:

n
i
i
1
1
2
Se noteaza:


n
i
i n G
1
1
2 ) (
2 2 ) 1 ( 2 2 2 2 ) 1 ( 2 2
2 2 2 2 2 ) ( 2 ) (
1
2
1 1
2
1 1
1
1
1
1 1
1
1
1
1
+ +

+

+

+



n
n
i
n
n
i
n
n
i
n
i
n
i
n
i
n n i i n
i i i i n G n G
Prin aceeasi tehnica se calculeaza suma:


n
i
n
1
1
2 ) 1 (
_________________________________________________________________________ 26
Structuri de date si algortitmi
_________________________________________________________________________
Curs 5
Am vazut ca:

+

n
i
n n
n i
1
1 1
2 2 2
Algoritmul recursiv si relatii de recurenta
Exemplu: Problema turnurilor din Hanoi
Se dau n discuri: a
1
, a
2
, ... , a
n
de dimensiuni diferite, cu d
1
< d
2
< ... < d
n
, d
i
- fiind diametrul
discului. Discurile respective sunt stivuite pe o tija:
cazul n=3
Se cere sa se deplaseze aceasta stiva pe o alta tija, folosind ca manevra o tija auxiliara, respectndu-se conditia
<< Un disc nu poate fi plasat dect 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 (t
i
) pe tija de
manevra, apoi plasarea celui mai lung disc pe tija finala, pentru ca la sfrsit sa se aduca de pe tija de manevra
(t
m
), pe tija finala (t
f
), cele n-1 discuri deplasate.
Primele miscari s-ar figura astfel:
t
t
t
t
t
t
i
m f
f
i m
Procedura Hanoi:
_________________________________________________________________________ 27
Structuri de date si algortitmi
_________________________________________________________________________
Hanoi(n, t
i
, t
f
, t
m
)
{
if(n=1) then muta (t
i
, t
f
)//deplaseaza discul superior t
i
-> tf
else | Hanoi(n-1, t
i
, t
m
, t
f
)
| muta(t
i
, t
f
)
|_ Hanoi(n-1, t
m
, t
f
, t
i
)
}
Pentru o problema P(1) , timpul T(1) = 1 , pentru o mutare.
Pentru P(n) , timpul:
1 ) 1 ( 2 ) ( + n T n T (1)
Dorim sa aflam ordinul de complexitate a lui T(n).
) 1 ) 2 ( 2 ( 2 1 1 ) 1 ( 2 ) ( + + + n T n T n T
Asociem relatiei (1) ecuatia caracteristica: 1 2 ; 1 ; 1 2
0 0
+ x x x x x
0 0 0
) ( ) ( ) 1 ( 2 ) ( ) 1 ( ( 2 ) ( x n T n f n f n f x n T x n T
const. cu , ) 1 ( ) 1 (
0 0
x x n T n f
) 1 ( 2 ... ) 4 ( 2 ) 3 ( 2 2 2 ) 2 ( 2 2 ) 1 ( 2 ) (
1 4
f n f n f n f n f n f
n


Facnd identificarea:
x f(1)
1 2 = T(n) 1 ) ( 2 2
0
n 1
+

n T
n
Ordinul este O(2
n
),
adica o complexitate exponentiala.
Relatii de recurenta. Clasele relatiilor de recurenta
1. f n a f n b ( ) ( ) + 1
f(n) = a f(n - 1) + b
x
0
= a x
0
+ b
Prin scaderea celor doua relatii, rezulta un algoritm exponential cu baza a: f(n) - x
0
= a (f(n-1) - x
0
)
2. f n a f n b f n ( ) ( ) ( ) + 1 2
f(n) = t
n
t
n
= a t
n-1
+ b t
n-2
Facnd n = 2, t
2
= a t + b , cu urmatoarele cazuri:
a) t
1
, t
2
R solutia ecuatiei este de forma:
f n t t
n n
( ) +
1 2
iar si se calculeaza din conditiile
initiale:

'

+
+

'

2
2
2
2
1
1 2 1
2
1
) 2 (
) 1 (
x t t
x t t
x f
x f


cu x
1 si
x
2 constante.
Astfel, este rezolvata ecuatia recursiva.
b) t
1
= t
2
= t Solutia este de forma:
n
t n n f + ) ( ) (
c) t
1
, t
2
C Solutia este de forma:
n n
t t n f
2 1
) ( +
n care si C, =

(conjugat) solutia trigonometrica: ) sin cos ( ) (


1 1
nt nt r n f
n
+
_________________________________________________________________________ 28
Structuri de date si algortitmi
_________________________________________________________________________
3. Clasa de relatii de recurenta pentru algoritmi de tip "divide et impera"
Exemplu: Algoritmul Merge Sort (sortare prin interclasare)
Pentru a sorta o secventa de n elemente ale unui vector A, se mparte vectorul n 2 segmente de
lungime n/2 pe care le sorteaza separat recursiv, dupa care urmeaza interclasarea.
Pseudocod: Procedura MERGE_SORT primeste ca argumente A - vectorul de sortat, si doi indici care
delimiteaza o portiune din acest vector. Apelul initial va fi MERGE_SORT(A, 1, n).
MERGE_SORT(A, low, high)
{
if(low high) return
else | | low + high |
| mid=| ---------------- | //partea ntreaga
| |_ 2 _|
| MERGE_SORT(A, low, mid) //sortare separata
| MERGE_SORT(A, mid+1, high) //sortare separata
|_MERGE(A, low, mid, high) //interclasare
}
Procedura MERGE interclaseaza secventele sortate A[lowmid] si A[mid+1high]. Pentru aceasta este
nevoie de un vector auxiliar B, de aceeasi dimensiune cu A.
MERGE(A, low, mid, high)
{
i=low; j=mid+1; k=low;
while i mid and j high do
| if A[i] <A [j] then B[k]=A[i]; i=i+1
| else B[k] = A[j]; j=j+1
|_ k = k+1
while i mid do
| B[k] = A[i]; i=i+1
|_ k = k+1
whilej high do
| B[k] = A[j]; j=j+1
|_ k = k+1
for k=low to high do
A[k] = B[k];
}
Aratam complexitatea procedurii MERGE_SORT: O(n log n)
Aceasta functie cere pentru o secventa c n operatii.
Timpul de executie al algoritmului este:
1 n n, + 2T(n/2)
1 = n , 0
) (

n T
Consideram: n = 2
k
;
T(n) = 2 T(n/2) + n = 2 (2 T(n/4) + n/2) + n = ... = 2
2
T(n/2
2
) + 2n= 2
2
(2T(n/2
3
) + n/2
2
) + 2n =
= 2
3
T(n/2
3
) + 3n = ... = 2
k
T(n/2
k
) + k n T(n) = k n = n log
2
n , pentru ca n = 2
k
, si, deci, k = log
2
n.
Asadar complexitatea algoritmului este O(n log n).
_________________________________________________________________________ 29
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:
k
n C b n T a n T + ) / ( ) (
Solutia acestei ecuatii de recurenta este:
k k
k k
k a
b a n O
b a n n O
b a n O
n T
b
), (
), log (
), (
) (
log

Utiliznd aceste retete putem calcula complexitatile pentru:


algoritmul Merge_Sort: a = 2 , b = 2 , k = 1 , b
k
= a complexitatea O(n
k
log n)
algoritmul Binary_Search: a = 1 , b = 2 , k = 0 complexitatea O(n
0
log n) = O(log n), (situatia a
k
= b).
4. Relatii de recurenta cu istorie completa
Exemplu: T n C T i
i
n
( ) ( ) +

1
1
T n C T n T n T ( ) ( ) ( ) ... ( ) + + + + 1 2 1
T n C T i T n T n
i
n
( ) ( ) ( ) ( ) + + +

1 1
1
- se face
Exemplu: Algoritmul Quick_Sort
elemente pivot A[k]=pivot elemente
pivot
l h
Quik_Sort(A, low, high)
{
if(high >low) then
| k= Partition(A, low, high) // procedura de |
// partitionare
| Quick_Sort (A, low, k-1)
|_ Quick_Sort(A, k+1, high)
}
Pseudocodul pentru functia partition:
Partition(A, low, high)
{
l= low; h= high;
x= A[l];
while (l <h) do
I | while (A[l] <= x) and (l <=high)
| do l= l+1
II | while (A[l] >x) and (h low)
| do h= h-1
|_ if (l <h) then interchange (A[l], A[h])
_________________________________________________________________________ 30
Structuri de date si algortitmi
_________________________________________________________________________
interchange (A[h], A[low])
return(h);
}
Algoritmul considera pivotul ca fiind: A[low]. Indicele l parcurge vectorul de la stnga la dreapta, iar
indicele h parcurge vectorul de la dreapta la stnga. Ei se apropie pna se ntlnesc (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 ct timp A[l] pivot. Acest ciclu se opreste pe conditia
A[h] > pivot, fixndu-se aici.
Ciclul II while nsemna ca nainteaza h ct timp A[h] > pivot. Acest ciclu se opreste pe conditia
A[h] pivot, fixndu-se aici.
Cele doua pozitii se schimba, astfel nct 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 T n ( ) ( ) ( ) + 1 1
Rezolvnd aceasta ecuatie, avem:
T n n T n n n T n n n n T ( ) ( ) ( ) ... ... ( ) + + + + + + + + 1 1 1 2 2 1 2 3 1 1
unde: T(1) este 0 (nu se partitioneaza). Rezulta:
T n i n n
i
n
( ) ( ) /

1 2
1
1
Aceasta suma este de complexitate O(n
2
). Rezulta ca este un algoritm ineficient.
Studiul complexitatii algoritmului Quick_Sort n caz mediu
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 n , ( , ) 1 .
Suma tuturor probabilitatilor este 1. Evenimentul este plasarea pivotului n pozitia k. Consideram
T
i
(n) timpul de executie al algoritmului Quick_Sort atunci cnd pivotul este plasat n pozitia i:
i
_________________________________________________________________________ 31
Structuri de date si algortitmi
_________________________________________________________________________
Rezulta: T n n T i T n i
i
( ) ( ) ( ) + 1 1
Timpul mediu va fi o medie aritmetica:



,
_


,
_

n
i
i
n
i
i
n T
n
n T
n
n T
1 1
) (
1
) (
1
) (
Dezvoltnd,




,
_

+
,
_

+
,
_

+

,
_

+
,
_

+
,
_

+ +
,
_

n
i
n
j
n
i
n
i
n
i
n
i
i T
n
n j T
n
i T
n
n
i n T
n
i T
n
n n
n
i n T i T n
n
n T
1 1
1
0
1 1 1
) (
2
1 ) 1 (
1
) 1 (
1
1
) (
1
) 1 (
1
) 1 (
1
)) ( ) 1 ( ) 1 ((
1
) (
(Facnd schimbarea de variabila j = n - i + 1)
Rezulta relatia de recurenta cu istorie completa:


,
_

+
1
0
) (
2
1 ) (
n
i
i T
n
n n T
Aceasta se rezolva astfel: nmultind relatia cu n rezulta:

+
1
0
) ( 2 ) 1 ( ) (
n
i
i T n n n T n
Scriem acum relatia nlocuind pe n cu n+1:

+ + + +
n
i
i T n n n T n
0
) ( 2 ) 1 ( ) 1 ( ) 1 (
Si scazndu-le acum membru cu membru rezulta:
) ( ) 2 ( 2 ) 1 ( ) 1 (
) ( 2 ) 1 ( ) 1 ( ) ( ) 1 ( ) 1 (
n T n n n T n
n T n n n n n nT n T n
+ + + +
+ + + +
care se nmulteste cu:
) 2 )( 1 (
1
+ + n n
,
1
) (
) 2 )( 1 (
2
2
) 1 (
+
+
+ +

+
+
n
n T
n n
n
n
n T
Notam: ) 1 - ( +
) 1 (
) 1 ( 2
) ( ), (
) 2 )( 1 (
2
) 1 ( ,
1
) (
) ( n F
n n
n
n F n F
n n
n
n F
n
n T
n F
+
+
+
+ +

+
+

Facnd o majorare: ) 1 (
n
2
) ( + n F n F
) (
2
2
...
2
2
1
2 2
... ) 3 (
2
2
1
2 2
) 2 (
1
2 2
) 1 (
2
) ( i F
n n n
n F
n n n
n F
n n
n F
n
n F + + +

+ +

+ +

+ +
0 ) ( i F (un element nu se ordoneaza).
Rezulta:

,
_


n
i
i
n F
2
1
2 ) ( si dx
1
2
1
2 ) (
2


,
_


,
_

n
i
n
i
x i
n F este aria zonei de sub graficul
functiei
x
x f
1
) ( .
) ln ( ) ( ln 2 ln ) 1 ( ) ( ) (
1
) (
ln 2 ) ( 1 ln 2 ln 2 ) (
n n O n T n n n n n T n F
n
n T
n n F n n F
+
+

unde O(n ln n) este complexitatea acestei functii.
_________________________________________________________________________ 32
Structuri de date si algortitmi
_________________________________________________________________________
Curs 6
Analiza spatiului de memorie consumat ntr-un algoritm
Algoritmi recursivi
Algoritmii recursivi consuma memorie suplimentara pentru simularea recursivitatii.
Fie urmatoarea procedura recursiva:
parcurgere(l) // l - lista nlantuita (pointer la primul
//element)
{
if(l 0)
| parcurgere(link(l))
|_ prelucrare(data(l)) // exemplu: afisare
}
Functia afiseaza o lista invers, de la coada la cap.
Apelul functiei se face astfel:
se creeaza n stiva programului o "nregistrare de activare" n care sunt memorate:
- parametrii de apel;
- adresa instructiunii de retur (cu care va continua programul dupa terminarea executiei
functiei);
se rezerva spatiu pentru variabile locale.
se executa instructiunile functiei care folosesc pentru parametri si variabile locale din "nregistrarea de
activare";
se scoate din stiva "nregistrarea de activare" (decrementarea vrfului stivei), stiva fiind ordonata;
se continua cu instructiunea data de adresa de retur memorata n "nregistrarea de activare".
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++).
Consumul de memorie al algoritmului recursiv este proportional cu numarul de apeluri recursive ce se
fac. Variabilele recursive consuma mai multa memorie dect cele iterative. La prelucrarea unei liste, daca
primul element nu este vid, se prelucreaza acesta, urmnd apoi ca restul listei sa fie considerata ca o noua lista
mai mica, etc.
De exemplu, algoritmul Quick_Sort:
Quick_Sort(A, low, high)
{
_________________________________________________________________________ 33
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:
low
k
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 nct un
apel sa fie rezolvat recursiv, iar celalalt apel iterativ.
secventa mica rezolvata recursiv
k
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), nsemnnd ca oricare ar fi secventa mai
mica, ea este dect jumatatea M(n) = O(log n) am redus ordinul de complexitate.
Liste generalizate
Definitie:
_________________________________________________________________________ 34
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.
Reprezentarea listelor generalizate
Presupunem o lista de forma: (tag, data, link) n care tag este o eticheta {0,1}.
Daca tag = 0 nodul va corespunde unui element atomic cmpul data va contine atomul respectiv.
Daca tag = 1 nodul va corespunde unei subliste cmpul data va semnifica legatura la primul
element al sublistei; link este legatura pentru urmatorul nod din lista.
Fie urmatoarele primitive de selectie pentru un nod de adresa p:
p adresa unui nod;
link(p) cmpul "link" din nodul indicat de p;
Notam: tag(p) cmpul "tag" din nodul indicat de p
data(p) cmpul "data" din nodul indicat de p
Fie urmatoarele liste:
D = ( )
A = (a, (b, c))
B = (A, A, ( ))
C = (a, C)
cu urmatoarele reprezentari:
D : o lista vida nseamna un pointer nul
A:
B:
C:
0 a 1
b c
0
0 0 0
0 0
0 0
1 1 1
1 a
Ne propunem sa dam nume unei subliste, deci daca tag = 1, adica tag(p) = 1 data(p) va fi adresa
unei structuri ce contine:
_________________________________________________________________________ 35
Structuri de date si algortitmi
_________________________________________________________________________
nume lista
pointer la primul element
Asadar, obtinem urmatoarea reprezentare:
D:
A:
B:
C:
D 0
A
a 0 0 1
_
b 0
0 0 c
B
1 1
0 1
_
C
a 0
0 1
Operatii la liste generalizate:
functia insert, este asemanatoare cu cea de la liste nlantuite. Elementul ce se insereaza poate fi un atom
sau o sublista;
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.
_________________________________________________________________________ 36
Structuri de date si algortitmi
_________________________________________________________________________
Exemplu:
A 2
Numai daca acest indicator este 0, se face stergerea efectiva a listei.
Traversarea listelor generalizate
Traversarea listelor generalizate presupune prelucrarea elementelor listei, si a elementelor sublistelor
componente.
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 nti primul element, si apoi recursiv restul listei:
Varianta recursiva:
Copy (l) // l - lista nlantuita
{
if (l = 0) then return (0)
else | p = get_sp()
| data(p) = data(l)
| link(p) = Copy(link(l))
|_ return(p)
}
Copy (l) // l - lista generalizata
{
if (l = 0) then return (0)
else | p = get_sp()
| if (tag(l) = 0) then data(p) = data(l)
| else data(p) = Copy(data(l))
| link(p) = Copy(link(l))
|_ return(p)
}
Functia pentru testarea egalitatii este:
isEqual (l
1
,l
2
) // procedura tratata iterativ
{
p
1
= l
1
; p
2
= l
2
while(p
1
0 and p
2
0)
| if (data(p
1
) data(p
2
)) then return (FALSE)
| p
1
= link(p
1
)
|_ p
2
= link(p
2
)
return(p
1
= p
2
)
}
isEqual (l
1
,l
2
) // procedura tratata recursiv
{
p
1
= l
1
; p
2
= l
2
_________________________________________________________________________ 37
Structuri de date si algortitmi
_________________________________________________________________________
while(p
1
0 and p
2
0)
| if (tag(p
1
) tag(p
2
)) then return (FALSE)
| if (tag(p
1
) = 0 and data(p
1
) data(p
2
)) then |
return (FALSE)
| if (tag(p
1
) = 1 and not isEqual |
(data(p
1
),data(p
2
))then return FALSE) |
p
1
= link(p
1
)
|_ p
2
= link(p
2
)
return (p
1
== p
2
)
}
_________________________________________________________________________ 38
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 nct j V, (j, r) E (nici un arc nu intra in radacina);
2) x V\{r} , y V unic , astfel nct (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 = x
0
, x
1
, x
2
, ... ,x
n
= y} , cu x
i
V si (x
i
, x
i+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 nct 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}= T
1
T
2
... T
k
, T
i
T
j
= .
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.
_________________________________________________________________________ 39
Structuri de date si algortitmi
_________________________________________________________________________
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 = x
0
, x
1
, x
2
, ..., x
k
} cu proprietatea ca
(x
i
, x
i+1
) E , i= {0,k - 1} si (x
k
, x) E
Nivelul unui nod : level(x) = ancestors(x) + 1
Adncimea 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:
_________________________________________________________________________ 40
Structuri de date si algortitmi
_________________________________________________________________________
0 K L
E
F 1 G
H
M
I J
0
0
0 0 0
B
C D
1
0 0
0
0
A
1 1 1 0
0
Reprezentarea prin structuri nlantuite
_________________________________________________________________________ 41
Structuri de date si algortitmi
_________________________________________________________________________
K L M
0 0
0 0 0 0 0 0 0
0 0 0 0
0 0
0 0 0 0
0 0 0 0 0
E
F G H I J
A
B
C
D
0
0 0
data
link1 link2 link k
......
k=degree(T)
Aceasta reprezentare are calitatea ca, atunci cnd 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):
_________________________________________________________________________ 42
Structuri de date si algortitmi
_________________________________________________________________________
A
B C D
E F
0
G H I J
K L M
Avnd adresa unui nod, se gasesc toti predecesorii, obtinndu-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
Observatie: Arborele A este diferit de A
/ \ / \
B vid vid B
Structura de baza a unui arbore binar:
rad
/ \
/ \
/ \
/ \
/ \ / \
/ SAS \ / SAD \
/______ \ /_______\
SAS subarborele stng (binar)
SAD subarborele drept (binar)
Definitii
_________________________________________________________________________ 43
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 k l 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 2
i-1
.
Demonstratia se face prin inductie:
La nivelul 1 avem 2
0
= 1 nod = rad (radacina A). Presupunem conform metodei P(n): pe nivelul n
avem 2
n-1
noduri. Demonstram pentru P(n+1): se observa ca toate nodurile de pe nivelul n+1 sunt noduri
descendente de pe nivelul n. Notnd niv(i) numarul de noduri de pe nivelul i,
niv(n+1) 2niv(n) 2 2
n-1
= 2
n
.
Lema 2
Numarul maxim de noduri ale arborelui binar de adncime h este egal cu 2
h
-1.
Demonstratie:
Numarul total de noduri este egal cu: niv i
i
i
h
h
h
h
i
h
( ) ... + + +


2 2 2 2
2 1
2 1
2 1
1
1
0 1 1
1
(progresie geometrica)
Observatie: Numarul maxim de noduri pentru arborele binar se atinge n situatia unui arbore binar complet.
2
h
-1 = numarul de noduri n arborele binar complet de adncime h
Lema 3
Notam cu:
n
2
numarul de noduri de grad 2 din arborele binar;
_________________________________________________________________________ 44
Structuri de date si algortitmi
_________________________________________________________________________
n
1
numarul de noduri de grad 1 din arborele binar;
n
0
numarul de noduri terminale (frunze) din arborele binar;
n orice arbore binar, n
0
= n
2
+1 (nu depinde de n
1
).
Demonstratie: fie n = n
0
+ n
1
+ n
2
(numarul total de noduri); conform definitiei, fiecare nod are un
singur predecesor numarul de muchii E = n - 1. Acelasi numar de muchii E = 2 n
2
+ n
1
.
Deci, n - 1 = 2n
2
+ n
1
, nlocuind, n
0
+ n
1
+n
2
-1 = 2n
2
+ n
1
n
0
= n
2
+ 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 cmpului de date dintr-un nod si selectia descendentilor;
inserarea unui nod;
stergerea 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 E 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 stng (p stg)
_________________________________________________________________________ 45
Structuri de date si algortitmi
_________________________________________________________________________
rchild(p) pointer la succesorul drept (p drt)
data(p) informatia memorata n nodul respectiv (p data)
n 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 avnd n
noduri reprezentarea va contine n (k - 1) + 1 pointeri cu valoare zero (nuli).
Demonstratie: Numarul total de pointeri utilizati n reprezentare este n k Numarul total de pointeri nenuli este
egal cu numarul de arce n k - (n - 1) = n (k - 1) + 1
nr. pointeri nuli
nr. total pointeri n k

n k n
n k
( ) 1 1
1
1
raportul este maxim pentru k = 2.
Rezulta ca cea mai eficienta reprezentare (n structura nlantuita) este reprezentarea n arbori binari.
_________________________________________________________________________ 46
Structuri de date si algortitmi
_________________________________________________________________________
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) k cu proprietatea ca doi atomi distincti au chei
diferite de cautare: a
1
a
2
key(a
1
) key(a
2
).
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)
Considernd r V (radacina arborelui), Ts subarborele stng 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 stng sunt mai mici dect radacina si toate
nodurile din subarborele drept sunt mai mari dect radacina.
Exemple: 15 15
/ \ / \
7 25 10 25
_________________________________________________________________________ 47
Structuri de date si algortitmi
_________________________________________________________________________
/ \ / \ / \ / \
2 13 17 40 2 1 7 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)
_________________________________________________________________________ 48
Structuri de date si algortitmi
_________________________________________________________________________
}
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 facnd 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 adncime pe ramura
dreapta pna se gaseste primul nod care nu are descendent dreapta. Acesta va fi cel mai mare.
_________________________________________________________________________ 49
Structuri de date si algortitmi
_________________________________________________________________________
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
stng (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 adncimea arborelui. n 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 dect 15 12 11 13 .
Studiem un caz de complexitate medie:
Crearea unui BST pornind de la secventa de atomi (a
1
a
2
... a
n
)
gen_BST (va fi n programul principal)
| rad= 0
| for i= 1 to n
| insert (rad, a
i
)
Calculam complexitatea medie a generarii BST:
Complexitatea n cazul cel mai defavorabil este:

n
i
n O i
1
2
) ( ) (
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 (a
1
a
2
... a
n
) cu observatia ca a
1
este radacina arborelui.
Ca rezultat, n urma primei operatii de inserare pe care o facem, rezulta:
a1
/ \
a
1
a
i

(a
i
<a1) (a
i
>a1)
_________________________________________________________________________ 50
Structuri de date si algortitmi
_________________________________________________________________________
Nu vom considera numararea operatiilor n ordinea n care apar ele, ci consideram numarul de operatii
globale. Dupa ce am inserat a
1
, pentru inserarea fiecarui element n SAS sau SAD a lui a
1
, se face o comparatie
cu a
1
. 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 stng SAS
val.med.SAD = valoarea medie a numarului de comparatii necesar pentru a construi subarborele drept SAD

+
+ +
n
i
n
i
i n T n i T n n n T
1 1
) ( ) / 1 ( ) 1 ( ) / 1 ( ) 1 ( ) (
Notam:
T
i
(n) = numarul mediu de comparatii necesar pentru construirea unui BST cu n noduri atunci cnd prima
valoare inserata (a
1
) este mai mare dect i-1 dintre cele n valori de inserat. Putem scrie:
T n n T i T n i
i
( ) ( ) ( ) ( ) + + 1 1

+
n
i
i
n T n n T
1
) ( ) / 1 ( ) (
Deci:

+
+ +
+ +
n
i
n
i
n
i
n
i
i T n n
i n T n i T n n
i n T i T n n n T
1
1 1
1
) 1 ( ) / 2 ( ) 1 (
) ( ) / 1 ( ) 1 ( ) / 1 ( ) 1 (
)) ( ) 1 ( ) 1 (( ) / 1 ( ) (
Deci:


1
0
) ( ) / 2 ( 1 ) (
n
i
i T n n n T
Complexitatea acestei functii este: O(n ln 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 adncimile
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
_________________________________________________________________________ 51
Structuri de date si algortitmi
_________________________________________________________________________
complet echilibrat echilibrat
Adncimea 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 T
3
h = depth(T
1
) = depth(T
2
) = depth(T
3
)
/ \
T
1
T
2
Consideram arborii T
1
, T
2
, T
3
echilibrati. Insernd un nod prin rotatie simpla, rezulta structurile rotit
simplu la dreapta si rotit simplu la stnga imaginea oglinda a rotatiei dreapta:
A A
/ \ / \
B T
3
T
3
B
/ \ / \
T
1
T
2
T
2
T
1

Caz 2: Inserarea se face prin rotatii duble:
A A
/ \ / \
B T
3
T
3
B
/ \ / \
T
1
T
2
T
2
T
1

rotatie dubla rotatie dubla
la dreapta la stnga
Fie primul caz:
A
/ \
B T
3
/ \
T
1
T
2
este BST: T
1
< B < T
2
< A < T
3

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


Ridicnd pe B n sus, si notnd cu // legaturile neschimbate, rezulta:
B
// \
// A
T
1
/ \\
_________________________________________________________________________ 52
Structuri de date si algortitmi
_________________________________________________________________________
____________T
2
_T
3
_________ pe aceeasi
linie
care este un BST , deci este arbore echilibrat, si are aceeasi adncime (!!!) 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 T
2
. Nu se poate sparge arborele initial ca n
cazul 1.
A
/ \\
B \\
// \ T
3
// C
// / \
T
1
T
2S
T
2D

depth(T
1
) = depth(T
3
) = h
depth(T
2S
) = depth(T
2D
) = h - 1
n urma inserarii, unul dintre arborii T
2S
si T
2D
si mareste adncimea. Aplicam aceiasi tehnica:
T
1
< B < T
2S
< C < T
2D
< A < T
3
ncepem cu C:
C
/ \
B A
// \ / \\
// T
2S
T
2D
\\
____T
1
_________T
3
_____________________
la acelasi nivel
Rezulta un BST echilibrat, de aceeaai adncime cu arborele initial. Rotatiile sunt duble, n sensul ca
s-a facut o rotatie simpla B la stnga 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.
_________________________________________________________________________ 53
Structuri de date si algortitmi
_________________________________________________________________________
Curs 9
Asadar, n inserarea prin rotatie se obtine un arbore echilibrat cu adncimea egala cu adncimea
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 adncimea subarborelui stng si adncimea subarborelui drept.
Calculam factorii de balansare dupa inserare.
Observatie: Pentru nodul terminal s-a schimbat adncimea 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 ntlnit 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. 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 cnd 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 nct noua radacina capata bf = 0, conservndu-se adncimea.
Concluzie: Problema conservarii proprietatii de echilibrare a arborelui se rezolva aplicnd o rotatie asupra
nodului critic numai atunci cnd inserarea dezechilibreaza acest nod.
_________________________________________________________________________ 54
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.
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 nti cu 55 : e n stnga lui, s.a.m.d. pna cnd gasim locul
ei n pozitia de mai jos. Pentru a doua valoare de inserat, 7, nodul critic este 55. El este dezechilibrat stnga.
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
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 (T
2D
, T
2S
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 T
2S
,
B are bf = 0 |
_________________________________________________________________________ 55
Structuri de date si algortitmi
_________________________________________________________________________
A are bf = -1 | exceptie facnd nodul C nodul de inserat
La rotatia dubla, daca nodul 9 a fost inserat n subarborele T
2D
,
B are bf = 1 |
A are bf = 0 | exceptie facnd nodul C nodul de inserat
Reprezentarea implicita a arborilor binari
n 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 2
4
- 1 = 15 noduri.
Asociem acestui arbore un vector V:
1
a
indici
V:
2 3 4 5 6 7 9 11 10 8 12 14 13 15
b c d e f g h i k m
j l n o
structurat astfel: radacina, nivelul 1 de la stnga la dreapta, nodurile nivelului 2 de la stnga 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 [2i];
2. right_child(x) este reprezentat n elementul de vector V [2i + 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 completndu-se
structura arborelui cu noduri fictive pna 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.
_________________________________________________________________________ 56
Structuri de date si algortitmi
_________________________________________________________________________
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 dect cheile descendentilor).
Exemplu: 99
/ \
50 30
/ \ / \
45 20 25 23
Observatie: De obicei functia cheie reprezinta selectia unui subcmp din cmpul de date memorate n nod.
Aplicatii ale arborilor heap
Coada cu prioritate;
Algoritmul Heap_Sort
Arborii heap nu se studiaza complet n acest curs.
_________________________________________________________________________ 57
Structuri de date si algortitmi
_________________________________________________________________________
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:
50 40 30 33 37 12 2 10 7 15 42
Operatiile insert si remove pentru arbori heap au o forma foarte simpla cnd utilizeaza reprezentarea
implicita. Consideram, n continuare, arbori heap n reprezentare implicita.
Exemplu: un arbore cu ultimul nivel avnd toate nodurile aliniate la stnga:
Inseram valoarea 42 se adauga nodul la un nivel incomplet;
n reprezentarea implicita se adauga nodul la sfrsit.
insert:
_________________________________________________________________________ 58
Structuri de date si algortitmi
_________________________________________________________________________
1) In reprezentarea implicita: V [N + 1] = a
N = N + 1
n continuare reorganizam structura arborelui astfel nct 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 dect V[parent] (se schimba 42 cu 37)
3) naintare n sus: child = parent
parent = [child/2]
4) Se reia pasul 2) pna cnd 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
_________________________________________________________________________ 59
Structuri de date si algortitmi
_________________________________________________________________________
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 pna cnd 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 stnga retrogradarea se face n stnga;
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:
n 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 adncimea
arborelui. Daca N este numarul de noduri din arbore, 2
k
N 2
k+1
, si adncimea arborelui este k+1.
2
k
- 1 < N 2
k+1
- 1 k = [log
2
N]
| |
| |
| |
nr. de noduri nr. de noduri
ale arborelui ale arborelui
complet de complet de
adncime k adncime k+1
_________________________________________________________________________ 60
Structuri de date si algortitmi
_________________________________________________________________________
k log
2
N < k + 1 adncimea arborelui este k = [log
2
N].
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.
heap i
partea sortata
a vectorului
min max
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 opera_ii insert n heap-uri cu dimensiunea N n
Rezulta complexitatea acestor operatii nu depaseste O(n log n). Facem un studiu pentru a vedea daca
nu cumva ea este mai mica dect O(n log 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 adaugnd 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 : 2
i-1
noduri i-1 comparatii
_________________________________________________________________________ 61
Structuri de date si algortitmi
_________________________________________________________________________
Considernd un arbore complet (nivel complet) N = 2
k
- 1 numarul total de comparatii pentru toate
nodurile este T(n) de la nivelul 2 la nivelul k. Vom calcula:


k
i
i
i n T
2
1
2 ) 1 ( ) (
Sa aratam:


1
1
1
2 ) (
k
i
i n T
cu tehnica ) ( ) ( 2 ) ( n T n T n T . Asadar:
2 2 ) 2 ( ) 1 2 ( 1 2 ) 1 (
2 2 2 2 2 ) 1 ( 2 2 ) 1 ( 2
2 ) 1 ( ... 2 3 2 2 2 1 2 ) 1 ( 2 ) 2 ( ... 2 3 2 2 2 1
2 2 2 2 2 ) (
1
0
1 0
1
2
1 3 2 1 1 4 3 2
1
1
1
1
1
1
1
1
1
+ +
+ + +
+ + + + +


k k k
k
i
i k
k
i
i k
k k k
k
i
k
i
k
i
i i i
k
i
i
k k
k k
k k k
i i i i n T
Rezulta: k k n k k k n T
k k
+ + + + ) 2 ( 2 2 ) 1 2 ( ) 2 ( 2 2 ) 2 ( ) (
iar: ) 1 ( log
2
+ n k ,
din
1 2
k
n
Rezulta: ) 1 ( log ) 2 ) 1 ( (log ) (
2 2
+ + + n n n n T
------------------------
termen dominant
Facndu-se majorari, rezulta complexitatea O(n log 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 stnga). Cele mai multe noduri sunt la baza, doar nodurile
din vrf parcurg drumul cel mai lung.
noduri terminale
I
II
i
_________________________________________________________________________ 62
Structuri de date si algortitmi
_________________________________________________________________________
Elementele V[i+1,N] ndeplinesc conditia de structura a heap-ului:
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 stnga 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
}
n aceasta situatie, vom avea:
heap_gen
{
for i = [N/2] downto 1 step -1 do
retrogradare(V, n, i)
}
Complexitatea acestei operatii
Fie un arbore complet cu n = 2
k
- 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 : 2
k-2
noduri o operatie de comparatie
nivel k-2 : 2
k-3
noduri 2 operatii
------------------------------------------------------------------
nivel i : 2
i-1
noduri k-i operatii
-------------------------------------------------------------------
nivel 2 : 2
1
noduri k-2 operatii
nivel 1 : 2
0
noduri k-1 operatii
Se aduna, si rezulta:


1
1
1
2 ) ( ) (
k
i
i
i k n T
Tehnica de calcul este aceeasi: ) ( ) ( 2 ) ( n T n T n T
_________________________________________________________________________ 63
Structuri de date si algortitmi
_________________________________________________________________________
1 2 3 1 ) 1 2 ( 2 ) 1 ( 2 2 2
2 2 ) 1 ( 2 2 ) 1 ( 2 2 2 2 ... 2 ) 3 ( 2 ) 2 ( 2 ) 1 (
2 2 2 3 ... 2 ) 3 ( 2 ) 2 ( 2 ) 1 ( 2 ) ( 2 ) 1 ( ) (
2 2 2 1
3
1
3
0
1 0 1 1 2 3 2 1 0
2
1
2
1
2 3 3 2 1 1
+ +
+ +
+ + + + +




k k k
k k k k k
k k k i k k n T
k k k k
k
i
k
i
k k k
k
i
k
i
k k i i
Rezulta: 1 ) 1 2 ( 3 1 2 3 ) (
2


k k n T
k k
1 ) 1 ( log 3 ) (
2
+ n n n T
-------
termen dominant
Rezulta complexitatea este O(n). Comparnd cu varianta anterioara, n aceasta varianta (cu heap-ul la
baza) am cstigat 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(nlog n)
---------------
termen ce determina complexitatea
Rezulta complexitatea alg. Heap_Sort = O(nlog n)
_________________________________________________________________________ 64
Structuri de date si algortitmi
_________________________________________________________________________
Curs 11
Grafuri. Traversari pe grafuri
Definitie:
Fie G = (V, E) o multime, n care V este o multime de noduri finita, V = n , si E o multime de arce,
E = {(i, j), i, j N }. G se numeste graf, E V V.
Avem: grafuri orientate n care (i, j) (j, i)
grafuri neorientate n care (i, j) E (j, i) E
Observatie: Un arbore este un caz particular de graf.
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;
E
i
= { k, (k, i) E} multimea de vecini la intrare;
E'
i
= { j, (i, j) E} multimea de vecini la iesire;
E
i
E'
i
multimea vecinilor.
grad_intrare(i) = in_degree(i) = E
i

grad_ie_ire(i) = out_degree(i) = E'


i

Pentru un graf neorientat (G - neorientat), E


i
E'
i
si degree(i)= E
i
.
Definitie
Se numeste drum orientat ntr-un graf de la x la y secventa de noduri D = (i
1
= x, i
2
, ... , i
n
= y),
(i
k
, i
k+1
) E,
k n 1,
.
Drumul D este neorientat, daca (i
k
, i
k+1
) E sau (i
k+1
, i
k
) 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 = V V \ {(i, i), i V}
Definitie
Fie G = (V, E) un graf. Se numeste subgraf al grafului G, un graf G' = (V', E'), astfel nct V' V,
E' (V' V') E
Reprezentari
1) Matrice de adiacenta
Fie G = (V, E) ,V = n. Se determina matricea de adiacenta
_________________________________________________________________________ 65
Structuri de date si algortitmi
_________________________________________________________________________
astfel: M M i j
i j E
i j E
n n

, ( , )
( , )
( , )
1
0


1
1
1
1
1
1
]
1

0 0 0 0 1
1 0 0 1 0
0 0 0 0 0
1 0 0 0 0
0 0 1 1 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:
C i j
i j i j E
i j E
( , )
( , ), ( , )
, ( , )


+
0

Complexitate:
O(1), (i, j) E
O(n), E
i
, E'
i
, indegree(i), outdegree(i)
O(n
2
), spatiu
1) Liste de adiacenta directa
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 continnd adresa unei liste dupa regula urmatoare: L(i) da
adresa listei nlantuite care contine toti succesorii lui i.
L(i) E
i
Exemplu:
0
0
0
0
3
5
1
2
2
3
0
1
2
3
4
5
3) Liste de adiacente inverse
_________________________________________________________________________ 66
Structuri de date si algortitmi
_________________________________________________________________________
(i,j) E
O(E) cardinal de E
L'(1, 2, ..., n), L'(i) E'
i
E
i
: O(E) sau O(maxout_degree)
E'
i
: O(E)
spatiu: O(E + n)
4) Liste de adiacenta dubla
(i,j) , E, (i,j, e(i,j))
Operatii pe grafuri, traversari
La grafuri se folosesc operatii ntre elemente ((i,j) adiacente, etc), operatii de traversare pe grafuri. Se
numeste traversare pe graf o procedura de traversare, pentru un scop bine definit, prin toate nodurile.
Exista: traversari n adncime depth first
traversari n latime bmedth first
Proceduri:
dfs(G, i) // mark(1... n) initializat cu
// (00 ... 0) ext
{ // procesare()
procesare(i)
mark(i) = 1
for fiecare k V cu (i,k) E
| if mark(k) = 0
|_dfs(k)
}
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) = N
0
)) 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)
_________________________________________________________________________ 67
Structuri de date si algortitmi
_________________________________________________________________________
| | if mark(k) = 0
|_ |_ dfs(k)
}
2) Liste de adiacenta directa
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)
}
Arbori de acoperire pe grafuri
Fie G(V, E) un graf.
Se numeste arbore de acoperire un arbore T = (V, E') , cu E' E
Observatie: Algoritmii dfs, bfs produc arbori de acoperire.
_________________________________________________________________________ 68

You might also like