Sunteți pe pagina 1din 22

Liste sortate

Intr-o lista sortata elementele sunt dispuse in ordinea crescatoare (sau descrescatoare) a valorilor
unor chei. In general, listele sortate se pot folosi in situatii similar celor in care se folosesc tablourile
sortate.
Avantajele listelor constau in :
-viteza inserarii (nefiind necesare deplasari de elemente)
-aceea ca o lista se poate extinde pana cand ocupa toata memoria disponibila, in timp ce tabloul este
limitat la o dimensiune fixata.
Dificultatea implementarii constituie un dezavantaj de luat in seama pentru listele inlantuite.
O operatie des folosita este inserarea unui element intr-o lista ordonata ea se face astfel:

e1

e2

ei-1

ei

ei+1

ei-1 ei ei+1 i=2,n-1 pentru lista ordonata crescator


Daca se doreste inserarea unui element e se cauta pozitia de inserare, fie ea j, pentru care ej-1e
e1

ej-1

ej

en

e
Functia de inserare intr-o lista ordonata crescator:
LIST insert_loc(LIST l, int k)
{
LIST aux, capat ;
aux=cons (k, NULL);
if((!l)||(k<=lk))
{
aux next=l;
return (aux);
}
else
{
capat=l;
while ((lnext)&&(lnextk<k))l=lnext;
aux next=l next;
lnext=aux;
return (capat);
}
}
O aplicatie des intalnita a listelor sortate este sortarea prin insertie intre lista ordonata crescator.
Ex.
25

Plecam cu lista vida


Pas 1 25

Pas 2 6
25

16

45

19

Pas 3
Pas 4
Pas 5
Pas 6

6
3
3
3

16
6
6
6

25
16
16
16

25
25
19

45
25

45

Functia de sortare prin insertie intr-o lista ordonata crescator:


LIST sortare_prin_insertie (LIST l)
{
LIST aux; aux=NULL;
if (!l)return(l);
else
{
while (l)
{aux=insert_loc(auxmlk); l=lnext;}
return (aux);
}
}
Eficienta listelor inlantuite ordonate
-inserarea|stergerea unui element oarecare: O(N); a elementului minim: O(1);
-cautarea elm. minim: O(1); elm. oarecare: O(N);
-sortarea prin insertie: O(N2), dar se evita deplasarile de elemente care au loc in cazul tablourilor.
Liste dublu inlantuite: elimina dezavantajul listei simplu inlantuite privind parcurgerea listei in
sens invers. Ex. de folosire: o lista care pastreaza liniile de text introduse folosind un editor de texte.
Cand cursorul este deplasat la linia urmatoare se poate trece usor urmand legatura text. Daca
acesta este mutat in linia precedenta, se va urma pointerul prev.
next
prev
linia 1

linia 2

typedef struct celula_2lista


{ int k;
struct celula_2lista next, prev;
} LIST2;
Pretul platit pentru cresterea in flexibilitate consta in:
-fiecare celula de lista va ocupa ceva mai multa memorie datorita pointerului in plus prev;
-la fiecare inserare|stergere trebuie modificate 4 legaturi in loc de 2.
Functii de lucru cu liste dublu inlantuite:
1)
LIST2 cons_2inl(int k, LIST2 l)
{ LIST2 aux;
aux=(LIST2)malloc(sizeof(LIST2));
auxk=k; auxnext=l; auxprev=NULL; lprev=aux;
return (aux);
}
2)
LIST2 reverse_2inl(LIST l)
{ LIST2 pred, salv;
pred=NULL;
while(l)
{ salv=lnext; lnext=pred; lprev=lnext;
pred=l; l=salv;
}
return(pred);
}

3) Eliminarea unui element dintr-o lista dublu inlantuita


LIST out_2inl(LIST2 l, int k)
{ LIST2 capat, p;
if ((!l)||(k<lk))return(l);
else
if (lk==k)return(lnext);
else
{capat=l;
while ((lnext)&&(lnextk!=k))l=lnext;
if (lnext)
{ p=lnext;
if (lnextnext)lnextnextprev=l;
lnext=lnextnext;
}
else printf (cheia de eliminat nu apartine listei \n);
free (p);
return (capat);
}
}
4) Se da o lista dublu inlantuita si un numar x. Se cere sa se extraga intr-o alta lista simplu
inlantuita, cu header, p celule din stanga celei cu numarul x si p celule din dreapta ei astfel:

csp cs1

cd1 .. cdp
x

p=2
st

cs1

csp

dr

cd1

cdp

algoritm extractie
*se initializeaza lista dublu inlantuita
*se citeste numarul x de la tastatura
*se cauta numarul in lista si daca se gaseste se intoarce adresa celulei in care a fost gasit
*se construieste lista care contine celulele din stanga lui x
cs1,cs2 csp
*se construieste lista care contine celulele din dreapta lui x cd1,cd2 cdp
*se construieste header-ul ca o structura cu 3 campuri: un pointer catre lista stanga, numarul x si un
pointer catre lista dreapta
*se afiseaza lista cu header
stop

