P. 1
Structuri de Date Si Algoritmi

Structuri de Date Si Algoritmi

|Views: 1,076|Likes:
Published by jack

More info:

Published by: jack on Nov 05, 2010
Copyright:Attribution Non-commercial

Availability:

Read on Scribd mobile: iPhone, iPad and Android.
download as DOC, PDF, TXT or read online from Scribd
See more
See less

05/15/2013

pdf

text

original

Sections

  • Curs 1
  • Curs 2
  • Curs 3
  • Curs 4
  • Curs 5
  • Curs 6
  • Curs 7
  • Curs 8
  • Curs 9
  • Curs 10
  • Curs 11

Structuri de date si algortitmi

_________________________________________________________________________
Facultatea de automatica si
calculatoare
_________________________________________________________________________ 1
Structuri de date si algortitmi
_________________________________________________________________________
STRUCTURI DE DATE SI ALGORITMI
Curs de Bârsan 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 b∈R, 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.
_________________________________________________________________________ 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.
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 câte ori este
nevoie.
_________________________________________________________________________ 4
Structuri de date si algortitmi
_________________________________________________________________________
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.
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 pâna 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 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:
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 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:
//////
a a a ... a
1 2 3
n
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):
//////
a a a ... a
1 2
i+1
a
i-1
...
Presupunând 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 stânga 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. Când 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 când 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 sfârsit)
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 ramân 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 sfârsit.
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 sfârsit
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 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
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 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.
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 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
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:
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 î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
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 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]
};
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 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])
}
Functia isEmpty(S) testeaza conditia stiva vida:
isEmpty(S)
{
_________________________________________________________________________ 14
Structuri de date si algortitmi
_________________________________________________________________________
return(S.sp==0)
}
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 având 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
î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.
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 încât la incrementarea indicatorilor head si tail, când 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" ramâne 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 amândoua.
Î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 atât head cât 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 încât 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
).
Facându-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[1÷k-1] sunt sortate, iar
elementul k va fi inserat, astfel încât, dupa aceasta inserare, primele elemente A[ ÷k] sa fie sortate.
Pentru a realiza inserarea elementului k în secventa A[1÷k-1], aceasta presupune:
• memorarea elementului intr-o varibila temporara;
• deplasarea tuturor elementelor din vectorul A[1÷k-1] care sunt mai mari decât A[k], cu o pozitie la
stânga (aceasta presupune o parcurgere de la dreapta la stânga);
• 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 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(n
2
) ÷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.
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 //restrâng
| //cautarea la partea stânga
|_ else low = mid+1 //restrâng 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 facând 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 ramâne 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 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:
_________________________________________________________________________ 25
Structuri de date si algortitmi
_________________________________________________________________________
(f(n))
c
= O(a
f(n)
)
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
_________________________________________________________________________ 26
Structuri de date si algortitmi
_________________________________________________________________________
Prin aceeasi tehnica se calculeaza suma:

