Documente Academic
Documente Profesional
Documente Cultură
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
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
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
linia 2
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
top
maxsize
StackArray
(top)
Cu lista inlantuita
s
.
} /*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
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
a
b
+
c
a
a
ab
ababab-c
ab-c+
vida
vida
+
+
vida
a
a
ab
ab
abc
abc
abc
vida
^
^
^^
^^
^
vida
a(bc)
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
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.
Obs
QUEUE *q;
Cu lista cu header:
front
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
rear
0 1 2 3 4 5 6 7 8 9 10
1 1 0 2 1 0 0 1 2 1 0
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 ( );
}
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:
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.
0 n = radacina
nivel predn + 1 n radacina
Nivel (n)=
Inaltimea arborelui =
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
}
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); }
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);
}
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
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
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
11
12
10
10
11
12
a*b-c
a*(b-c)+d/e
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
expresie
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
x
.
.
x
.
x
.
0
..
.
0
x
x