STIVE SI COZI
Atat stivele cit si cozile sunt SD care modeleaza colectii de elemente pentru care doar un element
este vizibil la un moment dat. Alegerea sa in vederea prelucrarii se face pe baza ordinii in care
elementele au fost introduse in colectie:
Stiva: LIFO (Last In First Out)
Coada: FIFO (First In First Out)
Obs.: TDA stiva este similar TDA lista cu exceptia ca in lista poate fi accesat, folosind operatorii
specifici TDA, orice element. Ca urmare:
*o stiva poate fi definita ca un tip particular de lista in care toate operatiile de inserare si stergere se
fac la o extremitate: varful stivei
inserare
inserare
iesire
O(1)
elemente
elemente
stergere

*o coada este un tip particular de lista in care inserarea se face la sfarsitul cozii, iar eliminarea la
inceputul ei
inserare
O(1)
stergere
iesire elemente

inserare elemente

TDA stiva
tip Stack [GenElm]
Domenii
GenElm: multimea elementelor generice care pot fi stivuite
Bool: set de constant logice
StackSet: multimea stivelor cu elemente din GenElm
Operatori
newStack: Stack Set // construieste stiva vida; este constructor de baza si apartine Conb
push: StackSet x GenElm Stack Set // adauga un element in varful stivei; apartine Conb
pop: StackSet Stack set // elimina varful stivei
gettop: StackSet GenElm // intoarce elm. din varful stivei
isEmpty: StackSet Bool
clearStack: StackSet StackSet // elimina toate intrarile din stiva
Restrictii (pre-conditii)
pop, gettop !isEmpty (s)
clearStack !isEmpty (s)
Axiome pentru oricare s StackSet si oricare e GenElm
pop (push (s,e))=s
gettop(push(s,e))=e
isEmpty(newStack)=true
isEmpty(push(s,e))=false
clearStack(push(s,e))=newStack

Implementari posibile

Cu vector:
1

typedef struct stiva


{ int maxsize, top;
GenElm StackArray [MaxSize];
}STACK;

top

maxsize
StackArray

(top)

Cu lista inlantuita
s
.

typedef struct celula_stiva


{ GenElm StackElm;
struct celula_stiva next;
} STACK;
STACK s;

Aplicatii simple pentru stive (in pseudocod)


1) Inversarea unui sir de caractere
alg inversare_sir este
*preluarea sirului de intrare, inputString, de la tastatura si afisarea sa /ecou date de intrare/
*construieste stiva vida s
for (i=0; i<length(inputString); i++)
push(s, inputString[i]);
*initializarea sirului de iesire, in variabila outputString, cu sirul vid
while (!isEmpty(s))
{ ch=gettop (s); outputString=concat (outputString, ch);
}
*afisare outputString
sfirsit
2) Implementarea functiilor recursive exemplu
Stiva in 3 momente de timp
10 void main ( )
{
cand main isi
cand f1 isi
cand f2 isi
int a=1;
incepe executia
isi incepe executia isi incepe executia
50 int b=f1(a);

} /*end main*/
100 int f1 (int x)
{
int c=2;
f2
125 f2 (c);
PC=150

y=2
return (c);
f1
f1
} /*end f1*/
PC=100
PC=125
150 void f2 (int y)
x=1
x=1
{
c=2

main
main
main
} /*end f2*/
PC=10
PC=50
PC=150
a=1
a=1
b=0
b=0

Program - PC (Program Counter) indica instructiunea curenta


- varf stiva: functia curenta aflata in executie
- AR(Activation Record): nume si argument functie, var. locale, o copie a PC
Obs.: Acest exemplu ilustreaza elocvent de ce uneori se intampla ca stiva sa fie depasita
(overflow) de multitudinea apelurilor recursive, care implica incarcarea in stiva a inregistrarilor de activare
(AR) corespunzatoare functiilor apelate.

Aplicatie complexa pentru stiva: Evaluarea expresiilor aritmetice


Scop: evaluarea expresiilor de forma (a+b)*c
Deoarece evaluarea prin program a expresiilor in forma infixata, ca atare, este practic imposibila (ar
trebui sa inglobeze toate cunostintele de matematica din primii ani de scoala :), se foloseste un artificiu: se
transforma expresia din forma infixata in cea postfixata, numita si notatie poloneza inversa (ab+c*), dupa
care se evalueaza expresia postfixata.
Etapa 1. Conversia expresiilor din forma infixata
Ex.1: a+bc

postfixata

Caracterul
urmator
in expresie
a
+
b
*
c

Expresia postfixata
partiala

Stiva operatorilor
(varf spre stinga)

a
vida
a
+
ab
+
ab
*+
abc
*+
abc*
+
abc*+
vida
Sa observam ca operatorul *a trebuie pus in stiva deoarece el avea prioritate (precedenta) mai mare
decat + si, deci, b nu putea fi al II-lea operand al sumei.
Cand se intalneste un:
-operator, el trebuie salvat pana se
determina care-i sunt operanzii
-operand, el se adauga la sfarsitul
expresiilor postfixate partiale

Ex.2: Operatori succesivi cu aceeasi precedent


a) a-b+c (asociere stdr)
+ si - au aceeasi prioritate, se
extrage - din stiva si se adauga
la sfarsitul expresiilor postfixate

a
b
+
c

b) abc (asoc.dr st)

a
a
ab
ababab-c
ab-c+

vida
vida
+
+
vida

a
a
ab
ab
abc
abc
abc