·
⋅ −
n
i
n
1
1
2 ) 1 (
_________________________________________________________________________ 27
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, 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 (t
i
) 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 (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:
_________________________________________________________________________ 28
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
⋅ · · − ⋅ · − ⋅ ⋅ ⋅ · − ⋅ ⋅ · − ⋅ ·

Facând 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
Facând 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:
_________________________________________________________________________ 29
Structuri de date si algortitmi
_________________________________________________________________________

¹
'
¹
· ⋅ + ⋅
· ⋅ + ⋅

¹
'
¹
·
·
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
β α + ⋅ ·
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[low÷mid] si A[mid+1÷high]. 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
_________________________________________________________________________ 30
Structuri de date si algortitmi
_________________________________________________________________________
|¯ 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
(2·T(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).
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
) , (
) , l o g (
) , (
) (
l o g

· ⋅

·
Utilizând 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
_________________________________________________________________________ 31
Structuri de date si algortitmi
_________________________________________________________________________
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])
interchange (A[h], A[low])
return(h);
}
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).
_________________________________________________________________________ 32
Structuri de date si algortitmi
_________________________________________________________________________
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
Rezolvând 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 când pivotul este plasat în pozitia i:
i
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
) (
Dezvoltând,
∑ ∑ ∑
∑ ∑ ∑
· ·

·
· · ·

,
`

.
|
+ − · − ⋅
,
`

.
|
+ − ⋅
,
`

.
|
+ − ·
· − ⋅
,
`

.
|
+ − ⋅
,
`

.
|
+ − ⋅
,
`

.
|
· − + − + − ⋅
,
`

.
|
·
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
) (
(Facând 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 scazându-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
⋅ + + · + ⋅ ⋅ +
+ − − + · − + ⋅ ⋅ +
_________________________________________________________________________ 33
Structuri de date si algortitmi
_________________________________________________________________________
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
+
+
· +
+ +

· +
+
·
Facând 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
d x
1
2
1
2 ) (
2



,
`

.
|
⋅ 〈

,
`

.
|
⋅ 〈
·
n
i
n
i
x i
n F
este aria zonei de sub graficul functiei
x
x f
1
) ( · .
_________________________________________________________________________ 34
Structuri de date si algortitmi
_________________________________________________________________________
) l n ( ) ( l n 2 l n ) 1 ( ) ( ) (
1
) (
l n 2 ) ( 1 l n 2 l n 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.
_________________________________________________________________________ 35
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 vârfului 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 decât cele iterative. La prelucrarea unei liste,
daca primul element nu este vid, se prelucreaza acesta, urmând apoi ca restul listei sa fie considerata ca o
noua lista mai mica, etc.
De exemplu, algoritmul Quick_Sort:
Quick_Sort(A, low, high)
{
_________________________________________________________________________ 36
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 încât 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), î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:
_________________________________________________________________________ 37
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 ⇒câmpul data va contine atomul
respectiv.
Daca tag = 1 ⇒nodul va corespunde unei subliste ⇒câmpul 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) ÷câmpul "link" din nodul indicat de p;
Notam: tag(p) ÷câmpul "tag" din nodul indicat de p
data(p) ÷câmpul "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:
_________________________________________________________________________ 38
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.
_________________________________________________________________________ 39
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 întâi 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
_________________________________________________________________________ 40
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
)
}
_________________________________________________________________________ 41
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 = 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 î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}= 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.
_________________________________________________________________________ 42
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
Adâncimea arborelui : depth(T) = max { level (x) pentru x ∈ V
Exemplu:
A
/ | \
B C D
/ \ | / | \
E F G H I J
/ \ |
K L M
predecesor(E) = B
succesor(C) = G
E(D) = {H, I, J}
degree(D) = 3
degree(B) = 2
degree(F) = 0
degree(T) = 3
ancestors(L) = {A, B, E}
level(L) = 4 , level(B) = 2 , level(A) = 1
depth(T) = 4
Reprezentarea arborilor
Reprezentarea prin liste generalizate
Se considera ca nodurile terminale sunt elemente atomice, iar nodurile de grad ≥ 1 sunt subliste.
Deci, fie arborele de mai sus scris sub forma :
A( B (E (K, L), F), C (G), D (H (M), I, J)) cu reprezentarea:
_________________________________________________________________________ 43
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
_________________________________________________________________________ 44
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 când conteaza ordinea descendentilor, ea poate
surprinde structura diferita.
De exemplu:
structura x este diferita de structura x
/ | \ / | \
vid y vid y vid vid
Metodele de reprezentare expuse permit sa putem identifica legatura nod-descendent (succesor).
Dar, exista aplicatii în care este nevoie de legatura nod-predecesor. Asadar, pare utila reprezentarea
arborelui sub forma nodului (data, parent):
_________________________________________________________________________ 45
Structuri de date si algortitmi
_________________________________________________________________________
A
B C D
E F
0
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
Observatie: Arborele A este diferit de A
/ \ / \
B vid vid B
Structura de baza a unui arbore binar:
rad
/ \
/ \
/ \
/ \
/ \ / \
/ SAS \ / SAD \
/______ \ /_______\
SAS ÷subarborele stâng (binar)
SAD ÷subarborele drept (binar)
Definitii
_________________________________________________________________________ 46
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. Notând niv(i) numarul de noduri de pe nivelul i,
⇒ niv(n+1) ≤ 2·niv(n) ≤ 2⋅ 2
n-1
= 2
n
.
Lema 2
Numarul maxim de noduri ale arborelui binar de adâncime 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 adâncime h
Lema 3
Notam cu:
n
2
÷numarul de noduri de grad 2 din arborele binar;
_________________________________________________________________________ 47
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 câmpului 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 stâng (p →stg)
_________________________________________________________________________ 48
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 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 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.
_________________________________________________________________________ 49
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)
Considerând r ∈ V (radacina arborelui), Ts ÷subarborele stâng al radacinii si Td ÷subarborele
drept al radacinii, atunci structura acestui arbore este definita de urmatoarele proprietati:
1) ∀ un nod x ∈ Ts atunci key(data(x)) < key(data(r));
2) ∀ x ∈ Td atunci key(data(x)) > key(data(r));
3) Ts si Td sunt BST.
Observatii:
1) Consideram ca pe multimea k este definita o relatie de ordine (de exemplu lexico-grafica);
2) Pentru oricare nod din BST toate nodurile din subarborele stâng sunt mai mici decât radacina si toate
nodurile din subarborele drept sunt mai mari decât radacina.
Exemple: 15 15
/ \ / \
7 25 10 25
_________________________________________________________________________ 50
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)
_________________________________________________________________________ 51
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 facând apeluri de forma p= insert(p,a).
3) Delete:
delete(rad,k) // rad ÷ referinta la pointer la radacina
{ // k - cheia de cautare a atomului care trebuie sters de noi
if rad = 0 then
return // nodul cu cheia k nu se afla în arbore
else if key(data(rad)) > k then delete(lchild(rad),k)
else if key(data(rad)) < k then delete(rchild(rad),k)
else delete_root(rad)
}
Stergerea radacinii unui BST.:
1) rad ⇒arbore vid
2) a) rad sau b) rad ⇒ a) SAS sau b) SAD
/ \
SAS SAD
delete_root(rad) // rad ÷ referinta la pointer la radacina
{
if lchild(rad)=0 then
|¯ p= rchild(rad)
| ret_sp(rad)
|_ rad= p
else if rchild(rad)= 0 then
|¯ p= lchild(rad)
| ret_sp(rad)
|_ rad= p
else |¯ p= remove_greatest(lchild(rad))
| lchild(p)= lchild(rad)
| rchild(p)= rchild(rad)
| ret_sp(rad)
|_ rad= p
}
15
/ \
7 25
/ \ / \
2 13 17 40
/ /
9 27
/ \
26 33
Detasarea din structura arborelui BST a celui mai mare nod ÷ (remove_greatest):
Pentru a gasi cel mai mare nod dintr-un arbore binar de cautare, se înainteaza în adâncime pe
ramura dreapta pâna se gaseste primul nod care nu are descendent dreapta. Acesta va fi cel mai mare.
_________________________________________________________________________ 52
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
stâng (SAS).
Caz3: rad ⇒functia returneaza pointer la cel mai mare nod din SAD, iar rezultatul va fi SAS ÷arborele
care este fomat din radacina,SAS si SAD ÷cel mai mare nod.
remove_greatest(rad) //rad -referinta la pointer la
//radacina: un pointer la radacina de poate
//fi modificat de catre functie
{
if rchild (rad)= 0 then |¯ p= rad
| rad= lchild (rad)
|_ return(p)
else return (remove_greatest (rchild(rad)))
}
Observatie:
Functia remove_greatest modifica arborele indicat de parametru, în sensul eliminarii nodului cel mai mare,
si întoarce pointer la nodul eliminat.
Demonstratia eficientei (complexitate)
Complexitatea tuturor functiilor scrise depinde de adâncimea arborelui. Î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 decât 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)
_________________________________________________________________________ 53
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 stâng 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 când prima
valoare inserata (a
1
) este mai mare decât 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
adâncimile SAS si SAD în modul este ≤ 1.
Exemple:
a a
/ \ / \
b c b c
/ \ / \ / \ \
d e f g d e f
/ \
g h
arbore binar arbore binar
_________________________________________________________________________ 54
Structuri de date si algortitmi
_________________________________________________________________________
complet echilibrat echilibrat
Adâncimea unui arbore echilibrat cu n noduri este O(ln n).
Se completeaza operatiile insert si delete cu niste prelucrari care sa pastreze proprietatile de arbore
binar echilibrat pentru arborele binar de cautare. Arborele binar echilibrat este un BST echilibrat,
proprietatea de echilibrare fiind conservata de insert si delete. Efortul, în plus, pentru completarea
operatiilor insert si delete nu schimba complexitatea arborelui binar echilibrat.
Transformarea structurii arborelui dupa inserare pentru a conserva proprietatea de arbore binar
echilibrat
Modificarile care se vor face se vor numi rotatii.
Caz 1: Fie arborele echilibrat
A
/ \
B 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. 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 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 stânga
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.
Ridicând pe B în sus, si notând cu // legaturile neschimbate, rezulta:
B
// \
// A
T
1
/ \\
_________________________________________________________________________ 55
Structuri de date si algortitmi
_________________________________________________________________________
____________T
2
_T
3
_________ 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 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 adâncimea. 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 adâncime cu arborele initial. Rotatiile sunt duble, în sensul
ca
s-a facut o rotatie simpla B la stânga cu o rotatie simpla A la dreapta.
Operatiile care trebuiesc facute în cazul 1 (rotatie simpla la dreapta):
r ÷pointer la nodul radacina (A)
a ÷pointer la radacina
p = lchild(r) b = lchild(a)
lchild(r) = rchild(p) lchild(a) = rchild(b)
rchild(p) = r rchild(b) = a
r = p a = b
Operatiile care trebuiesc facute în cazul 2 (rotatie dubla)
b = lchild(a)
c = rchild(b)
lchild(a) = rchild(c)
rchild(b) = lchild(c)
rchild(c) = a
lchild(c) = b
a = c // se schimba radacina arborelui.
_________________________________________________________________________ 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
S-a notat pentru fiecare nod bf ÷balance factor (factor de dezechilibrare):
bf(nod) = depth (lchild (nod)) ÷ depth (rchild (nod))
adica este diferenta dintre adâncimea subarborelui stâng si adâncimea subarborelui drept.
Calculam factorii de balansare dupa inserare.
Observatie: Pentru nodul terminal s-a schimbat adâncimea si factorul de balansare; bf = -2 dupa inserare
devine nod dezechilibrat. Trebuie aplicata, deci, echilibrarea.
Definitie:
Se numeste nod critic primul nod cu bf ≠ 0 întâlnit la o parcurgere de jos în sus a ramurii care leaga nodul
inserat de radacina.
Observatie: Toate nodurile din ramura care sunt pe nivele inferioare nodului critic vor capata bf = 1 sau
bf = -1.
La nodul critic exista doua situatii:
1. Nodul critic va fi perfect balansat (bf = 0), daca dezechilibrul creat de nodul inserat anuleaza
dezechilibrul initial al nodului. Î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.
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
_________________________________________________________________________ 57
Structuri de date si algortitmi
_________________________________________________________________________
operatiilor de rotatie si inserare. Operatia de stergere într-un AVL implica mai multe rotatii, ea nu se
studiaza în acest curs.
Exemplu: Se da arborele cu urmatoarea structura:
55
/ \
20 80
/ \ \
10 35 90
/ /
5 30
Sa se insereze nodurile 15, apoi 7 si sa se echilibreze arborele.
Inseram prima valoare 15. Comparam mai întâi cu 55 : e în stânga lui, s.a.m.d. pâna când gasim
locul ei în pozitia de mai jos. Pentru a doua valoare de inserat, 7, nodul critic este 55. El este dezechilibrat
stânga. Deci, va fi echilibrat la valoarea 2. Este necesara aplicarea unei rotatii asupra radacinii.
55
/ \
20 80
/ \ \
10 35 90
/ \ /
5 15 30
\
7
Facem o identificare cu unul din desenele de echilibrare prezentate în cursul anterior. Se asociaza
nodurile: 55 →A
20 →B etc.

20 ----------> noduri implicate
/ \ în
10 55 ------> rotatie
/ \ / \
5 15 35 80
\ / \
7 30 90
Î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 |
_________________________________________________________________________ 58
Structuri de date si algortitmi
_________________________________________________________________________
A are bf = -1 | exceptie facând 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 facând 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 stânga la dreapta, nodurile nivelului 2 de la stânga la dreapta, etc,
despartite de linii duble.
Lema
Daca în vectorul V un nod x este reprezentat prin elementul de vector V(i), atunci:
1. left_child(x) este reprezentat în elementul de vector V [2·i];
2. right_child(x) este reprezentat în elementul de vector V [2·i + 1];
3. parent(x) este reprezentat în elementul de vector V [[i/2]]
cu observatia ca paranteza patrata interioara este partea întrega.
Demonstratie:
Se face inductie dupa i:
Pentru i = 1 ⇒V[1] ÷radacina
⇒V[2] ÷left_child(rad)
⇒V[3] ÷right_child(rad)
Presupunem adevarata lema pentru elementul V[i] ⇒V[2i] ÷left_child
V[2i + 1] ÷right_child
Elementul V[i + 1] este nodul urmator (de pe acelsi nivel sau de pe nivelul urmator) într-o parcurgere
binara.
V[i + 1] ⇒ left_child în V[2i + 2] = V[2(i + 1)]
right_child în V[2i + 3] = V[2(i + 1) + 1]
Daca avem un arbore binar care nu este complet, reprezentarea lui implicita se obtine
completându-se structura arborelui cu noduri fictive pâna la obtinerea unui arbore binar complet.
Arbori heap (heap ÷ gramada)
Definitie:
Se numeste arbore heap un arbore binar T = (V, E) cu urmatoarele proprietati:
_________________________________________________________________________ 59
Structuri de date si algortitmi
_________________________________________________________________________
1) ∃ functia key : V → R care asociaza fiecarui nod o cheie.
2) ∀ un nod v ∈ V cu degree(v) > 0 (nu este nod terminal), atunci:
key(v) > key(left_child(v)), daca ∃ left_child(v)
key(v) > key(right_child(v)), daca ∃ right_child(v)
(Pentru fiecare nod din arbore, cheia nodului este mai mare decât cheile descendentilor).
Exemplu: 99
/ \
50 30
/ \ / \
45 20 25 23
Observatie: De obicei functia cheie reprezinta selectia unui subcâmp din câmpul de date memorate în nod.
Aplicatii ale arborilor heap
• Coada cu prioritate;
• Algoritmul Heap_Sort
Arborii heap nu se studiaza complet în acest curs.
_________________________________________________________________________ 60
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 când utilizeaza
reprezentarea implicita. Consideram, în continuare, arbori heap în reprezentare implicita.
Exemplu: un arbore cu ultimul nivel având toate nodurile aliniate la stânga:
• Inseram valoarea 42 ⇒se adauga nodul la un nivel incomplet;
• În reprezentarea implicita se adauga nodul la sfârsit.
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:
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
_________________________________________________________________________ 62
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 pîna când structura heap-ului este realizata.
parent
conditia de heap: / \
lchild rchild
parent = max(parent, lchild, rchild).
Exista trei cazuri:
1. conditia este îndeplinita deodata;
2. max este în stânga ⇒retrogradarea se face în stânga;
3. max este în dreapta ⇒retrogradarea se face în dreapta.
remove(V, N)
{
a= V[1]
N[1]= V[N]
N= N-1
parent= 1
child= 2
while child ≤ N do
|¯ if child+1 ≤ N and key(V[child+1]) >
| > key(V[child]) then
| child= child+1
| if key(V[parent]) < key(V[child]) then
| |¯ interchange(V[parent], V[child])
| | parent= child
| |_ child= 2*parent
| else break
|_ return(a)
}
Complexitatea celor doua operatii insert si remove:
Î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 adâncimea
arborelui. Daca N este numarul de noduri din arbore, 2
k
≤ N ≤ 2
k+1
, si adâncimea 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
adâncime k adâncime k+1
_________________________________________________________________________ 63
Structuri de date si algortitmi
_________________________________________________________________________
k ≤ log
2
N < k + 1 ⇒ adâncimea 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 decât 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 adaugând un nod la ultimul nivel. Pentru nivelul 2 sunt doua
noduri. La inserarea lor se va face cel mult o retrogradare (comparatie).
Pe ultimul exemplu de arbore, avem:
nivelul 2 : 2 noduri 1 comparatie
nivelul : 4 noduri 2 comparatii
nivelul 4 : 8 noduri 3 comparatii
--------------------------------------------------
nivelul i : 2
i-1
noduri i-1 comparatii
_________________________________________________________________________ 64
Structuri de date si algortitmi
_________________________________________________________________________
Considerând 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
Facându-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 stânga). Cele mai multe noduri sunt la baza, doar
nodurile din vârf parcurg drumul cel mai lung.
noduri terminale
I
II
i
Elementele V[i+1,N] îndeplinesc conditia de structura a heap-ului:
_________________________________________________________________________ 65
Structuri de date si algortitmi
_________________________________________________________________________
∀ j >i avem: V[j] > V[2*j] , daca 2*j ≤ N
V[j] > V[2*j +1] , daca 2*j + 1 ≤ N
Algoritmul consta în adaugarea elementului V[i] la structura heap-ului. El va fi retrogradat la baza heap-
ului (prelucrare prin retrogradare):
retrogradare(V, N, i)
{
parent = i
child = 2*i // fiu stânga al lui i
while child ≤ N do
|¯ if child+1 ≤ N and
| key(V[child+1]) > key(V[child]) then
| child = child+1
| if key(V[parent]) < key(V[child]) then
| |¯ interchange(V[parent], V[child])
| | parent = child
| |_ child = 2*parent
|_ else break
}
Î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 − ⋅ ·
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
_________________________________________________________________________ 66
Structuri de date si algortitmi
_________________________________________________________________________
Rezulta:
1 ) 1 2 ( 3 1 2 3 ) (
2
− − − ⋅ 〈 − − ⋅ ·

k k n T
k k
1 ) 1 ( l o g 3 ) (
2
− + − ⋅ 〈 n n n T
-------
termen dominant
Rezulta complexitatea este O(n). Comparând cu varianta anterioara, în aceasta varianta (cu heap-ul
la baza) am câstigat un ordin de complexitate. Rezulta, complexitatea algoritmului Heap_Sort este
determinata de functiile remove ce nu pot fi aduse la mai putin de complexitate O(log n). Rezulta:
Heap_Sort = O(n) + O(n·log n)
---------------
termen ce determina complexitatea
Rezulta complexitatea alg. Heap_Sort = O(n·log n)
_________________________________________________________________________ 67
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 încât V'⊂ V,
E' ≤ (V'× V') ∩ E
Reprezentari
1) Matrice de adiacenta
Fie G = (V, E) ,│V│ = n. Se determina matricea de adiacenta
_________________________________________________________________________ 68
Structuri de date si algortitmi
_________________________________________________________________________
astfel: M M i j
i j E
i j E
n n ×
·


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


]
]
]
]
]
]
]
]

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 continând adresa unei liste dupa regula urmatoare: L(i)
da adresa listei înlantuite care contine toti succesorii lui i.
L(i) → E
i
Exemplu:
_________________________________________________________________________ 69
Structuri de date si algortitmi
_________________________________________________________________________
0
0
0
0
3
5
1
2
2
3
0
1
2
3
4
5
3) Liste de adiacente inverse
(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 adâncime — 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
_________________________________________________________________________ 70
Structuri de date si algortitmi
_________________________________________________________________________
// 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)
| |¯ 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.
_________________________________________________________________________ 71

Structuri de date si algortitmi _________________________________________________________________________

STRUCTURI DE DATE SI ALGORITMI

Curs de Bârsan M.
BIBLIOGRAFIE
1. 2. 3. 4. 5. 6. 7. E. Horowitz, S. Sahni  "Fundamentals of Computer Algorithms" - 1985 E. Horowitz, S. Sahni  "Fundamentals of Data Structurs" Livovschi Georgescu  "Sinteza si analiza algoritmilor" U. Mandber  "Introduction to Algorithms" S. Baase  "Computer Algorithms" M. Shapiro  "Algorithms from P to NP" 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 b∈ R, 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.

_________________________________________________________________________ 3

etc. • se face analiza specificatiilor. for. • identificatori.Structuri de date si algortitmi _________________________________________________________________________ Masina TURING 1. Programul rezultat se compara cu cerintele. • codarea (scrierea programului).bazat pe predicate de ordin I. ML. MODELUL logic . si daca nu corespunde. Modelul initial:waterfall (cascada): Requirmens Design Testing Implement Etapele de realizare ale unui produs software: O prima faza: • se pleaca de la cerinte. do/while. • operatori relationali. iar programarea este în acest caz programare functionala. • proiectarea structurilor de date. În cele ce urmeaza ne vom limita la modelul Von Newman.Iar programarea se numeste programare logica. • se obtin specificatii. • structuri de control a executiei: if/else. se reia ciclul ori de câte ori este nevoie. while. MODELUL functional . • operatori numerici obisnuiti. etc). A treia faza: • testarea. Analiza performantelor algoritmului Analiza performantelor (estimarea algoritmului) se impune înca înainte de scrierea programelor. Un exemplu de limbaj în acest model este PROLOG. Limbajele în acest model sunt LISP. etc. 2. Etapele de realizare a unui produs software (software engineering) Aceasta stiinta pune în evidenta metodologii clare pentru modele.calcul. Asadar limbajul C++ se constituie din: • variabile. • analiza performantelor.bazat pe teoria λ . A doua faza (DESIGN): • proiectare de ansamblu (se sparge modulul în submodule. • proiectarea algoritmilor. MIRANDA. • constante. _________________________________________________________________________ 4 . Ultima faza: • implementarea.

nestiind înca limbajul care va fi folosit si calitatea programului ce se va obtine.rezulta ca timpul estimat pentru executia unui algoritm este proportional cu numarul instructiunilor executate de acel algoritm. numarul de instructiuni al algoritmului respectiv. si ca fiecare instructiune pseudocod consuma acelasi timp de executie. pentru un n dat. f este ordinul de complexitate a lui g daca ∃ N ∈ N si const. _________________________________________________________________________ 5 . g : N → N cu f = O(g) sau f(n) = O(g(n)).Structuri de date si algortitmi _________________________________________________________________________ Analiza performantelor presupune  renuntând la acuratete  estimarea timpului de lucru si a spatiului de stocare. f ( n) = c ⋅ log 2 n Estimarea se face pâna la o constanta c. Spatiul de memorare suplimentar Definitie: Date doua functii f. c > 0 astfel incat ∀n > N 0 = f ( n ) < c ⋅ g ( n ) . Presupunând ca modelul RAM de masina pe care lucram executa instructiuni pseudocod. • • 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.

de exemplu.reprezentare |--. si definirea operatiilor acceptate. Timpul de accesare al elementului numar. pentru care este definita o ordine: a1 a 2 a 3 a 4 a 5 .înseamna ca tipul de date este abstract.reprezentare: pe doi octeti: cod | complementar |---operatii acceptate: +. • stergere: scoaterea unui element dintr-o anumita pozitie.Structuri de date si algortitmi _________________________________________________________________________ Curs 2 Structuri de date elementare O structura de date presupune un mod de organizare a datelor (în tablouri. • consultare: accesul asupra fiecarui element din lista. Liste O lista este o multime de obiecte. putând accesa direct orice element.operatii acceptate De exemplu.. *. ci doar operatiile acceptate. numite atomi. etc. • parcurgere. Structuri de date Tablouri Tabloul este o colectie de date în care fiecare element poate fi identificat pe baza unui index. o structura de date reprezinta o colectie organizata de date si un set de operatii definite. a n Operatiile principale care se pot se face în cadrul listei: • inserare: introducerea unui nou element într-o anumita pozitie. -. etc).. structuri. fiind acelasi cu timpul de accesare al elementului n. colectia asigurând timp de acces constant pentru fiecare element. Si notiunea de tip de date presupune: |--. Deci. |. pentru tipul int: |--. &. Daca pentru un tip de date nu intereseaza reprezentarea. Prin reprezentarea tabloului se intelege plasarea elementelor în locatii succesive de memorie: Locatiile de memorie pot fi numerotate. /. Tipuri speciale de liste _________________________________________________________________________ 6 .

nu de continutul elementelor.... inserarea s-ar putea figura astfel: _________________________________________________________________________ 7 . 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 Implementari de liste O lista poate fi realizata ca: del  lista ordonata sau  lista înlantuita Lista ordonata tine cont de o ordine a pozitiilor elementelor listei. iar cea care scoate obiecte din stiva se numeste pop.. 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). Partea din fata a cozii (a primului element) se numeste front. iar stergerea se face de la celalalt capat al cozii (de la început). Stiva se poate asemana cu un recipient în care se pun si se pot scoate diferite obiecte. Capatul accesibil pentru stiva se numeste vârful stivei: Asadar: push  insereaza un element în vârful stivei. top(S)  citeste vârful stivei. Operatia care pune obiectele în stiva se numeste push. push pop Cozi O coada este o lista în care inserarea se face la un capat (la sfârsit). stergere si consultare se efectueaza asupra unui capat al listei. ////// Presupunând acum hasurat corpul de elemente din lista si nehasurata zona de memorie libera... top  consulta (citeste) elementul din vârful stivei. ai-1 a ai+1 . Deplasarea se face înspre zona de memorie libera (cea hasurata)  presupunem ca dorim sa inseram pe a în pozitia i): a1 a2 .Structuri de date si algortitmi _________________________________________________________________________ Stive O stiva este o lista în care operatiile de inserare. pop  sterge un element din vârful stivei.

Element* x data(x) ---------------. Informatiei succesor(a(i)) i se potriveste notiunea de pointer (identificator). ////////////// ////////// ////////// ///////////////////////////// Liste înlantuite Într-o lista înlantuita. În programe vom utiliza urmatoarele notatii: x  adresa unui element din lista. Când informatiile de înlantuire sunt pointeri. Un element de lista va fi o astfel de structura: struct Element { Atom data. iar informatia succesor(a(i)) ne permite sa identificam un nou element de lista înlantuita. Element* link. pointer-ul fiind o variabila care pastreaza o adresa din memorie. Ordinea din memorie a elementelor listei nu conteaza. 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 când este vorba de pointer).Structuri de date si algortitmi _________________________________________________________________________ ///////////////////////////// ////////// ///////////////// /////////////////////////////////// Stergerea: deplasarea cu o pozitie la stânga din acel punct. iar 0 este pointer-ul nul (NULL) cu valoare zero. succesor(a(i))) unde a(i) este atomul listei înlantuite. data(x)  atomul memorat în elementul de lista indicat de x. deci un pointer. Informatia care indica primul element al listei se numeste "capul" listei. }. ordinea din memorie nu mai corespunde cu ordinea din lista. Fiecare element al listei înlantuite va avea urmatoarea structura: (a(i) . link(x)  informatia de legatura memorata în elementul de lista indicat de x.x → data _________________________________________________________________________ 8 . El indica pozitia elementului urmator. La implementarea listei înlantuite concentrarea se face la fluxul instructiunilor. putem utiliza urmatoarea reprezentare: a1 cap de lista a2 a3 Capul de lista este un pointer separat care duce la primul element din lista. Se va scrie: tipul lui x ---------------. adica adresa elementului urmator. ret_sp(x)  elibereza memoria ocupata de elementul de lista indicat de x (din momentul respectiv acolo se poate memora altceva). nu la declaratiile de variabile.

A doua atribuire: l = x a2 Observatie: daca lista este vida. Operatii primitive pentru liste înlantuite 1.y = new Element ret_sp() ---------------. l are valoarea 0 (capatul listei) iar atribuirile de mai sus ramân valabile: 1 x 1 0 a0 0 a0 x b) Inserarea la mijloc 0 _________________________________________________________________________ 9 . Inserarea Inserarea se face:  în fata.x → link y = get_sp() ---------------.Prima atribuire: link(x) = l 2 .Structuri de date si algortitmi _________________________________________________________________________ link(x) ---------------.delete x Deoarece lucram cu conceptul de lista vom face declaratia : typedef Element* Lista. sau  în interior (la mijloc ori la sfârsit) a) Inserarea în fata a1 1 1 2 a0 x 1 . Un pointer la un element de lista considerat aproximeaza lista ce porneste cu elementul indicat.

a) Stergerea (stergerea din fata): 1 a1 a2 2 1 .Prima atribuire: p = l 2 . _________________________________________________________________________ 10 .Structuri de date si algortitmi _________________________________________________________________________ ai-1 2 y a ai 1 ai+1 x Analog pentru inserarea la sfârsit.A doua atribuire: link(y) = x 2.Prima atribuire: link(x) = link(y) 2 .A doua atribuire: l = link(l) 3 .ret_sp(p) Sau. 1 . stergerea din fata s-ar mai putea reprezenta astfel: Situatia initiala: cap 0 Situatia finala: P 1 cap 0 2 (1) (2) p = cap. cap = cap → link.

Observatii: Atunci când q indica penultimul element dintr-o lista. q → link = p → link. de aceea adresa lui trebuie salvata (sa zicem în variabila pointer p). 2. // sau q → link = q → link → link. atribuirile de mai sus functioneaza corect si sterg ultimul element din lista. _________________________________________________________________________ 11 . delete p.Structuri de date si algortitmi _________________________________________________________________________ delete p . // Elibereaza zona de memorie Elementul care a fost izolat de lista trebuie sa fie procesat în continuare.b) Stergerea de la mijloc sau de la sfârsit Varibila q va indica elementul din fata celui care va fi sters. Nu se poate face stergerea elementului indicat de q fara parcurgerea listei de la capat. cel putin pentru a fi eliberata zona de memorie pe care o ocupa. Situatia initiala: q Situatia finala: q (1) (2) p = q → link.

link(p)=link(q). Inserarea în fata unui element specificat Functia înscrie un element în fata altui element dintr-o lista: insert (l. o functie de forma: _________________________________________________________________________ 12 { . data(p)=a. link(q)=p. b) // l  lista (pointer la primul element) // a  valoarea atomului de inserat // b  valoarea atomului în fata caruia se insereaza p=get_sp(). Deci vom avea în pseudocod. while ((link(q)!=0)and (data(link(q)!=b)) do q=link(q). a.Structuri de date si algortitmi _________________________________________________________________________ Curs 3 Operatia de inserare într-o lista înlantuita Presupune adaugarea unui element într-o pozitie specificata în lista. } } Operatia de stergere dintr-o lista înlantuita Operatia delete sterge un atom dintr-o lista. l=p. 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. if (l==0) or (data(l)==b) then { link(p)=l. } else { q=l. • Situatia în care pozitia de inserat poate fi data implicit prin valoarea atomului de inserat. • Situatia în care pozitia de inserat este data prin valoarea atomului dupa care sau înainte de care se face inserarea.

Operatiile de acces la stiva (push . 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). ATOM) → STACK Operatia PUSH primeste ca parametri o stiva si un obiect si produce stiva modificata prin adaugarea obiectului în stiva.Structuri de date si algortitmi _________________________________________________________________________ delete(l. • POP(STACK) → STACK. 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. 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 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. ci o secventa de instructiuni): Fie p pointer-ul care indica pe rând fiecare element al listei. ATOM _________________________________________________________________________ 13 . O parcurgere a listei presupune o prelucrare efectuata asupra fiecarui element din lista (asadar nu o functie. creeaza o stiva care pentru început este vida (nu contine nici un obiect). O stiva este un caz particular de lista.scoate un element din stiva) sunt create astfel încât pop scoate din stiva elementul introdus cel mai recent.adauga un element in stiva si pop . • PUSH(STACK. si anume este o lista pentru care operatiile de acces (inserare. stergere. accesare element) se efectueaza la un singur capat al listei.

sp-1 } Observatie: Se obisnuieste ca pe lânga stergerea elementului.vect si S.Structuri de date si algortitmi _________________________________________________________________________ Operatia POP primeste ca parametri o stiva pe care o modifica scotând un obiect.sp  indicele vârfului stivei Elementele sunt memorate asadar unul dupa altul în vectori.vect[S. Facem notatiile: S  stiva S. Conditia de stiva vida este: S.sp >=DIMMAX then eroare("Stiva plina") else |¯ S.a) { if S.sp]) } Functia isEmpty(S) testeaza conditia stiva vida: isEmpty(S) { _________________________________________________________________________ 14 .sp]=a //atomul este pus pe prima //pozitie } Functia pop scoate un element din stiva: pop(S) { if S. functia pop sa returneze elementul scos din lista.sp=S.vect[S. De asemenea produce ca rezultat obiectul scos din stiva.sp+1 |_ S. Atom vect [DIMMAX] }. Zona de memorat trebuie sa contina aceste doua informatii: S. • TOP(STACK) → ATOM Operatia TOP întoarce ca rezultat obiectul din vârful stivei pe care o primeste ca parametru.sp grupate într-o structura: struct Stack { int sp.sp=0 then eroare ("Stiva vida") else S.sp=0 then eroare("Stiva vida") else return(S. nefiind neaparat în ordine crescatoare.vect  vectorul în care se reprezinta elementele stivei S S. top(S) { if S.sp=S. • ISEMPTY(STACK) → boolean Operatia ISEMPTY este folosita pentru a testa daca stiva este vida.sp=0 Se scrie: push(S.

iar functiile care implementeaza operatiile de acces vor avea aceleasi prototipuri cu cele date mai sus. Fie S pointer-ul la primul element din înlantuire. struct Element { Atom data.a) { p=get_sp() data(p)=a link(p)=S S=p } pop(S) { if S=0 then eroare("Stiva vida") else |¯ p=S. Element* link. Deci. se echivaleaza tipul Stack cu typedef Element* Stack.Structuri de date si algortitmi _________________________________________________________________________ return(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.//legatura }. 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. vom echivala tipul Stack cu tipul "pointer la element de lista". ele având si operatiile imediate. typedef Element* Stack. _________________________________________________________________________ 15 . deci.sp==0) } Stive înlantuite O stiva poate fi implementata ca o lista înlantuita pentru care operatiile de acces se fac numai asupra primului element din lista. iar conditia de stiva vida este S=0 : push(S.

Conditia coada vida este C. Primul element care urmeaza sa fie scos va fi întotdeauna în prima pozitie. Returneaza atomul scos. Initial indicatorii vor fi initializati astfel încât sa indice ambii primul element din vector. GET(Coada) → Coada. fata de începutul vectorului. Conditia "coada vida" este echivalenta cu: head = tail. Atom) → Coada Adauga un element la coada. indicatorul head pierzându-si utilitatea. Facem notatiile: C  coada C.head=C. Operatia PUT înseamna: . coada PUT GET ultimul • • Pricipalele operatii de acces sunt: PUT(Coada.incrementeaza tail.întoarce V[head].tail. primul head tail O coada poate fi organizata pe un spatiu de memorare de tip tablou (vector). Operatia GET înseamna: .V[tail] primeste Atomul adaugat. Sunt necesari doi indicatori: head  indica: primul element care urmeaza sa fie scos. Functia put pune în coada C un atom a: put(C.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.a) { _________________________________________________________________________ 16 .incrementeaza head Se observa ca adaugari si stergeri repetate în coada deplaseaza continutul cozii la dreapta. Dezavantajul acestei solutii consta în faptul ca operatia GET necesita o parcurgere a continutului cozii.tail  indicele (pozitia) în care va fi memorat urmatorul element adaugat la coada. .vect  vectorul în care sunt memorate elementele cozii C. tail  indica: locul unde va fi pus urmatorul element adaugat la coada. Pentru a evita acest lucru ar trebui ca operatia GET sa deplaseze la stânga continutul cozii cu o pozitie. .head  indicele elementului ce va fi scos din coada la urmatoarea operatie get C. Atom Scoate un Atom din coada.

tail=C.head=C. } unde DIMVECTOR este dimensiunea vectorului în care se memoreaza elementele cozii.tail then eroare("Coada vida") else |¯ C.vect [C.vect [C.tail]=a |_ C. Continutul cozii va arata asa: 1 2 3 4 5 6 7 8 head tail sau asa: _________________________________________________________________________ 17 . când acestia ating ultima pozitie din vector sa se continue cu prima pozitie din vector.head=C. else return 0.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 încât la incrementarea indicatorilor head si tail.head+1 |_ return C.head-1].head==C. } isEmpty(C) { return(C.Structuri de date si algortitmi _________________________________________________________________________ if C.tail+1 } Functia get scoate un element din coada si-l returneaza: get(C) { if C.tail>DIMMAX then eroare("Coada plina") else |¯ C. Functia urmatoare poate realiza aceasta cerinta: int nextPoz(int index) { if (index<DIMVECTOR-1) return index+1.

a) { if C.head Iar.tail) le contine pe amândoua.tail) then eroare("Coada plina") _________________________________________________________________________ 18 .tail=DIMMAX b) C. conditia C.head=inc(C. "Coada plina" se realizeaza în 2 situatii: a) C. În cazul cozilor circulare se modifica doar operatiile put si get: put(C.head=inc(C.Structuri de date si algortitmi _________________________________________________________________________ 5 6 7 8 1 2 3 4 head tail Conditia "coada vida" ramâne 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.head=1 si C. Observatie: În coada circulara de dimensiune DIMMAX pot fi memorate DIMMAX elemente.tail+1=C.

head) |_ return(a) } _________________________________________________________________________ 19 .tail]=a |_ C.head] | C.head=C.vect[C.tail) } get(C) { if C.tail then eroare("Coada vida") else |¯ a=C.vect [C.head= inc (C.Structuri de date si algortitmi _________________________________________________________________________ else |¯ C.tail=inc(C.

nil head tail • • 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.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  pointer la primul element din coada. _________________________________________________________________________ 20 . adaugarea unui element în coada modifica numai tail iar stergerea unui element numai head.tail  pointer la ultimul element din coada. Conditia de coada vida este head=0. C  coada. Facem notatiile : C. C. Într-un mod special trebuie sa fie tratate cazurile: • adaugare într-o coada vida: Initial: head=tail=nil Final: Coada cu un element: nil head tail • stergere dintr-o coada cu un element: Initial: Coada cu un element Final: head=tail=nil În aceste cazuri se modifica atât head cât si tail.

a) { p= get_sp(). în pozitia fata: put(C.head=0 then eroare("Coada vida") else return data(C. |¯ C. data(p)=a.tail= p _________________________________________________________________________ 21 .tail)= p |_ C.Structuri de date si algortitmi _________________________________________________________________________ Operatiile cozii înlantuite Functia put insereaza un element în coada.head) | ret_sp(p) |_ return(a) } Functia front returneaza elementul din fata cozii.head=0) } Exista aici un element de redundanta: ar fi convenabil sa nu mai avem spatiu suplimentar de memorare. De aceea apar utile cozile înlantuite circulare.head= p |_ C. fara a-l scoate din coada.tail= p |¯ link(C. if C.head= link(C.head | C.a) { if C. link(p)= 0.head) } isEmpty(C) { return(C.head= 0 then eroare("Coada goala") else |¯ a= data(C. front(C) { if C.head) | p= C. ci.head=0 then else } Functia get scoate un element din pozitia fata: get(C. sa avem un singur pointer ca sa putem manevra coada.

_________________________________________________________________________ 22 .a) { p= get_sp() data(p)=a if C=0 then else } 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) isEmpty(C)  returneaza data(link(C))  retuneaza conditia C=0. |¯ C= p |_ link(C)= p |¯ link(p)= link(C) | link(C)= p |_ C= p Complexitatea algoritmilor La evaluarea (estimarea) algoritmilor se pune în evidenta necesarul de timp si de spatiu de memorare al lui. sunt: put(C..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: ultimul .... tail primul Fie: C  pointer la primul () element din coada link(C)  pointer la ultimul() element din coada Operatiile de plasare si de scoatere din coada.

de forma: f(n) = ak ⋅ nk + ak-1⋅ nk-1 + . _________________________________________________________________________ 23 . 3. pentru compararea algoritmului. nu coeficientul termenului de grad maxim.. este o functie polinomiala de gradul I... Facându-se majorari în membrul drept. comportarea medie. + a1 ⋅ n + a0. Si în ceea ce priveste timpul. Putem spune ca T(n) = O(n). se studiaza configuratia cea mai defavorabila a algoritmului.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. obtinem rezultatul de mai sus: f(n) =  k ⋅ nk +  k-1⋅ nk-1 + . Punctul 3 presupune probabilitatea de aparitie a diferitelor configuratii de date la intrare. si ordinul O exprima viteza de variatie a functiei. Exemplu: Calcularea maximului unui sir maxsir(A. Lema 1 Daca f este o functie polinomiala de grad k. cu n = 1. Punctul 1 este cel mai studiat si este folosit. f(n) < c ⋅ n 0 Concluzie: f = O(nk). Definitie Fie f : N → N si g : N → N doua functii.Structuri de date si algortitmi _________________________________________________________________________ Studierea complexitatii presupune analiza completa în cadrul algoritmului a urmatoarelor 3 puncte de vedere: 1. + 1 ⋅ n + 0 < nk·⋅ ( k +  k-1 +  0 < nk ⋅ c pentru ∀ n > 1 ⇒ a a a a a a a ) k. configuratia de date cea mai defavorabila (cazurile degenerate). 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) < c⋅ g(n) Observatie: f : N → N este o functie f(n) cu n  dimensiunea datelor de intrare. Complexitatea unui algoritm se noteaza cu: O(f(n)). f(n) reprezinta timpul de lucru al algoritmului exprimat în "pasi".. 2. Iar la numararea pasilor ne concentram asupra numarului buclelor. atunci f = O(nk). Conteaza doar Ordinul polinomului. functie de argument. configuratia de date cea mai favorabila. nu asupra pasilor din interiorul buclei. de obicei. 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).

j] } Rezulta complexitatea O(n3). + n . aceasta presupune: • memorarea elementului intr-o varibila temporara. astfel încât. n ∑ i = O ( n2 ) i =1 Exemplu: Înmultirea a doua matrici prod_mat(A. iar elementul k va fi inserat.1) + (1 + 2 + 3+ .k] * B[k.B.n) { for k= 2 to n do |¯temp = A[k] |i=k-1.1)/2 Rezulta complexitatea: T(n) = O(n2)  functie polinomiala de gradul II. |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.j] + A[i. elementele A[1÷k-1] sunt sortate. de ordin n. iar mijlocul virtual al vectorului.. mid (de la middle). adica sirul este ordonat descrescator. Complexitate: O(n) insertion_sort(A. cu o pozitie la stânga (aceasta presupune o parcurgere de la dreapta la stânga). Pentru a realiza inserarea elementului k în secventa A[1÷k-1]. Limita inferioara se numeste low.j] = 0 |for k = 1 to n do |_ C[i. Bucla cea mai interioara ne da complexitatea algoritmului. Exprimarea timpului de lucru: T(n) = 3·(n . dupa aceasta inserare.j] = C[i.C. primele elemente A[ ÷k] sa fie sortate. • plasarea lui A[k] în locul ultimului element deplasat.n) { for i = 1 to n do for j = 1 to n do |¯ C[i. Exemplu: Cautarea binara(Binary Search) Fie A.Structuri de date si algortitmi _________________________________________________________________________ Exemplu: Insertion Sort (algoritmul de sortare prin inserare) Algoritmul INSERTION SORT considera ca în pasul k. limita superioara se numeste high.1) = 3(n-1) + 3n (n .. _________________________________________________________________________ 24 . Se cere sa se determine daca o valoare b se afla printre elementele vectorului. un vector ordonat crescator. termenii buclei celei mai interioare dau gradul polinomului egal cu gradul algoritmului. Observatie: Când avem mai multe bucle imbricate. • deplasarea tuturor elementelor din vectorul A[1÷k-1] care sunt mai mari decât A[k].

Teorema: Oricare ar fi doua constante c > 0. Cele doua complexitati se aduna. g1. 2k ≤ n < 2k+1. atunci: _________________________________________________________________________ 25 . 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) } Calculul complexitatii algoritmului consta în determinarea numarului de ori pentru care se executa bucla while. high = n. a > 1. Rezulta complexitatea acestui algoritm: este O(log2n). g : N → N. la fiecare trecere. Dar. baza logaritmului se poate ignora. g. Proprietati: 1) Fie f. dimensiunea zonei cautate se înjumatateste. Se observa ca. Pentru simplitate se considera n = 2k unde k este numarul de înjumatatiri. complexitatea totala sa se obtina înmultindu-se cele doua complexitati. h : N → N. Cazul cel mai defavorabil este ca elementul cautat sa nu se gaseasca în vector.Structuri de date si algortitmi _________________________________________________________________________ low middle high Binary_search(A. deci ramâne O(log n).n. atunci când avem doua bucle imbricate (de complexitati diferite). si ∀ f : N → N. f2. ∀ k ∈ R constant. Daca f = O(g) ⇒ | k × f = O(g) | f = O(k × g) .î. si: f1 = O(g1) | f2= O(g2) | ⇒ f = O(h) ⇒ ⇒ | f1 + f2 = O(g1 + g2) | f1× f2 = O(g1× g2) Aceasta proprietate permite ca. o functie monoton strict crescatoare. 2 2) Fie f. T(n) ≤ log n + 1 → ∃ n. adica o functie logaritmica. si: f = O(g) | g = O(h) | 3) Fie f1. g2 : N → N.b) { low = 1. deoarece: logax = logbx * logab si logab este o constanta. daca buclele sunt succesive. a. Rezulta k = log2 n si facând o majorare.

O(nk⋅ log n)  clasa algoritmilor polilogaritmici.1 Sa calculam.Structuri de date si algortitmi _________________________________________________________________________ (f(n))c= O(af(n)) Demonstratia se bazeaza pe limita: xp x −∞ a x lim ( ∀a . suma: n ∑i ⋅ 2 i =1 n n 1 Se noteaza: G (n) = ∑ i ⋅ 21 i =1 G (n) = 2 ⋅ G ( n) = ∑ 2i ⋅ 21 − ∑ i ⋅ 21 = ∑ i ⋅ 21+1 − ∑ i ⋅ 21 = i =1 i =1 i =1 i =1 n 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 n _________________________________________________________________________ 26 . O(n⋅ log n)  clasa algoritmilor polilogaritmici. 2) O(n  clasa algoritmilor patratici. O(n)  clasa algoritmilor liniari. de exemplu. O(log n)  clasa algoritmilor logaritmici. O(2n)  clasa algoritmilor exponentiali. p ) Între clasa functiilor logaritmice. Tehnici de calcul a complexitatii Se folosesc urmatoarele sume: ∑i = i =1 n n n(n + 1) 2 ⇒ O(n 2 ) ⇒ O(n 3 ) ∑i i =1 n i =1 2 = n(n + 1) ⋅ (2n + 1) 6 n 2 (n +1) 2 4 ∑i 3 = ⇒ O(n 4 ) ∑2 i =0 n 1 = 2 n +1 . si cea a functiilor polinomiale exista relatia: O(nc) ⊂ O(an). Au loc urmatoarele incluziuni: O(1) ⊂ O(log n) ⊂ O(n) ⊂ O(n⋅ log n) ⊂ O(n2) ⊂ O(nk⋅ log 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(nk+1)  clasa algoritmilor polinomiali.

Structuri de date si algortitmi _________________________________________________________________________ Prin aceeasi tehnica se calculeaza suma: ∑(n −1) ⋅ 2 i =1 n 1 _________________________________________________________________________ 27 .

< dn . pentru ca la sfârsit sa se aduca de pe tija de manevra (tm). cele n-1 discuri deplasate. Problema P(n) a deplasarii a n discuri. folosind ca manevra o tija auxiliara.Structuri de date si algortitmi _________________________________________________________________________ Curs 5 Am vazut ca: ∑i ⋅ 2 i =1 n n −1 = 2 n +1 − 2 − n Algoritmul recursiv si relatii de recurenta Exemplu: Problema turnurilor din Hanoi Se dau n discuri: a1. apoi plasarea celui mai lung disc pe tija finala.fiind diametrul discului. pe tija finala (tf). respectându-se conditia << Un disc nu poate fi plasat decât peste un disc mai mare >>. Deplasarea de pe o tija pe alta este echivalenta cu deplasarea a n-1 discuri de pe tija intiala (ti) pe tija de manevra. se rezolva prin deplasari succesive ale discurilor de pe o tija pe alta. .. . di ... cu d1 < d2 < . a2. Primele miscari s-ar figura astfel: ti tf tm ti tf tm Procedura Hanoi: _________________________________________________________________________ 28 . Discurile respective sunt stivuite pe o tija: cazul n=3 Se cere sa se deplaseze aceasta stiva pe o alta tija.. an de dimensiuni diferite.

cu urmatoarele cazuri: a) t1 . tm. ⇒ t2 = a⋅ t + b . ti.Structuri de date si algortitmi _________________________________________________________________________ Hanoi(n. ti.1) + b x0 = a⋅ x0 + b Prin scaderea celor doua relatii. 2 n −1 ⋅ 2 = T ( n) +1 f(1) x0 ⇒ T(n) = 2 n −1 ⇒ Ordinul este O(2n). tm. timpul T(1) = 1 . t2 ∈ R ⇒ solutia ecuatiei este de forma: f ( n ) = α ⋅ t n + β ⋅ t n iar α si β se calculeaza din conditiile 1 2 initiale: _________________________________________________________________________ 29 . f ( n) = 2 ⋅ f ( n −1) = 2 ⋅ 2 ⋅ f ( n − 2) = 2 ⋅ 2 ⋅ 2 ⋅ f ( n − 3) = 2 4 ⋅ f ( n − 4) = . Relatii de recurenta. timpul: T (n) = 2 ⋅T (n −1) +1 Dorim sa aflam ordinul de complexitate a lui T(n). tf. cu x0 = const. T (n) = 2 ⋅T (n −1) +1 =1 + 2 ⋅ (2 ⋅T ( n − 2) +1) Asociem relatiei (1) ecuatia caracteristica: x = 2 x −1. Clasele relatiilor de recurenta 1. = 2 n −1 ⋅ f (1) Facând identificarea: adica o complexitate exponentiala.x0) 2.x0 = a ⋅ (f(n-1) . tf) |_ Hanoi(n-1. ti) } Pentru o problema P(1) . pentru o mutare. f ( n ) = a ⋅ f ( n − 1) + b ⋅ f ( n − 2 ) f(n) = tn ⇒ tn = a⋅ tn-1 + b⋅ tn-2 Facând n = 2. Pentru P(n) .. tf. tf) | muta(ti. f ( n ) = a ⋅ f ( n − 1) + b f(n) = a⋅ f(n . x = 2 x0 +1 (1) T (n) − x0 = 2 ⋅ (T (n −1) − x0 ⇔ f (n) = 2 ⋅ f (n −1) ⇒ f ( n) = T (n) − x0 f ( n −1) = T ⋅ ( n −1) − x0 . rezulta un algoritm exponential cu baza a: f(n) . x0 = −1.. tm) { if(n=1) then muta (ti. tf)//deplaseaza discul superior ti -> tf else |¯ Hanoi(n-1.

j=j+1 |_ k = k+1 while i ⇐ mid do |¯ B[k] = A[i]. Clasa de relatii de recurenta pentru algoritmi de tip "divide et impera"  f (1)= x1  α t1 +⋅ β t2 =⋅ x1  ⇔ 2 2  f (2) = x2  α t1 +⋅ β t2 =⋅ x2 cu x1 si x2 constante. 1. b) t1 = t2 = t ⇒ Solutia este de forma: f (n) = (α + β ⋅ n) ⋅ t n n c) t1. mid. j=mid+1. Apelul initial va fi MERGE_SORT(A. este rezolvata ecuatia recursiva. mid+1. high) { i=low. k=low. MERGE(A.vectorul de sortat. se împarte vectorul în 2 segmente de lungime n/2 pe care le sorteaza separat recursiv. mid) //sortare separata | MERGE_SORT(A. low. low. high) //interclasare } Procedura MERGE interclaseaza secventele sortate A[low÷mid] si A[mid+1÷high]. si doi indici care delimiteaza o portiune din acest vector.Structuri de date si algortitmi _________________________________________________________________________ Astfel. n). high) //sortare separata |_MERGE(A. MERGE_SORT(A. while i ⇐ mid and j ⇐ high do |¯ if A[i] <A [j] then B[k]=A[i]. low. high) { if(low ≥ high) return else |¯ |¯ low + high ¯| | mid=| ---------------. β = α (conjugat) ⇒ solutia trigonometrica: f ( n) = r n (α1 ⋅ cos nt + β1 sin nt ) 3. i=i+1 | else B[k] = A[j]. Pentru aceasta este nevoie de un vector auxiliar B. i=i+1 |_ k = k+1 whilej ⇐ high do _________________________________________________________________________ 30 .| //partea întreaga | |_ 2 _| | MERGE_SORT(A. mid. t2 ∈ C ⇒ Solutia este de forma: f ( n) = α ⋅ t1n + β ⋅ t 2 În care α si β ∈ C. Exemplu: Algoritmul Merge Sort (sortare prin interclasare) Pentru a sorta o secventa de n elemente ale unui vector A. low. de aceeasi dimensiune cu A. dupa care urmeaza interclasarea. Pseudocod: Procedura MERGE_SORT primeste ca argumente A .

. si relatia de recurenta: T ( n) = a ⋅ T ( n / b ) + C ⋅ n k Solutia acestei ecuatii de recurenta este: O(nl o bga ) .Structuri de date si algortitmi _________________________________________________________________________ |¯ B[k] = A[j]. b = 2 .se face T ( n + 1) − T ( n ) Exemplu: Algoritmul Quick_Sort _________________________________________________________________________ 31 .. a〈 b k Utilizând aceste retete putem calcula complexitatile pentru: • algoritmul Merge_Sort: a = 2 . deci. k = log2n. T(n) = 2⋅ T(n/2) + n = 2⋅ (2⋅ T(n/4) + n/2) + n = . Timpul de executie al algoritmului este: T ( n) = 0. (situatia ak= b). = 22·T(n/22) + 2n= 22 (2·T(n/2 3) + n/22) + 2n = = 23⋅ T(n/23) + 3n = . analog. a 〉 bk T (n) = O(n ⋅ l o ng) . + T (1) T ( n + 1) = C + ∑ T (i ) i =1 . k = 1 .. Relatii de recurenta cu istorie completa n −1 i =1 Exemplu: T ( n) = C + n ∑ T (i ) T ( n ) = C + T ( n − 1) + T ( n − 2 ) +. si.. n =1 2T(n/2) + n. 4.. n〉1 Consideram: n = 2k. k = 0 ⇒ complexitatea O(n0⋅ log n) = O(log n). Se demonstreaza. iar combinarea rezultatelor celor a prbleme. se rezolva pentru a probleme de dimensiune n/b.. b = 2 . } Aratam complexitatea procedurii MERGE_SORT: O(n⋅ log n) Aceasta functie cere pentru o secventa c⋅ n operatii. duce la g(n) operatii. = 2k⋅ T(n/2k) + k⋅ n ⇒ T(n) = k⋅ n = n⋅ log2n . Pentru a rezolva problema de dimensiune n. bk = a ⇒ complexitatea O(nk⋅ log n) • algoritmul Binary_Search: a = 1 . a = b k k k O(n ) . j=j+1 |_ k = k+1 for k=low to high do A[k] = B[k]. pentru ca n = 2k . Asadar complexitatea algoritmului este O(n⋅ log n).

h= high. Ciclul I while înseamna ca înainteaza l cât timp A[l] ≤ pivot.Structuri de date si algortitmi _________________________________________________________________________ elemente pivot A[k]=pivot elemente pivot l Quik_Sort(A. va fi plasat în ultima pozitie din secventa. Pivotul gasit. low. la primul pas. Pivotul va fi maximul dintre elementele secventei. low. cercetam cazul cel mai defavorabil. Ciclul II while însemna ca înainteaza h cât timp A[h] > pivot. high) } Pseudocodul pentru functia partition: Partition(A. Fie cazul în care vectorul este ordonat descrescator. iar h lasa în urma numai elemente A[i] > pivot. k+1. astfel încât sa se permita înaintarea indicilor mai departe. low. 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]. Ei se apropie pâna se întâlnesc (l = h). iar indicele h parcurge vectorul de la dreapta la stânga. l lasa în urma numai elemente A[i] ≤ pivot. } h | Algoritmul considera pivotul ca fiind: A[low]. P(0). Problema se împarte în 2 subprobleme: P(n) → P(n-1) . _________________________________________________________________________ 32 . k-1) |_ Quick_Sort(A. Acest ciclu se opreste pe conditia A[h] ≤ pivot. Indicele l parcurge vectorul de la stânga la dreapta. fixându-se aici. Cele doua pozitii se schimba. high) // procedura de // partitionare | Quick_Sort (A. high) { if(high >low) then |¯ k= Partition(A. fixându-se aici. Deci. low. Acest ciclu se opreste pe conditia A[h] > pivot. A[h]) interchange (A[h]. x= A[l]. deci. low h Pentru aflarea complexitatii. high) { l= low. este elementul maxim din vector. A[low]) return(h). rezulta ca trebuie plasat în ultima pozitie.

Asadar.Structuri de date si algortitmi _________________________________________________________________________ Numarul de comparatii pentru functia Partition este (n-1). n 1 1 n T ( n) = ∑   ⋅ Ti (n) =  ⋅ ∑Ti (n)  n  i =1 i =1  n  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 n n −1 1 1 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 . Vectorul se parcurge în doua directii. Rezulta ca este un algoritm ineficient. high ( k = 1. Rezulta ca timpul de functionare al algoritmului Quick_Sort este: T ( n ) = ( n − 1) + T ( n − 1) Rezolvând aceasta ecuatie. n ) . Studiul complexitatii algoritmului Quick_Sort în caz mediu Pentru complexitatea medie trebuie considerata probabilitatea tuturor aparitiilor datelor de intrare. Rezulta: ∑ i = n(n − 1) / 2 i =1 n −1 Aceasta suma este de complexitate O(n2).. pentru k = low. 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 ) Timpul mediu va fi o medie aritmetica: Dezvoltând. pivotul va fi plasat în pozitia k prin partitionare.. high . Consideram ca orice configuratie de date la intrare este egal probabila. +1 + T (1) T ( n) = unde: T(1) este 0 (nu se partitioneaza). Evenimentul este plasarea pivotului în pozitia k. Suma tuturor probabilitatilor este 1.i + 1) Rezulta relatia de recurenta cu istorie completa:  2  n −1 T (n) = n −1 +   ⋅ ∑T (i )  n  i =0 Aceasta se rezolva astfel: înmultind relatia cu n rezulta: n ⋅ T (n) = n(n −1) + 2 ⋅ ∑T (i ) i =0 n −1 Scriem acum relatia înlocuind pe n cu n+1: ( n + 1) ⋅ T ⋅ ( n + 1) = ( n + 1) ⋅ n + 2 ⋅ 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) ∑T (i) i =0 n _________________________________________________________________________ 33 .. cu o probabilitate egala cu 1/n. = n − 1 + n − 2 + n − 3+. avem: T ( n ) = n − 1 + T ( n − 1) = n − 1 + n − 2 + T ( n − 2 ) =.. dar o singura data. Probabilitatea ca pivotul sa fie plasat în pozitia k este egala pentru k = low.

Rezulta:  1 F ( n) 〈 2 ⋅ ∑   i= 2  i  n si  1  1  F n)( 2⋅〈 ∑   2⋅〈 ∫   d x i= 2  i  i  x  n n este aria zonei de sub graficul functiei f ( x ) = 1 .) . + F(i. (n +1)( n + 2) F (n) = 2(n +1) + F ( n . + .Structuri de date si algortitmi _________________________________________________________________________ T (n +1) 2n T ( n) 1 = + care se înmulteste cu: . ( n +1)( n +2) n +2 ( n +1)( n + 2) n +1 Notam: F ( n) = T ( n) . x _________________________________________________________________________ 34 . n +1 F ( n +1) = 2 ⋅n + F ( n).1) n(n +1) Facând o majorare: 2 F (n) 〈 + F (n − 1) n 2 22 22 2 22 2 2 F(n)〈 + F(n− 1)〈 + F(n−+ 2)〈 + + + F(n− 3)〈. n n − 1 n − 1 nn − 2 n − 1 nn − 2 2 F (i ) = 0 (un element nu se ordoneaza). + +.

_________________________________________________________________________ 35 .Structuri de date si algortitmi _________________________________________________________________________ nF )( 2⋅〈 l n n2⋅− l 1⇒ n nF )( 2⋅〈 l n n nT )( n+ 1 = nF )( ⇒ nT )( (n+〈 1)l n〈 2n ⋅ l n⇒ n nT )( = (nO l n) n unde O(n⋅ ln n) este complexitatea acestei functii.

Fie urmatoarea procedura recursiva: parcurgere(l) // l . • • • • Asadar. . Consumul de memorie al algoritmului recursiv este proportional cu numarul de apeluri recursive ce se fac.adresa instructiunii de retur (cu care va continua programul dupa terminarea executiei functiei). La prelucrarea unei liste. iar variabilele dinamice în "heap"uri (cu malloc în C. se prelucreaza acesta. Variabilele automate (locale) se memoreaza în stiva. De exemplu.parametrii de apel. se continua cu instructiunea data de adresa de retur memorata în "înregistrarea de activare". etc. de la coada la cap.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. urmând apoi ca restul listei sa fie considerata ca o noua lista mai mica. se scoate din stiva "înregistrarea de activare" (decrementarea vârfului stivei). se executa instructiunile functiei care folosesc pentru parametri si variabile locale din "înregistrarea de activare". mai exact în segmentele de date. • Apelul functiei se face astfel: se creeaza în stiva programului o "înregistrare de activare" în care sunt memorate: . se rezerva spatiu pentru variabile locale. Variabilele recursive consuma mai multa memorie decât cele iterative. low. si cu new în C++).lista înlantuita (pointer la primul //element) { if(l ≠ 0) |¯ parcurgere(link(l)) |_ prelucrare(data(l)) // exemplu: afisare } Functia afiseaza o lista invers. algoritmul Quick_Sort: Quick_Sort(A. high) { _________________________________________________________________________ 36 . stiva fiind ordonata. variabilele globale (statice) sunt memorate într-o zona de memorie fixa. daca primul element nu este vid.

low. secventa mare rezolvata iterativ Liste generalizate Definitie: _________________________________________________________________________ 37 . astfel încât un apel sa fie rezolvat recursiv.Structuri de date si algortitmi _________________________________________________________________________ if(low < high) |¯ k = Partition(A. high) } low k high Avem în acest algoritm doua apeluri recursive. high) | Quick_Sort(A. k+1. low. k-1) |_ |_low = k-1 } Necesarul de memorie pentru aceasta este M(n) ≤ c + M(n/2). se concepe un alt algoritm la Quick_Sort. low. Pentru reducerea consumului de memorie. k secventa mica rezolvata recursiv Quick_Sort(A. însemnând ca oricare ar fi secventa mai mica. ea este ≤ decât jumatatea ⇒ M(n) = O(log n) ⇒ am redus ordinul de complexitate. k-1) |_Quick_Sort(A.1) ⇒ M(n) = O(n) ⇒ un ordin de complexitate mare. high) | if( k-low > high-k) | |¯ Quick_Sort(A. low. high) { while (low < high) |¯ k = Partition(A. Cazul cel mai defavorabil: low k high Consideram consumul de memorie în stiva : M(n) = c + M (n . high) | |_high = k-1 | else | |¯ Quick_Sort(A. iar celalalt apel iterativ. low. k+1.

αn). în care αi sunt atomi. Fie urmatoarele primitive de selectie pentru un nod de adresa p: p  adresa unui nod.Structuri de date si algortitmi _________________________________________________________________________ Data o multime de elemente (atomi). c) B = (x. Exemplu: A = (a. se numeste lista generalizata o secventa finita (α1. Daca tag = 0 ⇒ nodul va corespunde unui element atomic ⇒ câmpul data va contine atomul respectiv. link este legatura pentru urmatorul nod din lista. c). A. adica tag(p) = 1 ⇒ data(p) va fi adresa unei structuri ce contine: _________________________________________________________________________ 38 . ( )) | | | \ atom lista lista lista vida Observatie: Listele generalizate pot sa aiba elemente comune. (b. link) în care tag este o eticheta ∈ {0. Se permite definirea de liste recursive. A. data. (a. b. α2. . Reprezentarea listelor generalizate Presupunem o lista de forma: (tag. link(p)  câmpul "link" din nodul indicat de p. ( )) C = (a. c)) B = (A.1}. ... Notam: tag(p)  câmpul "tag" din nodul indicat de p data(p)  câmpul "data" din nodul indicat de p Fie urmatoarele liste: D=() A = (a. C) cu urmatoarele reprezentari: D : o lista vida înseamna un pointer nul A: 0 a 1 0 0 B: C: 1 1 b 0 1 0 0 c 0 0 a 1 0 Ne propunem sa dam nume unei subliste. deci daca tag = 1. Daca tag = 1 ⇒ nodul va corespunde unei subliste ⇒ câmpul data va semnifica legatura la primul element al sublistei.

_________________________________________________________________________ 39 .Structuri de date si algortitmi _________________________________________________________________________ nume lista pointer la primul element Asadar. obtinem urmatoarea reprezentare: D: A: D 0 A 0 a 1 0 _ 0 B: B b 0 c 0 1 1 1 0 _ C: C 0 a 1 0 • • Operatii la liste generalizate: functia insert. este asemanatoare cu cea de la liste înlantuite. functia del ( ) trebuie sa tina seama de existenta unor liste comune. este necesara pastrarea în elementul ce contine numele listei A si a unui indicator care sa contorizeze numarul de referinte ale lui. Elementul ce se insereaza poate fi un atom sau o sublista. Deci.

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 . realizate recursiv si iterativ. se face stergerea efectiva a listei.l2) // procedura tratata iterativ { p 1 = l 1. p 2 = l 2 while(p1 ≠ 0 and p2 ≠ 0) |¯ if (data(p1) ≠ data(p2)) then return (FALSE) | p1 = link(p1) |_ p2 = link(p2) return(p1 = p2) } isEqual (l1. Copie mai întâi primul element.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 (l1. Traversarea listelor generalizate Traversarea listelor generalizate presupune prelucrarea elementelor listei. Functia returneaza o copie a listei. p 2 = l 2 _________________________________________________________________________ 40 . si a elementelor sublistelor componente.Structuri de date si algortitmi _________________________________________________________________________ Exemplu: A 2 Numai daca acest indicator este 0. Exemplu: O functie de copiere si o functie de test de egalitate a doua liste generalizate. si apoi recursiv restul listei: Varianta recursiva: Copy (l) // l .l2) // procedura tratata recursiv { p 1 = l 1.

Structuri de date si algortitmi _________________________________________________________________________ while(p1 ≠ 0 and p2 ≠ 0) |¯ if (tag(p1) ≠ tag(p2)) then return (FALSE) | if (tag(p1) = 0 and data(p1) ≠ data(p2)) then | return (FALSE) | if (tag(p1) = 1 and not isEqual | (data(p1).data(p2))then return FALSE) | p1 = link(p1) |_ p2 = link(p2) return (p1 == p2) } _________________________________________________________________________ 41 .

E) este un arbore si r ∈ V este radacina arborelui. x2. y  succesorul lui x (fiu) x / y 2) Fie E(x) = { y. y) ∈ E } multimea succesorilor lui x. xi+1) ∈ E (Sau arborele trebuie sa fie un graf conex: nu exista noduri izolate sau grupuri de noduri izolate). _________________________________________________________________________ 42 . Definitii 1) Daca avem (x. . Proprietate a arborelui Daca T. E'). a caror reuniune sa fie T\{r}... ∃ un drum { r = x0. T= (V. 2) ∀ x ∈ V\{r} .. x)/(r. Ti ∩ Tj = ∅ . cu V' = V -{r} si E' = E -{ (r. (j. sa dea multimea vida: T\{r}= T1 ∪ T2 ∪ . y) ∈ E . si oricare ar fi doi arbori intersectati. ∃ un singur arc ce intra în nodul respectiv) 3) ∀ y ∈ V. pentru toate nodurile minus radacina. r) ∉ E (nici un arc nu intra in radacina). . cu xi ∈ V si (xi. x) ∈ E (Cu alte cuvinte. x) ∈ E } poate fi partitionata astfel încât sa avem mai multi arbori. x1. ⇒ x  predecesorul lui y (tata). cu proprietatile: 1) ∃ nodul r ∈ V (nodul radacina) astfel încât ∀ j ∈ V. ∪ Tk .Structuri de date si algortitmi _________________________________________________________________________ Curs 7 Arbori Exemplu de structura de arbore: b / d Exemple de arbori: / a c / \ e f g / \ \ h i j \ \ poligoane / \ triunghi patrulatere / \ \ dreptunghi romb oarecare Definitie Se numeste arbore cuplul format din V si E : T= (V. astfel încât (y. ∃ y ∈ V unic . (x. atunci multimea T\{r} = (V'.xn= y} ..E) cu V  o multime de noduri si E ⊆ VxV  o multime de arce.

sau frunza. i= {0. adica daca nu are descendenti. daca degree(y) = 0. L)... level(A) = 1 depth(T) = 4 Reprezentarea arborilor Reprezentarea prin liste generalizate Se considera ca nodurile terminale sunt elemente atomice. I. C (G). F). D (H (M). J} degree(D) = 3 degree(B) = 2 degree(F) = 0 degree(T) = 3 ancestors(L) = {A. iar nodurile de grad ≥ 1 sunt subliste. fie arborele de mai sus scris sub forma : A( B (E (K. level(B) = 2 .. B. I. x2. .k . Deci.Structuri de date si algortitmi _________________________________________________________________________ Definim gradul lui x: degree(x) = │ E(x) │ = numarul de succesori Definim gradul arborelui : degree(T) = max{ degree(x)}. xk} cu proprietatea ca (xi. x1. x ∈ V unde y se mai numeste nod terminal. E} level(L) = 4 . xi+1) ∈ E . Stramosii unui nod sunt asa-numitii ancestors(x) : ancestors(x) = {r = x0. J)) cu reprezentarea: _________________________________________________________________________ 43 . x) ∈ E Nivelul unui nod : level(x) = │ ancestors(x) │ + 1 Adâncimea arborelui : depth(T) = max { level (x) pentru x ∈ V Exemplu: A | \ B C D / \ | / | \ E F G H I J / \ | K L M / predecesor(E) = B succesor(C) = G E(D) = {H.1} si (xk.

Structuri de date si algortitmi _________________________________________________________________________ A 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 Reprezentarea prin structuri înlantuite _________________________________________________________________________ 44 .

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). parent): _________________________________________________________________________ 45 .Structuri de date si algortitmi _________________________________________________________________________ data link1 link2 .... Dar. exista aplicatii în care este nevoie de legatura nod-predecesor. ea poate surprinde structura diferita. atunci când conteaza ordinea descendentilor. link k k=degree(T) A B 0 C 0 0 D E 0 0 F 0 0 0 G 0 0 H 0 0 I 0 0 0 J 0 0 0 K 0 0 0 0 L 0 0 0 M 0 0 Aceasta reprezentare are calitatea ca. pare utila reprezentarea arborelui sub forma nodului (data. Asadar...

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. instrumente în plus de operare. În cazul acestor arbori. se pot defini aplicatii. se gasesc toti predecesorii. 0: A A A / \ / / \ B C B B C / \ \ / / \ / \ D E F C D E F G Observatie: Arborele A / \ B vid este diferit de A / \ vid B rad / / / / / \ / SAS \ /______ \ \ \ \ Structura de baza a unui arbore binar: SAS  subarborele stâng (binar) SAD  subarborele drept (binar) Definitii \ / \ / SAD \ /_______\ _________________________________________________________________________ 46 . 1. Arborii binari pot avea deci gradele 2.Structuri de date si algortitmi _________________________________________________________________________ A 0 B C D E F G H I J K L M Având adresa unui nod.

. ⇒ niv(n+1) ≤ 2·niv(n) ≤ 2⋅ 2n-1 = 2n . Demonstram pentru P(n+1): se observa ca toate nodurile de pe nivelul n+1 sunt noduri descendente de pe nivelul n. Presupunem conform metodei P(n): pe nivelul n avem 2n-1 noduri.. Demonstratia se face prin inductie: La nivelul 1 avem 20 = 1 nod = rad (radacina A). nodurile terminale apartin ultimului nivel din arbore. sau degree(x) = 0. Exemplu: / b / \ a \ c / \ d e f g / \ / \ / \ / \ h i j kl mn o Relatii între numarul de noduri si structura unui arbore binar Lema 1 Numarul maxim de noduri de pe nivelul i al unui arbore binar este egal cu 2i-1. _________________________________________________________________________ 47 . 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. +2h −1 = 2h − 1 = 2h − 1 2 −1 Observatie: Numarul maxim de noduri pentru arborele binar se atinge în situatia unui arbore binar complet. Lema 2 Numarul maxim de noduri ale arborelui binar de adâncime h este egal cu 2h -1. Demonstratie: Numarul total de noduri este egal cu: (progresie geometrica) h h ∑ i =1 niv (i ) ≤ ∑ i =1 2i −1 = 20 + 21 +.Structuri de date si algortitmi _________________________________________________________________________ 1) Se numeste arbore binar strict arborele pentru care oricare ar fi un nod x ∈ V ⇒ degree(x) = 2 . Notând niv(i) numarul de noduri de pe nivelul i. 2h -1 = numarul de noduri în arborele binar complet de adâncime h Lema 3 Notam cu: n2  numarul de noduri de grad 2 din arborele binar.

conform definitiei. Strategii de traversare: • • • traversare în preordine: prelucrare în ordinea: rad. n .1. testarea unei conditii pentru fiecare nod. SAD. listare. SAD. Lemele se folosesc pentru calcule de complexitate. rad / \ SAS SAD Exemplu de traversare: / d g • • • 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 / b a \ \ / e \ h c \ f Functii de parcurgere (in pseudocod) Facem notatiile: p  pointer la un nod lchild(p)  pointer la succesorul stâng (p → stg) _________________________________________________________________________ 48 . n0  numarul de noduri terminale (frunze) din arborele binar. rad. Rezulta ca într-o expresie numarul de operatori binari si unari este egal cu numarul de operanzi + 1. de exemplu. Traversarea arborilor binari (A.Structuri de date si algortitmi _________________________________________________________________________ n1  numarul de noduri de grad 1 din arborele binar. În orice arbore binar. Operatii asupra arborilor binari Operatii curente: • selectia câmpului de date dintr-un nod si selectia descendentilor. O traversare realizeaza o ordonare a nodurilor arborelui (un nod se prelucreaza o singura data). • stergerea unui nod. n0 + n1 +n2 -1 = 2n2 + n1 ⇒ n0 = n2 + 1 ceea ce trebuia de demonstrat. Demonstratie: fie n = n0 + n1 + n2 (numarul total de noduri).B. n0 = n2 +1 (nu depinde de n1).1 = 2n2 + n1 . traversare în postordine: prelucrare în ordinea: SAS. Acelasi numar de muchii │ E │ = 2 n2 + n1. SAD. fiecare nod are un singur predecesor ⇒ numarul de muchii │ E │ = n . rad. traversare în inordine: prelucrare în ordinea: SAS.) Traversarea consta în "vizitarea" tuturor nodurilor unui arbore într-un scop anume. SAS. sau alta prelucrare. înlocuind. Deci. • inserarea unui nod.

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) } postorder(t) { if(t ≠ 0) } 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 n⋅ k Numarul total de pointeri nenuli este egal cu numarul de arce ⇒ n⋅ k - (n - 1) = n (k - 1) + 1

|

|¯ inorder (lchild(t)) | print (data(t)) |_ inorder (rchild(t))

|¯ postorder (lchild(t)) | postorder (rchild(t)) |_ print(data(t))

nr. pointeri nuli n ( k − 1) + 1 n −1 = = 1− nr. total pointeri n⋅k n⋅k

raportul este maxim pentru k = 2. Rezulta ca cea mai eficienta reprezentare (în structura înlantuita) este reprezentarea în arbori binari.

_________________________________________________________________________ 49

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: a1 ≠ a2 ⇒ key(a1) ≠ key(a2). Exemplu: (abreviere, definitie) ("BST","Binary Search Tree") ("LIFO","Last In First Out") key(a) = a.abreviere • • • Un dictionar este o colectie S de atomi pentru care se definesc operatiile: insert(S,a)  insereaza atomul a în S daca nu exista deja; delete(S,k)  sterge atomul cu cheia k din S daca exista; search(S,k)  cauta atomul cu cheia k în S si-l returneaza sau determina daca nu este.

O solutie imediata ar fi retinerea elementelor din S într-o lista înlantuita, iar operatiile vor avea complexitatea O(n). Tabelele Hashing Acestea sunt o alta solutie pentru a retine elementele din S. Complexitatea pentru arborele binar de cautare în cazurile: • cel mai defavorabil: O(n); • mediu: O(log n). Un arbore binar de cautare este un arbore T ale carui noduri sunt etichetate cu atomii continuti la un moment dat în dictionar. T = (V, E) , │V│ = n. (n atomi în dictionar) Considerând r ∈ V (radacina arborelui), Ts  subarborele stâng al radacinii si Td  subarborele drept al radacinii, atunci structura acestui arbore este definita de urmatoarele proprietati: 1) ∀ un nod x ∈ Ts atunci key(data(x)) < key(data(r)); 2) ∀ x ∈ Td atunci key(data(x)) > key(data(r)); 3) Ts si Td sunt BST. Observatii: 1) Consideram ca pe multimea k este definita o relatie de ordine (de exemplu lexico-grafica); 2) Pentru oricare nod din BST toate nodurile din subarborele stâng sunt mai mici decât radacina si toate nodurile din subarborele drept sunt mai mari decât radacina. Exemple: 15 / \ 7 25 15 / \ 10 25

_________________________________________________________________________ 50

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() data(p)= a lchild(p)= 0 rchild(p)= 0 return(p) //

// alocare de memorie pentru un nod nou

_________________________________________________________________________ 51

a).k) else delete_root(rad) } Stergerea radacinii unui BST. functia insert nu modifica structura arborelui. 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. Exista probleme în care este utila contorizarea numarului de inserari a unui atom în arbore.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). 2) Functia insert poate returna pointer la radacina facând apeluri de forma p= insert(p.k) // rad  referinta la pointer la radacina { // k .Structuri de date si algortitmi _________________________________________________________________________ } Observatie: 1) La inserarea unui atom deja existent în arbore.: 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. 3) Delete: delete(rad. _________________________________________________________________________ 52 .k) else if key(data(rad)) < k then delete(rchild(rad).

rezulta: a1 / \ a1 ai (ai<a1) (ai>a1) _________________________________________________________________________ 53 . în sensul eliminarii nodului cel mai mare.. în urma primei operatii de inserare pe care o facem. De exemplu.. fiecare functie parcurge lantul cel mai lung din arbore.Structuri de date si algortitmi _________________________________________________________________________ Vom trata aceasta procedura recursiv: Caz1: rad ⇒ se returneaza pointer la radacina si arborele rezultat va fi vid. an) cu observatia ca a1 este radacina arborelui. iar rezultatul va fi SAS  arborele care este fomat din radacina. si întoarce pointer la nodul eliminat. Functia de cautare are. Demonstratia eficientei (complexitate) Complexitatea tuturor functiilor scrise depinde de adâncimea arborelui. ai) Calculam complexitatea medie a generarii BST: Complexitatea în cazul cel mai defavorabil este: n ∑(i) = O(n 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.. Pentru problema T(n) avem de creat secventa (a1 a2 . Caz2: rad ⇒ se returneaza pointer la radacina si arborele rezultat va fi format doar din SAS  subarborele stâng (SAS). an) gen_BST (va fi în programul principal) | rad= 0 | for i= 1 to n | insert (rad. Studiem un caz de complexitate medie: Crearea unui BST pornind de la secventa de atomi (a1 a2 . Structura arborelui BST este determinata de ordinea inserarii. complexitatea O(n). ordinea 15 13 12 11 este alta decât 15 12 11 13 . 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.SAS si SAD  cel mai mare nod. Ordinea celor k elemente se considera aleatoare. Ca rezultat. Caz3: rad ⇒ functia returneaza pointer la cel mai mare nod din SAD.. În cazul cel mai defavorabil. în acest caz.

med.Structuri de date si algortitmi _________________________________________________________________________ Nu vom considera numararea operatiilor în ordinea în care apar ele. Putem scrie: Ti ( n ) = ( n − 1) + T ( i − 1) + T ( n − i ) T (n) + (1 / n) ⋅ ∑Ti (n) i =1 n Deci: T (n) = (1 / n) ⋅ ∑ (( n − 1) + T (i − 1) + T (n − i )) = i =1 n = (n − 1) + (1 / n) ⋅ ∑T (i − 1) + (1 / n) ⋅ ∑T (n − i ) = i =1 n i =1 n n = (n − 1) + (2 / n) ⋅ ∑T (i − 1) i =1 Deci: T (n) = n − 1 = (2 / n) ⋅ ∑T (i ) i =0 n −1 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.SAS = valoarea medie a numarului de comparatii necesar pentru a construi subarborele stâng SAS val. Dupa ce am inserat a1.med.SAD = valoarea medie a numarului de comparatii necesar pentru a construi subarborele drept SAD T ( n) = (n − 1) + (1 / n) ⋅ ∑ T (i − 1) + (1 / n) ⋅ ∑ T (n − i ) i =1 i +1 n n 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.1) + val. pentru fiecare nod din arbore.SAD val. Exemple: / a \ b c / \ / \ d e f g arbore binar b / a c / \ \ d e f / \ g h arbore binar \ _________________________________________________________________________ 54 . pentru inserarea fiecarui element în SAS sau SAD a lui a1.med. Deci: T(n)= (n .SAS + val. se face o comparatie cu a1. ci consideram numarul de operatii globale.med. diferenta dintre adâncimile SAS si SAD în modul este ≤ 1.

Transformarea structurii arborelui dupa inserare pentru a conserva proprietatea de arbore binar echilibrat Modificarile care se vor face se vor numi rotatii. Caz 1: Fie arborele echilibrat A / \ B T3 h = depth(T1) = depth(T2) = depth(T3) / \ T1 T 2 Consideram arborii T1. Efortul. 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 T2 T 1   rotatie dubla rotatie dubla la dreapta la stânga Fie primul caz: A / \ B T3 / \ T1 T 2 este BST: T1 < B < T2 < A < T3  Toti arborii care respecta în continuare aceasta conditie vor fi BST. în plus. pentru completarea operatiilor insert si delete nu schimba complexitatea arborelui binar echilibrat. T3 echilibrati. Se completeaza operatiile insert si delete cu niste prelucrari care sa pastreze proprietatile de arbore binar echilibrat pentru arborele binar de cautare. Ridicând pe B în sus. T2. rezulta: B // \ // A T1 / \\ _________________________________________________________________________ 55 . Inserând un nod prin rotatie simpla. proprietatea de echilibrare fiind conservata de insert si delete. si notând cu // legaturile neschimbate.Structuri de date si algortitmi _________________________________________________________________________ complet echilibrat echilibrat Adâncimea unui arbore echilibrat cu n noduri este O(ln n). Arborele binar echilibrat este un BST echilibrat.

Structuri de date si algortitmi _________________________________________________________________________ ___________ 2_T3_________ pe aceeasi _T linie care este un BST . \ B A // \ / \\ // T2S T2D \\ ____T1______ ___ 3_____________________ T la acelasi nivel Rezulta un BST echilibrat. Nu se poate sparge arborele initial ca în cazul 1. si are aceeasi adâncime (!!!) cu arborele initial (de dinainte de inserare). Aplicam aceiasi tehnica: T1 < B < T2S < C < T2D < A < T3 Începem cu C: / C ca s-a facut o rotatie simpla B la stânga cu o rotatie simpla A la dreapta. în sensul 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. unul dintre arborii T2S si T2D îsi mareste adâncimea. de aceeaai adâncime cu arborele initial. Nodurile superioare nu sunt afectate. _________________________________________________________________________ 56 . deci este arbore echilibrat. Rotatiile sunt duble. A / \\ B \\ // \ T3 // C // / \ T1 T2S T2D   depth(T1) = depth(T 3) = h depth(T2S) = depth(T 2D) = h . Fie cazul 2: Pentru rotatia dubla se detaliaza structura arborelui T2. Rationament analog pentru imaginea oglinda.1 În urma inserarii.

în inserarea prin rotatie se obtine un arbore echilibrat cu adâncimea egala cu adâncimea arborelui de dinainte de inserare. Observatie: Pentru nodul terminal s-a schimbat adâncimea si factorul de balansare. Nodul critic va fi perfect balansat (bf = 0). 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. se aplica o rotatie în urma careia se schimba strucutra subarborelui. conservându-se adâncimea.1 (bf = -2 dupa inserare) ⇒ bf = 0 (bf = 1 dupa inserare) ⇒ bf = 0 (bf = -1 dupa inserare) 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). Acesti factori de dezechilibrare vor fi actualizati în urma _________________________________________________________________________ 57 . astfel încât noua radacina capata bf = 0. deci sa gasim nodul asupra caruia trebuie aplicata rotatia. La nodul critic exista doua situatii: 1. În acest caz nu este nevoie de rotatie (el completeaza un gol în arbore). daca dezechilibrul creat de nodul inserat anuleaza dezechilibrul initial al nodului.Structuri de date si algortitmi _________________________________________________________________________ Curs 9 Asadar. Trebuie. Trebuie aplicata. Observatie: Toate nodurile din ramura care sunt pe nivele inferioare nodului critic vor capata bf = 1 sau bf = -1. w x ⇒ bf = 1 ⇒ bf = 0 ⇒ bf = . 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. bf = -2 dupa inserare devine nod dezechilibrat. În acest caz. La inserarea unui nod terminal într-un arbore AVL este necesara aplicarea a cel mult o rotatie asupra unui nod. echilibrarea. deci. Calculam factorii de balansare dupa inserare. Reprezentam ramura parcursa de la radacina la nodul inserat: / y \ z \ / v \ nodul inserat S-a notat pentru fiecare nod bf  balance factor (factor de dezechilibrare): bf(nod) = depth (lchild (nod))  depth (rchild (nod)) adica este diferenta dintre adâncimea subarborelui stâng si adâncimea subarborelui drept. Costul suplimentar care trebuie suportat se materializeaza prin necesitatea ca în fiecare nod sa se memoreze factorul de dezechilibrare bf. 2.

Exemplu: Se da arborele cu urmatoarea structura: 55 / \ 20 80 / \ \ 10 35 90 / / 5 30 Sa se insereze nodurile 15. Este necesara aplicarea unei rotatii asupra radacinii. B are bf = 0 | _________________________________________________________________________ 58 / . iar pentru 9 este este 10. Deci. Pentru a doua valoare de inserat. Inseram prima valoare 15. daca nodul 9 a fost inserat în subarborele T2S. Operatia de stergere într-un AVL implica mai multe rotatii. apoi 7 si sa se echilibreze arborele. T2S  vizi). ea nu se studiaza în acest curs. ⇒ 20 ----------> noduri implicate / \ în 10 55 ------> rotatie / \ / \ 5 15 35 80 \ / \ 7 30 90 În urma unei rotatii simple. Comparam mai întâi cu 55 : e în stânga lui. nodul critic este 55. apoi a patra 9: Nodul critic pentru 3 este 5. rezulta arborele: 20 / \ 7 55 / \ / \ 5 10 35 80 / / \ / \ 3 9 15 30 90 La rotatia dubla. pâna când gasim locul ei în pozitia de mai jos.d. Dupa rotatia aplicata (T2D.Structuri de date si algortitmi _________________________________________________________________________ operatiilor de rotatie si inserare. Fie o a treia valoare de inserat 3.a. s. va fi echilibrat la valoarea 2. Se asociaza nodurile: 55 → A 20 → B etc. 7.m. 55 \ 20 80 / \ \ 10 35 90 / \ / 5 15 30 \ 7 Facem o identificare cu unul din desenele de echilibrare prezentate în cursul anterior. El este dezechilibrat stânga. factorii de dezechilibru implicati în rotatie devin zero.

Structuri de date si algortitmi _________________________________________________________________________ A are bf = -1 | exceptie facând nodul C  nodul de inserat La rotatia dubla. atunci: 1. reprezentarea lui implicita se obtine completându-se structura arborelui cu noduri fictive pâna la obtinerea unui arbore binar complet. Fie un arbore binar complet: / / b a \ c \ / \ d e f g / \ / \ / \ / \ h i j k l m n o care are 4 nivele. Arbori heap (heap  gramada) Definitie: Se numeste arbore heap un arbore binar T = (V. parent(x) este reprezentat în elementul de vector V [[i/2]] cu observatia ca paranteza patrata interioara este partea întrega. Asociem acestui arbore un vector V: V: a 1 indici b 2 c 3 d 4 e 5 f 6 g 7 h 8 i 9 j 10 k 11 l 12 m 13 n 14 o 15 structurat astfel: radacina. B are bf = 1 | A are bf = 0 | exceptie facând nodul C  nodul de inserat Reprezentarea implicita a arborilor binari În acest mod de reprezentare se reprezinta arborele printr-un tablou. 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. 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. despartite de linii duble. 3. right_child(x) este reprezentat în elementul de vector V [2·i + 1]. deci 24 . etc. nivelul 1 de la stânga la dreapta.1 = 15 noduri. 2. nodurile nivelului 2 de la stânga la dreapta. E) cu urmatoarele proprietati: _________________________________________________________________________ 59 . left_child(x) este reprezentat în elementul de vector V [2·i]. Lema Daca în vectorul V un nod x este reprezentat prin elementul de vector V(i). daca nodul 9 a fost inserat în subarborele T2D.

• Algoritmul Heap_Sort Arborii heap nu se studiaza complet în acest curs. 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. daca ∃ left_child(v) key(v) > key(right_child(v)). 2) ∀ un nod v ∈ V cu degree(v) > 0 (nu este nod terminal). cheia nodului este mai mare decât cheile descendentilor). daca ∃ right_child(v) (Pentru fiecare nod din arbore.Structuri de date si algortitmi _________________________________________________________________________ 1) ∃ functia key : V → R care asociaza fiecarui nod o cheie. Aplicatii ale arborilor heap • Coada cu prioritate. atunci: key(v) > key(left_child(v)). _________________________________________________________________________ 60 .

Implementarea cozii cu prioritati prin lista permite definirea operatiilor insert si remove.a) .extrage din structura atomul cu cheia cea mai mare. una este de complexitate O(1) si cealalta este de complexitate O(n). insert: _________________________________________________________________________ 61 . Consideram. 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 15 7 42 Operatiile insert si remove pentru arbori heap au o forma foarte simpla când utilizeaza reprezentarea implicita.Structuri de date si algortitmi _________________________________________________________________________ Curs 10 Coada cu prioritati Este o structura de date pentru care sunt definite urmatoarele operatii: insert (S. remove (S) . Exemplu: un arbore cu ultimul nivel având toate nodurile aliniate la stânga: • • Inseram valoarea 42 ⇒ se adauga nodul la un nivel incomplet.insereaza un atom în structura. 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). în cazul cel mai bun. O coada cu prioritati poate fi implementata printr-o lista. arbori heap în reprezentare implicita. În reprezentarea implicita se adauga nodul la sfârsit. în continuare.

// ambele sunt plasate // prin referinta (functia insert // le poate modifica). 2) Se utilizeaza interschimbarile. // 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]. Structura S este un arbore heap.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.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 _________________________________________________________________________ 62 . // N  numarul de noduri din heap. a) // V  vectorul ce contine reprezentarea // implicita a heapu-lui. El se afla în reprezentare implicita în 2 informatii: V  vector N  dimensiune Operatia insert: insert(V. N. 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.

max este în stânga ⇒ retrogradarea se face în stânga. rchild). si aceasta valoare va fi retrogradata pîna când structura heap-ului este realizata. se reorganizeaza structura arborilor: se ia ultimul nod de pe nivelul incomplet si-l aduc în nodulradacina. 2k ≤ N ≤ 2k+1 . Daca N este numarul de noduri din arbore. 2k . conditia este îndeplinita deodata. 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]. de noduri ale arborelui ale arborelui complet de complet de adâncime k adâncime k+1 _________________________________________________________________________ 63 . de noduri nr. parent conditia de heap: / \ lchild rchild parent = max(parent. complexitatea este data de adâncimea arborelui. max este în dreapta ⇒ retrogradarea se face în dreapta. La insert avem o comparatie. se initializeaza cei 2 indici. Rezulta.1 < N ≤ 2k+1 . 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. Exista trei cazuri: 1. iar la remove avem doua comparatii. si adâncimea arborelui este k+1. 2. remove(V. lchild. 3.Structuri de date si algortitmi _________________________________________________________________________ • • se scoate elementul cel mai mare care este radacina heap-ului.1 ⇒ k = [log2N] | | | | | | nr.

n) { heap_gen(V. Deci complexitatea este O(log N). n) N = n for i = n downto 2 step -1 do |¯ N = i |_ V[i] = remove(V. La inserarea lor se va face cel mult o retrogradare (comparatie). avem: nivelul 2 : 2 noduri 1 comparatie nivelul : 4 noduri 2 comparatii nivelul 4 : 8 noduri 3 comparatii -------------------------------------------------nivelul i : 2i-1 noduri i-1 comparatii _________________________________________________________________________ 64 min . Pe ultimul exemplu de arbore. 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 partea sortata i a vectorului max Procedura Heap_Sort prin inserari repetate heap_sort { N = 1 //se considera pentru început un // heap cu un singur //element. • Se fac n-1 opera_ii insert în heap-uri cu dimensiunea N ≤ n Rezulta complexitatea acestor operatii nu depaseste 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 adaugând un nod la ultimul nivel. Algoritmul Heap_Sort Heap_Sort(V. V[i]) } Studiul complexitatii Observatii: • Se fac mai multe operatii insert în heap-uri a caror dimensiune creste de la 1 la N. N. Facem un studiu pentru a vedea daca nu cumva ea este mai mica decât O(n⋅ log n). dupa care toate celelalte // elemente vor fi //inserate în acest heap for i = 2 to n do insert(V. A doua aplicatie a heap-urilor este algoritmul Heap_Sort. Pentru nivelul 2 sunt doua noduri.Structuri de date si algortitmi _________________________________________________________________________ k ≤ log2N < k + 1 ⇒ adâncimea arborelui este k = [log2N].

rezulta complexitatea O(n⋅ log n).. − ( k −1) ⋅ 2 k −1 = k −1 i =0 = −2 + ( k −1) ⋅ 2 k − ∑ 2i = ( k −1) ⋅ 2 k − 2 + 20 + 21 − ∑ 2i = = ( k −1) ⋅ 2 k +1 − ( 2 k −1) = ( k − 2) ⋅ 2 k + 2 Rezulta: iar: din Rezulta: T (n) = (k − 2) ⋅ 2 k + 2 = ( k − 2) ⋅ ( 2 k −1) + k − 2 + 2 = n ⋅ (k − 2) + k k = log 2 ( n +1) . n = 2 k −1 T ( n) = n ⋅ (log 2 (n +1) − 2) + log 2 ( n +1) -----------------------termen dominant Facându-se majorari. Cele mai multe noduri sunt la baza. I II noduri terminale i Elementele V[i+1. Prezentam acum alta strategie de obtinere a heap_gen cu o complexitate mai buna: Construim heap-ul de jos în sus (de la dreapta spre stânga). Vom calcula: T (n) = ∑(i −1) ⋅ 2i −1 i =2 k Sa aratam: cu tehnica k −1 i =1 T ( n) = ∑i ⋅ 21 T ( n) = 2 ⋅T ( n) −T ( n) .Structuri de date si algortitmi _________________________________________________________________________ Considerând un arbore complet (nivel complet) ⇒ N = 2k . k −1 i =1 4 k −1 i =1 k −1 i =1 k −1 i =1 Asadar: T ( n) = 2 ⋅ ∑i ⋅ 2i −∑i ⋅ 2i = ∑i ⋅ 2i +1 − ∑i ⋅ 2i = = 1 ⋅ 2 + 2 ⋅ 2 + 3 ⋅ 2 + .. doar nodurile din vârf parcurg drumul cel mai lung.. + ( k − 2) ⋅ 2 2 3 k −1 i =2 k −1 + (k −1) ⋅ 2 k −1 ⋅ 21 − 2 ⋅ 2 2 − 3 ⋅ 23 − .1 ⇒ numarul total de comparatii pentru toate nodurile este T(n) de la nivelul 2 la nivelul k.N] îndeplinesc conditia de structura a heap-ului: _________________________________________________________________________ 65 ..

. daca 2*j + 1 ≤ N Algoritmul consta în adaugarea elementului V[i] la structura heap-ului. El va fi retrogradat la baza heapului (prelucrare prin retrogradare): retrogradare(V.. n.Structuri de date si algortitmi _________________________________________________________________________ ∀ j >i avem: V[j] > V[2*j] .. i) } Complexitatea acestei operatii Fie un arbore complet cu n = 2k ..1. + 3 ⋅ 2 k −3 + 2 ⋅ 2 k −2 − − (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) − 2 0 + ∑ 21 = i =1 i =0 k −3 k −3 =2 k −1 +2 k −2 − 2 − (k −1) = 2 k −2 ⋅ (2 +1) − k −1 = 3 ⋅ 2 k −2 − k −1 _________________________________________________________________________ 66 . N. vom avea: heap_gen { for i = [N/2] downto 1 step -1 do retrogradare(V. 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 } În aceasta situatie. daca 2*j ≤ N V[j] > V[2*j +1] . si rezulta: Tehnica de calcul este aceeasi: k −2 i =1 k −2 i =1 T ( n) = ∑( k − i ) ⋅ 2i −1 T ( n) = 2 ⋅T ( n) −T ( n) i =1 k −1 T (n) = ∑(k −1) ⋅ 2i − ∑( k − i ) ⋅ 2i −1 = (k −1) ⋅ 21 + (k − 2) ⋅ 2 2 + (k − 3) ⋅ 23 + . Cazul cel mai defavorabil este situatia în care la fiecare retrogradare se parcurg toate nivelele: nivel k : nu se fac operatii nivel k-1 : 2k-2 noduri o operatie de comparatie nivel k-2 : 2k-3 noduri 2 operatii -----------------------------------------------------------------nivel i : 2i-1 noduri k-i operatii ------------------------------------------------------------------nivel 2 : 21 noduri k-2 operatii nivel 1 : 20 noduri k-1 operatii Se aduna.

Heap_Sort = O(n·log n) _________________________________________________________________________ 67 . Comparând cu varianta anterioara. Rezulta. în aceasta varianta (cu heap-ul la baza) am câstigat un ordin de complexitate. Rezulta: Heap_Sort = O(n) + O(n·log n) --------------termen ce determina complexitatea Rezulta complexitatea alg.Structuri de date si algortitmi _________________________________________________________________________ Rezulta: T(n) = 3⋅ 2 − k − 1 〈 3⋅ (2 − 1) − k − 1 T (n) 〈 3⋅ n − l o2(n + g)1 − 1 k ------termen dominant k− 2 Rezulta complexitatea este O(n). complexitatea algoritmului Heap_Sort este determinata de functiile remove ce nu pot fi aduse la mai putin de complexitate O(log n).

• i. • Ei ∪ E'i  multimea vecinilor. Drumul D este neorientat. • E'i = { j. astfel încât V'⊂ V. Avem: grafuri orientate în care (i. (i. ∃ D un drum de la x la y. în care V  este o multime de noduri finita. k = 1. Un graf este complet daca fiecare nod este conectat cu oricare din celelalte noduri: E = V× V \ {(i. j) ∈ E}  multimea de vecini la iesire. i) ∈ E}  multimea de vecini la intrare. E ≤ V× V.│V│ = n. • Ei = { k. . • (i.neorientat). Definitie Se numeste drum orientat într-un graf de la x la y secventa de noduri D = (i1 = x.. ik+1) ∈ E. ik+1) ∈ E sau (ik+1. i) ∈ E Observatie: Un arbore este un caz particular de graf. j) ≠ (j. i2.Structuri de date si algortitmi _________________________________________________________________________ Curs 11 Grafuri. G se numeste graf. n . . • j se numeste succesor al lui i. (ik. Definitii: • i se numeste predecesor al lui j. in = y). i ∈ V} Definitie Fie G = (V. (k. E) . j)  este adiacent cu i si j. │V│ = n . j  adiacente. i. si E  o multime de arce. j). i) grafuri neorientate în care (i. E'). grad_intrare(i) = in_degree(i) = │Ei│ grad_ie_ire(i) = out_degree(i) = │ E'i│ Pentru un graf neorientat (G . Se numeste subgraf al grafului G.. E) un graf. Se determina matricea de adiacenta _________________________________________________________________________ 68 . Ei ≡ E'i si degree(i)= │Ei│. ik) ∈ E Definitie Se numeste graf conex graful pentru care ∀ doua noduri (x. Traversari pe grafuri Definitie: Fie G = (V. y) ∈ V. i). un graf G' = (V'. j) ∈ E → (j. E) o multime. j ∈ N }. daca (ik. E' ≤ (V'× V') ∩ E Reprezentari 1) Matrice de adiacenta Fie G = (V. E = {(i.

j ) ∉ E Complexitate: O(1). Astfel de grafuri se reprezinta prin matrici de colectivitate: 0 11 0 0   0 0 0 0 1    0 0 0 0 0   010 01   1 0 0 0 0  C (i . │V│ = n . n}. outdegree(i) O(n2). L(i) → Ei Exemplu: _________________________________________________________________________ 69 ... j) ∈ E O(n). fiecare pointer continând adresa unei liste dupa regula urmatoare: L(i) da adresa listei înlantuite care contine toti succesorii lui i. j ) = 1 (i . j ) = 0 (i . n). (i . indegree(i). j ) ∉ E Un graf este etichetat daca ∃ o functie definita pe E → R. Se numeste lista de adiacenta asociata acestui graf o colectie de n pointeri < (1. . .Structuri de date si algortitmi _________________________________________________________________________ astfel: M n × n . adica fiecarui arc i se asociaza o valoare. j ) ∈ E 0 (i . V = {1. E'i. spatiu 1) Liste de adiacenta directa Definitie Fie G = (V.. M (i . j ). 2. E) un graf.. 2. j ) ∈ E + ∞. (i. . Ei. (i . .

E. 0) — ext // procesare() procesare(i) mark(i) = 1 for fiecare k ∈ V cu (i.j) ∈ E O(│E│)  cardinal de E L'(1.. prin toate nodurile. i) { // mark(1. n).Structuri de date si algortitmi _________________________________________________________________________ 1 2 3 0 2 3 0 3 0 4 2 5 0 5 1 0 3) Liste de adiacente inverse (i. 2..j) adiacente. n) initializat cu // (00 .... L'(i) → E'i Ei: O(│E│) sau O(maxout_degree) E'i: O(│E│) spatiu: O(│E│ + n) 4) Liste de adiacenta dubla (i. i) { // mark(1.. e(i. traversari La grafuri se folosesc operatii între elemente ((i.j. pentru un scop bine definit. Se numeste traversare pe graf o procedura de traversare. . operatii de traversare pe grafuri..j) .j)) Operatii pe grafuri. 0)  ext _________________________________________________________________________ 70 .. etc).. (i...k) ∈ E |¯ if mark(k) = 0 |_dfs(k) } bfs(G. Exista: traversari în adâncime — depth first traversari în latime — bmedth first Proceduri: dfs(G. n) initializat cu (0 .

j) ∈ E | |¯ if(mark(j)=0) | | |¯ procesare(j) | | | mark(j) = 1 |_ |_ |_ put(q. i) while(isEmpty(q) = N0)) do |¯ k = pop(q) | for fiecare j ∈ V .j) } 1) Matrice adiacenta dfs(M. Se numeste arbore de acoperire un arbore T = (V. E') . E) un graf. i) { procesare(i) mark(i) = 1 for k = 1 to n |¯ if (M(i.Structuri de date si algortitmi _________________________________________________________________________ // o coada q → isempty. a= pop(p). bfs produc arbori de acoperire.k) = 1) | |¯ if mark(k) = 0 |_ |_ dfs(k) } 2) Liste de adiacenta directa dfs(L. cu E' ≤ E Observatie: Algoritmii dfs. (k. _________________________________________________________________________ 71 . put(q)  ext procesare(i) mark(i) = 1 put(q. 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.

You're Reading a Free Preview

Download
scribd
/*********** DO NOT ALTER ANYTHING BELOW THIS LINE ! ************/ var s_code=s.t();if(s_code)document.write(s_code)//-->