vida
^
^
^^
^^
^
vida

a(bc)

Desi care urmeaza dupa b are


aceeasi prioritate cu cel din
varful stivei, datorita asoc.
drst, va fi depus si el in stiva,
pana i se stabileste al II-lea
operand.

Ex.3: Daca expr. de evaluat contine paranteze acestea eludeaza regulile de precedenta ale
operatorilor fortand evaluarea partilor de expresie din interiorul parantezelor. Cand se intalneste o
paranteza deschisa ea este incarcata pe stiva. O data ajunsa acolo, ea va fi tratata ca un operator de
precedent minima. Ca urmare, orice operator consecutiv ei va fi si el incarcat in stiva. Cand in sirul de

intrare se gaseste paranteza inchisa corespunzatoare (prima), se descarca toti operatorii gasiti pana la
paranteza deschisa si sunt concatenati la sfarsitul expresiei partiale.
a (b+c)
a
a
vida
*
a
*
(
a
(*
b
ab
(*
+
ab
+(*
c
abc
+(*
)
abc+
(*
abc+
*
abc+*
vida
Ca urmare a celor reiesite din exemple rezulta algoritmul urmator:
alg Conversie_infixata_in_postfixata este
*se construieste operatorStack ca stiva vida
*se initializeaza variabila postfixata cu sirul vid
Se preiau operatori din stiva
si se concateneaza la
while * in expr. infixata mai sunt caractere de parcurs do
postfixata pana cand stiva
{ next C urmatorul caracter non_blank din expr. infixata
este vida sau varful sau este
switch (nextC)
precedenta < decat noul
{ case variabila: append (postfixata, nextC); break;
operator. Apoi noul operator
case : push (operator Stack, nextC); break;
este depus in stiva.
case + or - or * or /:
while (!isEmpty(operator Stack) and
precedence
(nextC)<=
precedence
(gettop(operatorStack)) do
{append (postfixata, gettop (operatorStack));
pop (operatorStack);
Se preiau operatori din stiva
}push (operatorStack, nextC); break;
si se concateneaza la
case (: push (operatorStack, nextC); break;
postfixata
pana
se
descarca paranteza (
case ): if (!isEmpty(operatorStack))
{ topO gettop (operatorStack);
while (topO != C)
{ append(postfixata, topO);
topO gettop (operatorStack);
pop (operator Stack); /* se descarca si paranteza deschisa */ break;
pop (operator Stack);
}}
default; break; } /* end switch */ } /* end while */
Ex: a/b*(c+(d-e))

next C
a
/
b
*
(
c
+
(
d
e
)
)

postfixata
a
a
ab
ab/
ab/
ab/
ab/c
ab/c
ab/c
ab/cd
ab/cd
ab/cde
ab/cdeab/cdeab/cde-+
ab/cde-+
ab/cde-+*

operatorStack
vida
/
/
vida
*
(*
(*
+(*
(+(*
(+(*
-(+(*
-(+(*
(+(*
(+(*
(*
*
vida

Etapa 2. Evaluarea expresiilor postfixate


-pleaca de la observatia ca de fiecare data cand se intalneste un operator el se aplica ultimilor 2
operanzi intilniti in parcurgerea de la stanga la dreapta.
a*(b+c) abc+*
(b+c)

Se va folosi o stiva in care daca intalnim un:


-operand: se insereaza in stiva
-operator: se extrag primii 2 operanzi si li se aplica operatorul in ordine inversa. Rezultatul se
insereaza in stiva la randul sau.

TDA coada
tip queue [GenElm]
domeniu
GenElm: multimea elementelor generice ale cozii
Bool: set constant logice
NatInt: multimea numerelor naturale
queueSet: multimea cozilor cu elemente din GenElm
operatori
newQueue: queue Set // apartine Conb; construieste coada vida
addQueue: queueSet x GenElm queueSet // apartine Conb; adauga un element la sfarsit
remQueue: queueSet queueSet // elimina un element de la inceput
getFront: queueSet GenElm intoarce elementele de la inceputul cozii
isEmpty: queueSet Bool
lengthQueue: queueSet NatInt // determina lungimea cozii
restrictii
newQueue (q), getFront (q) !isEmpty (q)
axiome pentru orice q queueSet si orice e GenElm
newQueue (addQueue(q,e))=if isEmpty (q) then q else addQueue (remQueue(q),e)
getFront (addQueue(q,e))=if isEmpty (q) then e else getFront (q)
isEmpty (addQueue(q,e))=false
lengthQueue (newQueue)=0; lengthQueue (addQueue(q,e))=1+lengthQueue (q)
Implementari posibile

Cu vector circular:

rear

front

Obs.
-front indica inceputul cozii
-rear point-eaza catre prima pozitie libera
-daca front = rear atunci coada poate fi vida
sau plina => se foloseste un indicator
boolean care sa arate care este cazul
-la o adaugare: rear (rear+1) modulo n
-daca nu s-ar folosi un vector circular
pozitiile ramase libere la inceputul
vectorului, in urma stergerilor, nu ar mai
putea fi folosite pentru noi inserari.

typedef struct coada


{ int front, rear max size;
GenElm queueArray [MaxSize];
int empty Q;} QUEUE;

Obs

alaturata), fie la apelul operatorului newQueue, in executie.

QUEUE *q;

.: Alocarea de spatiu se va face fie la compilare (ca in definitia

-se prefera lucrul cu pointeri catre cozi/stive pentru a


evita transferul lor prin valoare, care ar presupune
copierea lor pe stiva programului.

Cu lista cu header:

front

typedef struct celula_coada


{ GenElm queueElm;
struct celula_coada *next;
} LIST;
typedef struct header_coada
{ LIST *front, *rear } QUEUE ;

rear

Un caz particular de coada, deosebit de folosit in practica, este coada cu prioritati, in care
elementele sunt ordonate (crescator/descrescator) dupa prioritati. Stergerile se fac tot la inceputul cozii,
avand complexitatea in O(1). Inserarile se fac intr-o pozitie corespunzatoare prioritatii elementului inserat,
deci in O(N), dupa se lucreaza cu vector sau lista. Se poate obtine o eficienta mai mare daca se foloseste o
SD numita heap, care va fi prezentata in ultimul capitol.
Ex. de folosire: cozile care se formeaza pentru resurse in SO de timp real, primul in coada fiind nu
procesul cel mai vechi, ci acela care are prioritatea maxima.
Cea mai flexibila implementare cu liste este cea de mai jos deoarece ofera acces facil la elemente,
in ambele sensuri:

front

Setul de operatori se poate complete cu:

rear

Min, Max, remMin, remMax

Aplicatie: sortarea dupa ranguri


Ex. 1
v:

0 1 2 3 4 5 6 7 8 9 10
1 1 0 2 1 0 0 1 2 1 0

Chei simple (monolitice)

0 0 0 0
1 1 1 1 1
2 2
Concatenare: 0 0 0 0 1 1 1 1 1 2 2

vq:

0
1
2

10
7

10

Ex. 2
v:

15 41 23 12 32 42 31 25 33

Dupa rangul 1: vq
1
2
3
5
Dupa concatenare:

Chei compuse

41 31
12 32 42
23 33
15 25

41 31 12 32 42 23 33 15 25

Dupa rangul 2: vq

1
2
3
4

12 15
23 25
31 32 33
41 42

Dupa concatenare: 12 15 23 25 31 32 33 41 42
#include <stdio.h>
#include <conio.h>
#include <alloc.h>
#include coada.c
#include cit_af_v.c
void Radix_sort (int n, int m, int v[ ]) /*n este numarul de elemente ale vectorului v, iar m nr de ranguri*/
{ int i, j;
QUEUE vq[10];
for (i=0; i<m; i++) newq (vq[i]);
/*newq corespunde lui newQueue*/
for (i=0; i<n; i++)
{
addq (vq[v[i]],v[i]);
/* addq corespunde lui addQueue*/
/*printf (\n coada %d, v[i]); printq (vq[v[i]]);*/
}
for (j=1=0; i<m; i++) {
/*printf (\n coada %d,i); printq (vq[i]);*/
while (!emptyq (vq[i]))
/*emptyq corespunde lui isEmpty*/
{
v[j++]=frontq (vq[i]);
/*frontq corespunde lui getFront*/
remq (vq[i];
/*remq corespunde lui remQueue*/
/*printf (\n coada %d dupa remq:, i); printq (vq[i]);
}
free (vq[i];
}
}
main ( )
{ int n, m, v[30];
clrscr ( );
printf (\n intoarceti nr. de elemente ale vectorului=); scanf (%d, &n);
printf (\n intoarceti nr. de valori posibile pentru chei=); scanf (%d, &m);
citire_v (n, v);
Radix_sort (n, m, v);
printf (\n Dupa sortare cu metoda Radix Sort, vectorul devine: \n);
afisare_v (n, v);
getch( );
}
#include <stdio.h>
#include <conio.h>
void citire_v (int n, int v[ ])
{ int i;
printf (\n Initializati vectorul:);
for (i=0; i<n; i++)
{ printf (\n x[%d]=,i);
scanf (%d, &v[i]);
}
}

/*cit_af_v.c*/
void afisare_v (int n, int v[ ])
{ int i;
printf (\n vectorul este:);
for (i=0; i<m; i++)
printf ( %d, v[i]);
printf (\n);
}

#include <stdio.h>
#include <conio.h>
#inclide <alloc.h>
typedef struct lista
{ int k;
struct lista *next;
} LIST;
typedef struct coada
{ LIST *front, *rear;
} *QUEUE;
void newq (QUEUE q)
{ q=(QUEUE*)malloc(sizeof(QUEUE));
q front=q rear=NULL;
}
int emptyq (QUEUE q)
{ if ((q front==q rear)&&(q front==NULL))
return (1);
else return (0);
void printq (QUEUE q)
{ LIST * 1;
1=q front;
if (emptyq(q)) printf (coada vida \n);
else do { printf (%d, 1 k); 1= 1 next; }
while ( 1);
printf (\n);
}
void addq (QUEUE q, int e)
{ LIST *aux, int f;
if (emptyq(q))f=1; aux=(LIST*)malloc(sizeof(LIST));
aux k=e; aux next=NULL;
if (f==1) /*e primul element*/
q front=q rear=aux;
else
{ q rear next=aux;
q rear=aux;
}
}
void remq (QUEUE q)
{ LIST *out;
if (! empty q(q))
{ out=q front;
if (q front next)q front=q front next;
else q front=q rear=NULL;
free (out);
}
else printf (coada era vida!);
getch ( );
}

int frontq (QUEUE q)


{ if (! empty q(q))
return (q front k);
else printf (\n coada este vida);
}

Arbori
Arborii au aparut din necesitatea de a structura date ierarhice si, in plus, s-au dovedit a cumula
avantajele oferite de tablouri ordonate (cautari rapide) si de listele inlantuite (inserari/stergeri eficiente).
Un arbore este, deci, o SD care furnizeaza o organizare ierarhica pentru datele pe care le reprezinta,
spre deosebire de tablouri, liste, stive si cozi, care sunt structuri liniare. Non-liniaritatea arborilor este cheia
proprietatilor lor speciale.
Exemple de folosire a arborilor:

Organigrama unei firme:


director
general

director tehnic

director de
marketing

sef
compartiment
"Proiectare"

Sef
compartiment
"Realizare"

...........

Sef
compartiment
"Prototipuri"

Sef
compartiment
"Parte A"

............

director
economic

director de
persoane

..........

.............

Arborele genealogic
Sistemul de fisiere dintr-un sistem de operare
Cuprinsul unei carti
Reprezentarea structurii formulelor matematice
Reprezentarea elementelor unei multimi

Terminologie
Un arbore poate fi vazut ca un set de noduri care pot fi etichetate, conectate prin arce, care arata
relatiile dintre acestea. Nodurile sunt asezate pe niveluri corespunzatoare pozitiei in ierarhie. Pe nivelul cel
mai de sus se afla un singur nod, numit radacina. Nodurile de pe fiecare nivel sunt copiii(fiii) nodurilor de
nivel precedent. Un nod care are fii este parintele (tatal) nodurilor descendente lui. Nodurile care au acelasi
tata sunt frati, iar tatal este stramosul lor comun. Daca un nod nu are copii el se frunza. Nodurile care au
copii se numesc noduri interioare (non-frunza). Orice nod are un parinte unic, cu exceptia radacinii, care nu
are nici-un parinte. In general, fiecare nod dintr-un arbore poate avea un numar arbitrar de fii un astfel de
arbore se numeste arbore general (arbore m). Daca numarul de fii se reduce la maxim 2
arbore binar.

Definitia matematica a unui arbore arata ca acesta este un graf orientat aciclic (DAG) Arb=(N,A),
A NxN care satisface conditiile:
noduri arce
-exista si este unic un nod r N astfel incit i_grad(n)=0, numit radacina
-oricare ar fi n N, n r, i_grad(n)=1, unde i_grad(n)=nr. de arce care intra in nodul n.

Se mai definesc si:


Frunzele ca nodurile n N astfel incit e_grad(n)=0 (e_grad(n)=nr. de arce care ies din nod)
Predecesorul unui nod n este n astfel incit (n,n) A
Succesorul unui nod n este n astfel incit (n, n) A

0 n = radacina 
nivel predn + 1 n radacina

Nivel (n)=

Arborele vid ( ) ca arbore fara noduri

Inaltimea arborelui =

lunga din arbore de la radacina la o frunza.


Inaltimea unui nod oarecare = lungimea celui mai lung drum intre nodul respectiv si o
frunza. Deci inaltimea arborelui este inaltimea radacinii.
Ordinul unui nod ord(n)=e_grad(n), iar ordinul unui arbore Ord(ARB)=max{ord(n), n N}.
Daca ordinul unui arbore este 1, atunci el degenereaza intr-o lista. Pentru ordin=2 avem de-a
face cu arbori binari, iar pentru un ordin 3 avem de-a face cu arbore general (multicai)

0 pentru arborele vid 


, adica numarul de noduri pe calea cea mai
maxniveln/ + 1

Un subarbore al unui arbore dat se formeaza dintr-un nod si din toti descendentii sai in acel arbore,
pana la frunze, inclusiv. Se spune ca un subarbore este dominat de un nod n, daca radacina sa este un
successor direct al lui n.
Un arbore este ordonat daca pentru oricare n N, exista o relatie de ordine unica in multimea
subarborilor dominate de n.
Fie un arbore Arb, de ordin m si fie K multimea cheilor din nodurile sale. Se spune ca Arb este
arbore de cautare daca:
-exista o relatie de ordine in multimea K, fie ea si k1 k2 k3 km-1, m=1,n
-arborele este ordonat
-fiecare nod cu ordin mm contine exact (m-1) chei
-pentru oricare n Arb cu cheile k1k2km-1 exista exact (m) subarbori dominate de n astfel incit:
-daca SAi exista => pentru k SAi: kki

i=0,m-1
-daca SAi+1 exista => pentru k SAi+1: kik
K1K2 ... Ki ... Km'-1

Pentru m=2 se obtine un arbore binar de cautare, in care pentru orice nod n Arb, care nu este
frunza si contine cheia k, se respecta conditiile:
-daca exista SSn atunci pentru k SSn: kk

-daca exista SDn atunci pentru k SDn: kk, unde SSn, SS =subarborii dominati de n (stg/dr)
Se spune ca un arbore este perfect echilibrat daca:
nivel(n)=nivel(n) oricare n, n frunze din arbore
sau (def. echivalenta)
pentru n N si 2 subarbori dominati de n, >="h( )-h(=)"0
Un arbore este numit echilibrat daca pentru oricare n N si , "care sint subarbori dominati de n
=> |h( )-h( |)"1

Obs.: Echilibrarea unui arbore este importanta pentru a creste eficienta operatiilor de cautare. Pentru
arborii binari de cautare echilibrati se obtine O(log2N) in timp ce pentru arbori dezechilibrati, care
degenereaza spre liste, complexitatea este in O(N).
h=0
h=1
h=2
h=3

1 nod
1+21 noduri
1+21+22 noduri
1+21+22+23 noduri

0 frunze=20
2 frunze=21
4 frunze=22
8 frunze=23

N=1+21+22+2h

2h frunze

N=  2 ,

 dar  = 2 
1 =>   + 1 =>  

arbore plin
(full binary tree)
(complete binary tree)
arbore complet

arbore plin

Arborii, in functie de proprietatile de cautare si echilibrare, se clasifica in:


arbori binari de cautare
arbori binari de cautare echilibrati tip AVL
Acesta este primul tip de arbori echilibrati descoperiti. Intr-un astfel de arbore in fiecare nod se
memoreaza o informatie suplimentara, diferenta dintre inaltimea SS si a SD (aceasta trebuie sa fie
1). Dupa fiecare inserare se verifica radacina subarborelui aflat cel mai jos in arbore, in care s-a
inserat noul nod. Daca diferenta dintre inaltimile celor 2 fii este mai mare decat 1 atunci se va
executa o rotatie, simpla sau dubla, pentru a se egaliza cele 2 inaltimi. Procedeul se repeta, nivel cu
nivel, ascendent, pana se ajunge la radacina. Cautarea intr-un arbore AVL se face in O(logN) insa o
inserare/stergere presupune 2 parcurgeri, una descendenta (pentru determinarea locului de
inserare/stergere) si una ascendenta pentru reechilibrare.
arbori binari de cautare echilibrati bicolori. Nodurile din acesti arbori sunt colorate si pe parcursul
inserarilor/stergerilor se asigura pastrarea unor aranjamente prestabilite ale acestori culori, care
conserva proprietatea de echilibrare ale arborelui. Acesti arbori sunt mai eficienti decat arborii
AVL, deoarece elimina parcurgerea ascendenta pentru reechilibrare. Aceasta se face, descendent, in
paralel cu cautarea locului de inserare, tot prin rotatii locale.
arbori TRIE (reTRIEval)=arbori multicai de cautare cu cheile dispersate pe caile din arbore. Cheile
sunt secvente de caractere dintr-un alfabet oarecare (cifre, litere etc.). Se fol, in general pentru
regasirea informatiei (dictionare, prefix matching, compresii samd).
O(m_caractere_cheie): inserare, stergere, cautare
arbori B de grad g= arbori multicai de cautare perfect echilibrati in care:
-radacina poate contine 12g chei
-oricare n radacina contine g 2g chei
Se folosesc, in general, pentru stocarea datelor pe un suport extern de informatii. Pentru optimizarea
performantelor, de regula, fiecare nod dintr-un arbore B contine un bloc de date, de dimensiune
egala cu blocul de disc. Intr-un arbore stocat in memorie legaturile intre noduri se pastreaza prin
intermediul adreselor se memorie, insa pentru un arbore stocat pe disc se vor folosi adusele unor
blocuri de disc. Intr-un arbore B este foarte important ca nodurile sa fie cat mai pline posibil, astfel
ca in urma fiecarui acces la disc, facut pentru a citi informatia dintr-un nod, sa se acceseze o
cantitate maxima de informatie. Inserarile si stergerile, care conduc la modificarea structurii
nodurilor, vor respecta acest deziderat.

arbori 2-3-4 sunt arbori multicai de ordinul 4, in care:


-un nod care contine o singura cheie are obligatoriu 2 fii
-un nod care contine 2 chei are obligatoriu 3 fii
-un nod care contine 3 chei are obligatoriu 4 fii
Nu se permite existenta nodurilor vide si toate frunzele sunt pe acelasi nivel.
Inserarile se fac intotdeauna in frunze, altfel ar fi implicat modificari mult prea diferite pentru a
mentine numarul de fii corect pentru fiecare nod. Cand locul gasit pentru inserarea elementului
curent este un nod deja plin, se va diviza acest nod in alte 2 noduri care vor include atat vechile
elemente, cat si elementul nou inserat. Daca este necesar se vor face si alte divizari ascendente
pentru a mentine structura 2-3-4.
Obs.: Arborii bicolori si arborii 2-3-4 sunt 2 TDA izomorfe. Ca urmare a acestei echivalente arborii
2-3-4 pot fi transformati in arbori bicolori.
Prelucrarile frecvente ale arborilor sunt de 2 feluri:
-care NU modifica structura arborelui
cautari de chei
determinarea unei cai de la radacina la un nod remarcabil (care are anumite proprietati)
traversari ale intregului arbore in scopul cunoasterii si prelucrarii intregului continut
-care modifica structura arborelui
inserari de chei
cu conservarea proprietatilor arborelui
eliminari de chei
Parcurgeri ale arborilor binari exemple
1. RSD (p. prefixata): radacina, subarbore sting (recursiv), subarbore drept (recursiv)
2. SDR (p. postfixata): subarbore sting (recursiv), subarbore drept (recursiv), radacina
3. SRD (p. infixata): subarbore sting (recursiv), radacina, subarbore drept
Aceste parcurgeri pot fi generalizate pentru arborii oarecare de forma:
n

si se definesc recursiv astfel:


-daca arborele este vid
lista vida= parcurgerea sa
pre/in/postfixata
-daca arborele consta dintr-un nod
lista formata din
acel nod = parcurgerea sa pre/in/postfixata
-daca arborele este oarecare (fig alaturata), atunci:

1. RSD incepe cu parcurgerea radacinii, dupa care se parcurg prefixat, recursiv, , ,


2. SRD incepe cu parcurgerea infixata, recursiva, a , urmata de vizitarea radacinii, dupa care se
continua cu parcurgerea infixata, recursiva a nodurilor din ,
3. SDR parcurge postfixat, recursiv, , , si se termina cu radacina
4. O alta parcurgere folosita in cazul arborilor oaracare este parcurgerea pe niveluri (in latime)
(level_order, breadth_firstorder):
Pas1. Se parcurg nodurile de pe nivelul radacinii
Pas2. Se parcurg nodurile care sunt fii radacinii, de la stinga la dreapta
Pas3. Se viziteaza tot de la stinga la dreapta nodurile nepoti ale radacinii, samd.

5.Arborii pot fi parcursi si nerecursiv, cu ajutorul unei stive (de arbori!):


alg RSD_nerecursiv este
*se initializeaza S cu stiva vida
cit timp*arborele a este nevid sau *stiva nu este vida executa
cit timp *a este nevid
{ *tipareste cheia din radacina lui a (a k)
*s=push(s,a sd)
*a=a ss
}
executa
{ a=top(s); s=pop(s);
}
cit timp (! a && ! isEmpty(s))
sfarsit

Aplicatii simple cu arbori binari


1) Parcurgeri RSD, SDR, SRD
#include <stdio.h>
#include <conio.h>
#include <alloc.h>
struct nod_arbore
{ int k;
struct nod_arbore ss, sd; } a;
/*
typedef struct nod_arbore
{ int k;
struct nod_arbore ss, sd; } TREE;
TREE a; */
struct arbore int_a_2( )
{ struct arbore aux; char nn, c1, int k;
printf (e nod NULL?); scanf (%c, &nn); c1=getchar( );
if (nn==n)||(nn==N))
{ printf (cheie:); scanf (%d, &k); c1=getchar( );
aux=(struct arbore)malloc(sizeof(struct arbore));
aux k=k; aux ss=int_a_2( ); aux sd=int_a_2( );
return (aux);
}
else return (NULL);
}
RSD (struct arbore a)
{ if (a) { printf (psrcurgere RSD: %d, a k); RSD (a ss); RSD (a sd)}
return 1;
}
main( ) {struct arbore a; clrscr( ); a=int_a_2( ); RSD(a); getch( ); }
SDR (struct arbore a)
{ if (a) {SDR (a ss); SDR (a sd); printf (%d \n,a k); }
return 1;
}
2) Calcul inaltime arbore

int max (int a, int b) { return (a>=b?a:b); }


int h(struct arbore a)
{ int h1; h2;

if (!a) return (0);


else
{ h1=h (a ss); h2=h (a sd);
return (max(h1,h2)+1);
}

}
3) Parcurgere ne-recursiva RSD, folosind o stiva de arbori
struct astack;
{ struct arbore a;
struct astack next;
} s;
sfisare_stiva (struct astack s)
{ struct astack stv; /*pentru parcurgere nedistructiva a stivei*/
stv=s;
while (stv)
{ printf (\n un subarbore din stiva:);
RSD (stv a);
stv=stv next;
}
printf (NULL);
return (1);
}
struct astack news ( ) { return (NULL); }

struct astack push (struct astack s, struct arbore a)


{ struct astack stv;
stv=(struct astack )malloc(sizeof(struct astack));
stv a=a; stv next=s;
return (stv);
}
struct arbore top (struct astack s)
{ struct arbore arb;
if (s)
{ arb=s a; return (arb); }
else return (NULL);
}
struct astack pop (struct stack s)
{ struct astack s; p=s)
if (p) { s=p next; free (p); return (s); }
else return (NULL);
}
emptys (struct astack s) { if (!s) return(1); else return(0); }

NER_RSD (struct arbore a)


{
s=news ( );
while (a||!emptys(s))
{ while (a) { printf (%d, a k); s=push (s,a sd); a=a ss; }
do
{ a=top(s); s=pop(s); }
while (!a && ! emptys(s));
}
return (1);
}

int depl_l=2, depl_c, col_init=40;


PRSD (struct arbore a, int col, int lin)
{ int harb;
if (a)
{ harb=h(a); gotoxy (col, lin); printf (%d\n, a k);
if (harb==hmax) depl_c=hmax+3; else depl_c=0)
PRSD(a ss, col-1-depl_c, lin+1+depl_l);
PRSD (a sd, col+1+depl_c, lin+1+depl_l);
}
return 1;
}

main ( )
{ .
hmax=h(a);
PRSD (a, col_init, 1);
.
}
/* parcurgerea in latime */
public void display Tree (TREE a)
{ astack globalst=news ( ); int nBlanks=32; int isRowEmpty=0;
push (globalst, a);
while (!isRowEmpty)
{ astack localst=news( );
isRowEmpty=true;
for (int j=0; j<nBlanks; j++) printf ( );
while (!isEmpty(globalst))
{ TREE temp=pop(globalst);
if (temp)
{ printf (%d , temp k);
push (localst, temp ss);
push (localst, temp sd);
if (temp ss || temp sd) isRowEmpty=false;
}
else
{ printf (-- );
push (localst, NULL);
push (localst, NULL);
}

for (int j=0; j<nBlanks 2-2; j++) printf ( );


}
printf (\n);
nBlanks/=2;
while (!isEmpty (localst)push(globalst, pop(localst));
}
}

TDA arbore binar


tip 2 Tree este
domenii
GenElm, Bool, Int: SimilarArray
2treeSet: multimea tuturor arborilor binari cu elemente din GenElm
operatori
new2Tree: 2treeSet //apartine Conb

init2Tree: 2 treeSet x GenElm x 2treeSet


2treeSet //apartine Conb
isEmpty: 2 treeSet
Bool
root: 2treeSet
GenElm
rootLeftsubtree, rootRightsubtree: 2treeSet 2treeSet
restrictii
root, rootLeftsubtree, rootRightsubtree (a) !isEmpty (a)
axiome
isEmpty (newTree)=true isEmpty (init2Tree(ss, r, sd))=false
root(initTree (ss, r sd))=r
rootLeftsubtree (ss, r, sd)=ss // Analog sd

TDA arbore oarecare


tip m Tree este
.
operatori
newmTree, initmTree (r, A1, A2, , Am), isEmpty (A)
root(A), label(n, A), Parent(n, A)
firstChild (n, A), rightSibling(n, A)

Implementari posibile
-se allege aceea care conduce la implementarea cea mai eficienta posibila pentru primitivele
(operatorii) care se executa cel mai des in aplicatia de dezvoltat.
1) Cu vector tata

1 2 3 4 5
0 1 1 2 2

6
3

7
3

8
5

9
5

10
5

11

12

10

Pe linga tati vectorul poate include si etichetele nodurilor.


Cautare nod tata=O(1); Parcurgere arbore din tata in tata =O(N);
Ac. reprezentare NU faciliteaza: obtinerea informatiilor de filiatie, determinarea ordinii fiilor unui nod,
implementarea first Child, right Sibling, determinarea inaltimii.

2) Cu liste de fii pentru fiecare nod


1

4
5

10

10
Cautare nod tata=O(N2); Parcurgere arbore=O(N);
Faciliteaza obtinerea informatiilor despre fii; Faciliteaza determinarea ordinii fiilor nod n
NU faciliteaza: implementarea first Child, right Sibling, determinarea inaltimii

3) Cu vector de pointeri la fii: pt. arbori multicai de cautare


Arbore multicai de cautare

20

14

40

18

25

28

26

27

21

30

struct nod_arbore
{ int m; /*numarul de fii*/
int k[mMax]; /*m-1 chei*/
struct nod_arbore *fii[mMax];
} a;
Cautare nod tata=O(logN); poate deveni O(1) dc se pastreaza si pointeri inapoi catre tatal fiecarui nod
Parcurgere=O(N); Favorizeaza: obtinerea informatiilor de filiatie; determinarea ordinii fiilor; firstChild,
rightSibling, determinarea inaltimii

4) Cu arbore binar a arborilor oarecare


struct nod_arbore
{ int k;
struct nod_arbore ss, sd;
} a;
1

11

12

10

10

11

12

Ex. de folosire a arborilor binari

Pentru reprezentarea expresiilor algebrice (expression trees)

a*b-c

a*(b-c)+d/e

alg evaluare_expresie_cu_arbore este


if (arbore este vid) return 0
else
firstOperand=evaluare_expresie_cu_arbore (leftSubtree(arbore))
secondOperand=evaluare_expresie_cu_arbore (rightSubtree(arbore))
operator=root (arbore)
return (firstOperand operator secondOperand)
sfarsit
Arbori de decizie (decision trees)
o pot sta la baza sistemelor expert.
o pot fi binari (Da/Nu) sau m-ari (multiple choice)
Ex. SE in medicina
este febril?
Da

Nu

ii curge nasul?
Da

Nu

are
frisoane?

analizele indica
o infectie?

Da

Nu
Da

are dureri
musculare?

Nu

are o raceala
usoara

Da

Nu

este gripat

are guturai

Ex. de folosire a arborilor generali (oarecare)

expresie

Arbore sintactic: a*(b+c)

termen

factor

variabila

termen

factor

expresie

termen

-expresie algebrica=un termen sau 2 termeni separati de + sau -termen=un factor sau 2 factori separati de * sau /
-factor= o variabila sau o expresie algebrica intre paranteze
-variabila=o simpla litera

factor

variabila

factor

variabila

Arbori pentru jocuri: de ex. x si 0

x
.

.
x

.
x

.
0

..

.
0

x
x

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