Documente Academic
Documente Profesional
Documente Cultură
Structuri de date
- material didactic pentru ID -
Bucureşti
2011
Titlul cursului: Structuri de date
Introducere: Disciplina Structuri de date se studiază în Academia de
Studii Economice din Bucureşti, în cadrul Facultăţii de Cibernetică,
Statistică şi Informatică Economică şi se adresează studenţilor din anul 3,
secţia Informatică Economică.
Disciplina Structuri de date are ca obiectiv prezentarea tuturor structurilor
de date cu proprietăţile, avantajele şi dezavantajele utilizării lor, în vederea utilizării lor
eficiente. Cunoaşterea structurilor de date le permite programatorilor să privească procesul de
dezvoltare software ca proces de alocare şi nivelare de resurse. Evoluţia tehnicilor de
programare determină realizarea unor produse program caracterizate prin complexitate
ridicată şi prin consumuri reduse de resurse. Într-un context mai larg, structurile de date se
constituie ca resurse la dispoziţia programatorilor, care prin diversitate influenţează hotarâtor
calitatea programelor.
2
Articole
Masive
Stive şi cozi
multidimensionale
Arbori binari de
Matrice rare
căutare
Arbori B
3
CUPRINS
2. Matrice rare
2.1. Concepte de bază
2.2. Memorarea matricelor rare
2.3. Determinarea gradului de umplere al unei matrice rare
2.4. Adunarea, scăderea şi transpunerea
2.5. Înmulţirea şi inversarea matricelor rare
4. Fişiere
4.1. Structuri de date externe
4.2. Criterii de clasificare a fişierelor
4.3. Fişiere secvenţiale
4.4. Fişiere secvenţial – indexate
4.5. Fişierele bazei de date
6. Stive şi cozi
6.1. Consideraţii privind structurilor de date de tip stivă şi coadă
6.2. Caracteristicile structurilor de date de tip stivă
6.3. Operaţii de bază cu stive
6.4. Evaluarea expresiilor matematice cu ajutorul stivei şi cozii
4
8. Arbori B
8.1. Arbori B. Definiţie. Proprietăţi.
8.2. Operaţii de bază într -un arbore B
8.3. Algoritmii C++ pentru inserarea unei valori de cheie într-un arbore B
8.4. Algoritmii C++ pentru ştergerea unei valori de cheie intr -un arbore B
5
1. Masivele – structuri de date omogene si contigue
După studierea acestei unităţi de învăţare, studenţii îşi vor însuşi cunoştinţe
teoretice şi vor deprinde abilităţi practice pentru utilizarea masivelor
unidimensionale si bidimensionale. Studenţii vor aprofunda lucrul cu
vectori şi matrice, fiind capabili să definească, iniţializeze şi să refere elementele acestora.
Prin definirea:
int x[10];
se specifică:
x – o dată compusă din 10 elemente;
elementul – de tip întreg;
primul element – x [0];
al doilea element – x [1];
... – . . .
al zecelea element – x [9].
Dacă funcţia:
lg(a; b)
unde:
k – lungimea standard alocată la compilare pentru o dată de tipul b;
lg(x0 , x1 , x2 , . . . ., xn ; b) = lg (x0; b) + lg (x1; b) + . . . +lg(xn; b) =
*
= lg(.; b) + lg (.; b) + lg (.; b) = k b + k b + . . . k b = n k b
rezultă că:
*
lg(x, int) = 10 2 bytes
6
Modul grafic de reprezentare a alocării memoriei pentru elementele vectorului x,
conduce la:
dacă se notează:
= adr(x[i])
= adr (x[i+1])
- = lg (x[i],.)
se observă că:
pred(x[0]) =
succ(x[9]) =
succ ( ) = pred ( ) =
7
Dacă elementul x[i] este considerat reper, adresele celorlalte elemente se calculează folosind
deplasarea faţă de elementul reper.
Funcţia deplasare:
depl(a;b)
sau:
Dacă x reprezintă structura de date omogene în totalitatea ei, iar x[0], x[1], . . . , x[9] sunt
părţile care o compun, din reprezentarea grafică rezultă că:
Se defineşte:
unde:
a – numele datei structurate;
b – poziţia elementului a cărui adresă se doreşte a fi calculată;
adr(a+b) = adr(b+a).
Contiguitatea este pusă în evidenţă prin definirea funcţiei de distanţă DST(a, b), care
indică numărul de bytes care nu aparţin datelor a şi b, separând a de b.
a b
11 12
a b c
8
DST (a, b) ≤ DST(a, c) + DST (c, b)
11 ≤ 11 + 12 + lg (b) + 12
lg(b) + 2*12 ≥ 0
DST (a, b) = DST (b, a)
X0 X1 X2 X9
.........
int x[10];
int n;
.........
cin >> n;
for( i=0; i<n; i++)
x[i] = 0;
int y[11];
adr(y) = adr(y[-7])
9
DST(y[ -7], y[-6]) = [-6-(7)-1] * lg(. , int) = 0
Se observă că:
sau:
...
α = adr(x[0]);
α = α + i*lg(x[0], int);
10
numele masivului, referirea elementelor se realizează specificând acest nume şi poziţia elementului
cuprins între paranteze drepte sub forma unei expresii indiciale.
Expresia indicială are un tip oarecare, însă înainte de efectuarea calculului de adresa are loc
conversia spre întreg, care este atribuită funcţiei int( ). Deci funcţia int( ), realizează rotunjirea în
minus a valorii expresiei indiciale.
Astfel, pentru definirea:
int z[100];
se calculează astfel:
a
adr (z [ e + cos (b) / c ] ) = adr ( z[6] ) +
a
+ ( int (e + cos (b) / c )) * lg ( z[6] , int)
TRUE, dacă a d r ( a ) [ B
i, Bf ]
gm(a) =
FALSE, în caz contrar
unde:
fp( ) – funcţia de validare adrese în raport cu programul;
gm( ) – funcţia de validare a adreselor în raport cu un masiv m;
Ai – adresa se început a programului;
A f – adresa se sfârşit a programului;
Bi – adresa de început a masivului m;
B f – adresa de sfârşit a masivului m.
adr (I 1 ) = Ai
adr (I 100 ) = A f
int x[10];
atunci:
11
adr (x[0]) = Bi
adr (x[99]) = B f
sau dacă:
a
int (e + cos (b) / c ) * ( lg ( z[6] , int ) < B f - Bi (1.16)
pot apare situaţii în care să fie modificate alte zone decât cele asociate masivului unidimensional.
Dacă se iau în considerare reprezentări simplificate, pentr u localizarea unui element x[i] al
unui masiv unidimensional, se foloseşte formula cunoscută:
În cazul în care cele N masive au dimensiuni variabile d 1 , d 2 , …, d N , condiţia de utilizare
corectă a resursei memorie este ca:
12
Pornind de la ideea că o matrice este formată din linii, iar liniile sunt formate din elemente, se
ajunge la modelul grafic al matricei, care apare sub forma unei arborescenţe organizată pe trei nivele.
Pentru definirea:
int a[2][4];
rezultă că matricea a este formată din 2 linii, fiecare linie având câte 4 componente.
a0 a1
C0 C1 C2 C3
13
Fiecare linie sau coloană, prin descompunerea la nivelul următor, este privită ca vector de
elemente. Deci, în final o matrice este privită ca vector de vectori.
Definirea:
int a[2][4];
DST(a[i][n], a[i+1][0]) = 0
DST(a[m][j] , a[0][j+1]) = 0
adr(a[m][n]) adr(a[0][0 ])
1 m * n (1.23)
lg(a, Ti )
succ(a[i][n]) = a[i+1][0]
pred(a[i][0]) = a [i-1][n]
succ (a[i]) = a[i+1][0]
pred (a[i]) = a[i-1][0]
succ(a[i+k]) = succ (a[i])
pred (a[i]) = pred (a[i+k])
În mod asemănător:
sau:
deci:
14
adr(a+i) = adr(a[0]) + (i-0)*n*lg(a[i][0]) (1.25)
...
...
...
...
unde a, a[i], a[i][j] se definesc în aşa fel încât să reflecte următorul conţinut:
a[i] = adr(a[i][1]), i = 0, 1, . . . , m
a = adr (a[0])
Definirea unei matrice ca având m linii şi n coloane, deci în total m*n elemente, şi
explorarea elementelor prin includerea unui alt număr de linii şi coloane conduce la o
localizare a elementelor însoţită de un rezultat eronat, a cărui provenienţă e necesar a fi
interpretată şi reconstituită.
Se consideră matricea:
1 2 3 4 5
6 7 8 9 10
A 11 12 13 14 15
16 17 18 19 20
7 1 1 4 2
stocată în:
int a[5][5];
int b[4][4];
15
Procedura realizează însumarea elementelor diagonalelor matricei b. Întrucât transmiterea
parametrilor se face prin adresă, punerea în corespondenţă este:
a00 a01 a02 a03 a04 a10 a11 a12 a13 a14 a20 a21
1 2 3 4 5 6 7 8 9 10 11 12
b00 b01 b02 b03 b10 b11 b12 b13 b20 b21 b22 b23
a22 a23 a24 a30 a31 a32 a33 a34 a40 a41 a42 a43 a44
13 14 15 16 17 18 19 20 7 1 1 4 2
for (i = 0; i < 5; i + +)
S = S + b[i][i];
i = 0 localizează termenul;
adr (b[0][0] ) = adr(a[0][0]);
cont (b[0][0] ) = cont (a[0][0] ) = 1;
i = 1;
adr(b[1][1]) = adr (b[1][1] ) + (1-0)*4* lg (int) + 1*lg (int) =
= adr (b[0][0] ) +6*lg (int)
cont (b[1][1]) = 6;
i = 2;
adr (b[2][2] ) = adr (b [0][0] ) + (2-0) * 4* lg (int) + 2*lg (int) =
= adr (b[0][0]) + 11*lg (int);
cont (b[2][2] ) = 11;
i = 3;
adr (b[3][3] ) = adr (b [0][0]) + 16*lg (int);
cont (b [3][3] ) = 16;
i = 4;
16
adr (b[4] ) = adr (b[0][0} + (4-0) 4* lg*(int) + 4*lg (int) =
= adr (b[0][0] ) + 21* lg (int)
cont (b [4][4] ) = 7;
se ajunge la:
În cazul în care matricea este densă sau încărcată sau are puţine elemente nule sau este
complet încărcate, dar are elemente ce conţin valori particulare, se efectuează o tratare diferenţiată.
Matricele simetrice, au proprietatea:
ceea ce conduce la ideea memorării numai a elementelor de pe diagonala principală şi de sub aceasta
sau de deasupra ei.
Pentru o matrice simetrică a[n][n], cu n*n elemente, se impune memorarea a n*(n+1) /2
dintre acestea.
Dacă sunt memorate elementele a[i][j], j ≥ i, i = 0, 1, 2, . . ., n, j=i, i+1, . . ., n, astfel:
a00 a01 ... a0n a11 ... a1n a22 ... a2n ... ann
n n-1 n-2 1
adr(a [i][j]) = adr ([0][0]) + [(n-0) + (n-1) + . . . (n-i+1) + (j-0) ]*lg (int)
adr (a [i][j] ) = adr (a [0][0] ) + f (i, j, n) * lg (int)
1 1 1 1
A 1 1 1 1
1 1 1 1
17
se impun definirile:
- nume matrice;
- număr linii;
- număr coloane;
- valoarea elementului repetat, respectiv 1.
În cazul în care matricea are conţinutul:
1 2 3 4 5 0
1 2 3 4 5 0
A
6 8 3 2 6 1
2 2 2 2 2 2
1 3 4 5 0 6 8 3 2 6 1 2 *
2
Fig. 1.11. Reprezentarea grafică a matricei A
Matricele diagonale sunt matricele în care elementele componente apar sub forma:
1 0 0 0 0
0 7 0 0 0
A 0 0 5 0 0
0 0 0 8 0
0 0 0 0 2
În acest caz, forma de memorie adecvată, în condiţiile în care în urma prelucrării matricei A,
aceasta nu-şi schimbă structura, este vectorul.
Un alt tip matrice, cu modalităţi specifice de prelucrare, este matricea tridiagonală, care are
forma:
1 4 0 0 0 0
2 8 1 0 0 0
T 0 2 7 3 0 0
0 0 6 5 9 0
0 0 0 0 8 1
18
Elementele nenule se memorează într -un masiv unidimensional, iar accesarea lor se efectuează
după formula:
unde:
i = 0, 1, . . ., n;
j =0, 1, pentru i = 1;
j = i – 1, i, i + 1, pentru 1 < i < n;
j = n – 1, n, pentru i = n.
Matricea triunghiulară are elementele nenule fie deasupra, fie sub diagonala principală, iar
stocarea în memorie este asemănătoare matricei simetrice.
Pentru matricea triunghiulară A cu elementele nenule sub diagonala principală, adresa
elementului a[i, j] este:
k = 0;
for (i=0; i<n; i++)
for (j=i; j<n; j++)
{
k = k + 1;
s [k] = T [i][j];
}
k = -n*(n+1)/2;
for (i=n-1; i >= 0; i--)
for (j=i; j>=0; j--)
{
s [k] = T[i][j];
k = k – 1;
}
19
for (i=0; i<n; i++)
for (j=0; j<n; j++)
if (i = = j)
a[i][j] = 1;
else
a[i][j] = 0;
int a[10][10], m, n;
…………………
for (i=0; i<n; i++)
for (j=0; j<m; j++) {
cout<<”Element ”<<i<<”,”<<j<<”:”;
cin>>a[i][j]);
}
void Matrix::stergem(int K)
{
20
int i, j;
for (i=K-1; i<m; i++)
for (j=0; j<n; j++)
a[i][j]=a[i+1][j];
m --;
}
void Matrix::linerat(int b[ ])
{
int i, j, K = 0;
for (i=0; i<m; i++)
for (j=0; j<n; j++)
b[K++]=a[i][j];
}
b
Operaţiile cu masive uni-, bi- şi multidimensionale sunt necesare atunci când se lucrează cu
colectivităţi omogene înzestrate cu caracteristici omogene, care se supun operaţiilor de agregare prin
însemnare.
Producţiile înregistrate pe intervale de timp derivate unei mulţimi de clienţi pentru un anumit
produs se regăsesc în modele sub forma unui masiv bidimensional cu atâtea linii câţi clienţi sunt şi
atâtea coloane câte intervale sunt luate în considerare.
Se impune realizarea unei biblioteci de funcţii pentru prelucrări cu masive multidimensionale.
În cazul matricelor care au mai puţin de 30% elemente nenule se lucrează cu matrice rare, cărora le
corespund reprezentări destinate care ameliorează gradul de ocupare.
Acest indicator este dat ca raport între elementele iniţializate sau referite şi totalul elementelor
alocate dintr-un masiv.
21
1.5. Masive multidimensionale
int salarii[10][6][12][7];
iar elementul:
salarii [ i ][ j ][ k ][ h ]
salariu[.][j][k][h]
salariu[.][.][k][h]
salariu[.][.][.][h]
salariu[.][.][.][.]
sau a oricărei variante de grad de cuprindere a elementelor colectivităţii, este o problemă de:
- definire a masivelor multidimensionale, cu număr de dimensiuni mai redus cu o unitate,
cu două unităţi, cu trei şi în final cu patru şi apoi iniţializarea lor;
- efectuarea calculelor în cadrul unei secvenţe de structuri repetitive incluse.
De exemplu, pentru calculele mediilor specificate mai sus se definesc variabilele:
float mjkh_salariu[6][12][7];
float mmkh_salariu[12][7];
float mmmh_salariu[7];
float mmmm_salariu;
[1, 10 * 6 * 12 * 7] N (1.35)
22
a[n1][n2][…][np]
a1 a2 … an1
. . . . . . . . . . . . . . . . . . . .
Localizarea unui element oarecare din masivul p-dimensional de tipul T i, se efectuează după
formula:
a[n1][0][0] a[n1][n2][0]
i
a[i][n2][0]
j
23
(l 0 2 0 0 4)
asociem arborescenţa:
a
………..
a[2][0] a[2][1] 0 2 5 ……. a[2][n]
0 1 3
Fig. 1.15. Structura arborescentă a unui masiv tridimensional
Variabila a[2][1] are o structură convenabil definită, care să poată memora adresele primei şi
respectiv ultimei componente de pe nivelul imediat inferior, precum şi poziţiile elementelor nenule din
linie.
Este convenabilă şi definirea:
. 3 1 3 6 .
în care, prima informaţie de după adresa primei componente indică numărul componentelor memorate
la nivelul inferior.
Este de dorit ca multitudinea de informaţii suplimentare care vin să descrie structura, fie să
menţină un grad de ocupare a memoriei cât mai redus, fie să permită un acces mult mai rapid la
informaţiile dintr -un
-un masiv multidimensional.
Modul de a pune problema masivelor ca structuri de date omogene, prin folosirea de exemple,
pentru programatori nu reprezintă dificultate în înlocuirea valorilor particulare cu parametrii care
conduc la formule generale pentru fiecare clasă de probleme în parte.
Teme de control
Bibliografie
Ion IVAN, Marius POPA, Paul POCATILU - Structuri de date, Vol. 1 si 2, Editura ASE,
Bucuresti, 2008.
24
2. Matrice rare
Se consideră matricea:
25
1 0 0 0 0
0 0 2 0 4
A (2.1)
0 0 0 0 0
0 1 0 0 0
p
G 100 (%)
100 (2.2)
n m
unde:
p – numărul
– numărul de elemente nenule;
n – numărul
– numărul de linii;
m – numărul
– numărul de coloane.
În general se acceptă că o matrice este rară dacă densitatea sa este de cel mult 3%.
Densitatea matricei A este G( A) 20% , ea fiind prezentată aici în scopul ilustrării
conceptului de matrice rară.
Structura de date clasică folosită pentru manipularea matricelor, tabloul de dimensiune
(n, m) alocat la compilare, se dovedeşte a fi ineficientă în cazul în care matricea este rară. Un
prim avantaj este legat de folosirea neeconomică a spaţiului de memorie prin alocarea de zone
mari pentru memorarea elementelor nule, care nu sunt purtătoare de informaţie. Ocuparea
unor zone de memorie cu elemente nule nu se justifică deoarece acestea nu contribuie la
formarea rezultatului operaţiilor cu matrice, adunare, înmulţire etc., conducând totodată şi la
mărirea duratei de realizare a acestor operaţii prin ocuparea procesorului cu adunări şi
înmulţiri scalare cu zero. Acest inconvenient se manifestă cu atât mai pregnan t cu cât
dimensiunea matricei este mai mare.
Prin urmare, pentru probleme de dimensiuni mari, s- a căutat găsirea unor modalităţi de
reprezentare compactă a matricelor rare, în care să se renunţe la memorarea elementelor nule.
În acest caz este necesar ca tehnicile de memorare să încorporeze pe lângă elementele nenule
şi mijloacele de identificare a poziţiilor acestor elemente în matrice.
Sunt prezentate în continuare câteva posibilităţi de memorare compactă a matricelor
rare MR. Se face, de asemenea, o analiză a oportunităţii folosirii fiecărei tehnici în parte, în
funcţie de densitatea matricei.
identifi care binară se bazează pe natura binară a sistemului de calcul,
Memorarea prin identificare
constând în memorarea numai a elementelor nenule ale matricei într-o zon ă primară ZP având având
tipul de bază corespunzător tipului elementelor matricei şi dimensiunea egală cu numărul
elementelor nenule.
Structura matricei este indicată printr -o secvenţă binară memorată într -o zonă
secundară ZS .
Matricea A prezentată anterior se memorează astfel:
astfel:
Zona primară:
Locaţie 1 2 3 4
Valoare 1 -2 4 -1
Fig. 2.1. Structura ZP pentru matricea A
26
Zona secundară:
Locaţie 1 5 6 10
Valoare 1 0 0 0 0 0 0 1 0 1
Locaţie 11 15 16 20
Valoare 0 0 0 0 0 0 1 0 0 0
Fig. 2.2. Structura ZS pentru matricea A
Întrucât pentru memorarea matricei în forma clasică sunt necesare DM = m*n cuvinte,
raportul dintre cerinţele de memorie ale structurii de mai sus şi a celei standard este:
DMR1 1
c1 G (2.4)
DM 8b
1
c1 A 0,2 0,23 (2.5)
32
ceea ce indică că memorarea matricei A conform acestei structuri ocupă de aproximativ patru
ori mai puţină memorie decât cea standard.
Egalând c1 1 se determină limita superioară a densităţii unei matrice pentru care
această structură necesită mai puţină memorie decât cea standard:
1
Glim 1 (2.6)
8b
Pentru matricea A:
1
Gli m 1 0,96 96% (2.7)
32
Această structură de memorare diferă de abordări prin faptul că în zona secundară este
alocată memorie şi pentru elementele nule ale matricei. Structura este mai puţin eficientă
27
pentru matricele de mari dimensiuni foarte rare. Principala dificultate constă în complexitatea
programelor de implementare a operaţiilor matriciale.
O altă modalitate de memorare prin identificare binară se obţine prin modificarea
informaţiilor din zona secundară. Această zonă va conţine pe jumătăţi de cuvânt
indicii de coloană a elementelor nenule din matrice, precum şi anumite informaţii de
control pentru identificarea rapidă a poziţiei elementelor nenule în matrice. Structura
ZS pe cuvinte este următoarea:
Locaţie 1 2 3 4 5 6
Valoare 4 5 4 1 2 0 1 1 3 5 2
Fig. 2.3. Structura ZS pentru matricea A
(5 m m * n * G) / 2 (2.8)
valoarea fiind rotunjită la cel mai mare întreg. Numărul total de cuvinte necesar memorării
unei matrice prin intermediul celor două zone ZP şi ZS este egal cu
DMR2 = (5 m 3 * m * n * G) / 2 (2.9)
28
Raportul dintre cerinţele de memorie ale acestei structuri de identificare binară şi a
celei standard este:
3 G 5 m
c2 (2.10)
2 2*m*n
2 5 m
Glimm 1 0,666 66,6% (2.11)
3 2m 2
Locaţia 1 2 3 4
Valoare 1 -2 4 -1
Indice linie 1 2 2 4
Indice coloană 1 3 5 2
Fig. 2.4. Model de memorare compactă aleatoare a matricei A
Raportul dintre cerinţele de memorie ale acestei structuri şi a celei standard este:
c3 3 G (2.13)
29
În structura din figura 6.4, pentru identificarea elementelor nenule ale matricei rare au
fost folosite două zone secundare corespunzătoare indicelui de linie şi de coloană. Se prezintă
în continuare o altă posibilitate de memorare în care se va utiliza o singură zonă secundară de
dimensiune egală cu numărul de elemente nenule ale matricei, conţinând simultan informaţii
asupra indicilor de linie şi de coloană.
Astfel, fiecărui element din zona primară i se ataşează în zona secundară un număr
întreg din care se determină indicii de linie şi de coloană. Dacă elementul aij 0 este
memorat în locaţia k a zonei primare atunci în zona secundară se va memora un indice agregat
ig a cărui valoare este dată de relaţia
ig = i+(j-1)*n (2.14)
unde n este numărul de coloane a matricei. Acest număr este suficient pentru iden tificarea
elementului în matrice.
Utilizând acest artificiu, matricea A se memorează astfel:
Locaţia 1 2 3 4
Valoare 1 -2 4 -1
Indice agregat, ig 1 12 22 9
Fig. 2.5. Model derivat de memorare compactă a matricei A
j ig(k)/n (2.15)
Raportul dintre cerinţele de memorie ale acestei structuri şi a celei standard este:
c4 2G (2.18)
Valoarea limită a densităţii matricei pentru care această structură este eficientă este G
= 50%.
Memorarea compactă sistematică presupune că elementele nenule ale unei matrice
rare sunt memorate într-o anumită ordine, respectiv pe linii sau pe coloane. În acest
caz nu este necesar să se memoreze în zonele secundare indicii de linie, respectiv de
coloană. Pentru o memorare în ordinea liniilor, ne mai sunt necesari indicii de linie, însă se
cere specificarea începutului fiecărei linii.
30
Şi în acest caz există mai multe structuri de memorare. Cea prezentată în continuare
este caracterizată prin faptul că utilizează o singură zonă secundară ZS , care conţine indicii de
coloană ale elementelor nenule din matricea considerată, precum şi elemente false care indică
începutul fiecărei linii şi sfârşitul memorării întregii matrice. De exemplu, un element zero în
ZS marchează prezenţa unui element fals şi acesta specifică în ZP numărul liniei elementelor
de la dreapta locaţiei. Sfârşitul matricei este marcat prin prezenţa în ZP a unui element fals cu
valoarea zero.
Pentru matricea A, memorarea în această formă este următoarea:
Locaţia 1 2 3 4 5 6 7 8
ZP 1 1 2 -2 4 4 -1 0
ZS 0 1 0 3 5 0 2 0
Fig. 2.6. Model de memorare compactă sistematică a matricei A
2 * (m 1)
c5 2*G (2.20)
m*n
Locaţia 1 2 3 4
ZP 1 -2 4 -1
ZSL 1 2 2 4
ZSC 1 3 5 2
ZSU &2 &3 &4 NULL
Fig. 2.7. Model de memorare cu ajutorul listelor a matricei A
31
c6 4*G (2.21)
Prin urmare această structură de memorare este eficientă pentru memorarea matricelor
cu o densitate a elementelor nenule de maximum 25%.
Pentru a deduce dacă o matrice este sau nu rară, se defineşte gradul de umplere al unei
matrice, notat cu p. În cazul în care p < 0,3*m*n, se consideră că matricea este rară.
Problema matricelor rare comportă două abordări:
- abordarea statică, în care alocarea memoriei se efectuează în faza de compilare;
aceasta presupune ca programatorul să cunoască cu o precizie bună numărul maxim al
elementelor nenule;
- abordarea dinamică, în care alocarea se efectuează în timpul execuţiei, caz în care nu
este necesară informaţia asupra numărului de elemente nenule; această abordare este
dezvoltată în partea destinată listelor.
Memorarea elementelor matricei rare, presupune memorarea indicelui liniei, a
indicelui coloanei şi, respectiv, valoarea nenulă a elementului.
Se consideră matricea:
0 0 6 0 0
7 0 0 0 0
A (2.22)
0 0 0 9 0
0 8 2 0 0
5
G 0.25 (2.23)
5 4
Se definesc 3 vectori:
lin [ ] – memorează poziţia liniei ce conţine elemente nenule;
col [ ] – memorează poziţia coloanei ce conţine elemente nenule;
val [ ] – memorează valoarea nenulă a elementelor.
32
Pentru efectuarea calculelor cu matrice rare definite în acest fel, un rol important îl au
vectorii LIN, COL, iar pentru matricele rare rezultat se definesc vectori cu un număr de
componente care să asigure şi stocarea noilor elemente ce apar.
Astfel, pentru adunarea matricelor rare definite prin:
şi
Tabel 2.4. Valorile matricei rare B
LIN_B COL_B VAL_B
1 1 4
2 2 -7
3 2 8
4 1 5
4 3 6
atunci:
33
Tabel 2.6. Conţinutul final al matricei rare C
LIN_C COL_C VAL_C
3 2 8
4 1 5
4 3 6
4 4 8
? ? ?
? ? ?
? ? ?
? ? ?
Prin secvenţe de program adecvate, se face diferenţa între definirea unui masiv
bidimensional şi componentele iniţializate ale acestora, cu care se operează pentru rezolvarea
unei probleme concrete. Din punct de vedere al nivelului de umplere, tabelul 2.6 descrie o
matrice rară cu un grad de umplere egal cu
12
G= *100 = 37.5 % (2.27)
32
ce descrie iniţializarea elementelor matricei pe baza valorilor din vectori, iar funcţia rar( )
conţine secvenţa:
k =1;
for( i = 0; i < m; i++)
for( j = 0; j < n; j++)
if (a[i][j] != 0)
34
{
LIN_a[k] = i;
COL_a[k] = j;
val_a[k] = a[i][j];
k = k + i;
}
În cazul în care gradul de umplere nu este suficient de mic astfel încât matricea să fie
considerată rară, pentru memorare se utilizează o structură arborescentă care conţine pe
nivelul al doilea poziţiile elementelor nenule, iar pe ni velul al treilea valorile.
Astfel matricei:
7 3 5 0 0
0 2 4 8 0
A (2.28)
0 0 9 0 5
6 8 9 8 1
1 2 3 2 3 4 3 5 -1
7 3 5 2 4 8 9 5 6 … 1
1 2 3 4 5
A 6 7 8 9 10 (2.29)
11 12 13 14 15
35
prin punerea în corespondenţă cu elementele vectorului b, să se obţină interschimbul între
două coloane oarecare k şi j ale matricei.
a00 a01 a02 a03 a04 a10 a11 a20 a21 a22 a23 a24
1 2 3 4 5 6 7 … 11 12 13 14 15
b0 b1 b2 b3 b4 b5 b6 b10 b11 b12 b13 b14
Fig. 2.9. Punerea în corespondenţă a matricei A cu vectorul b
Dacă matricea are M linii şi N coloane şi elemente de tip int , atunci adresa elementului
a[i][j] este dată de relaţia
iar din modul în care se efectuează punerea în corespondenţă a matricei A cu vectorul b,
rezultă:
Pentru o matrice liniarizată, adresa elementului a[i][j] în cadrul vectorului este dată de relaţia
36
Pentru generalizare, un masiv n-dimensional rar, este reprezentat prin n + 1 vectori,
fiecare permiţând identificarea coordonatelor elementului diferit de zero, iar ultimul stocând
valoarea acestuia.
În cazul în care se construieşte o matrice booleană ce se asociază matricei rare, o dată
cu comprimarea acesteia se dispun elementele nenule într-un vector. Punerea în
corespondenţă a elementelor vectorului are loc în acelaşi moment cu decomprimarea matricei
booleene şi analiza acesteia.
De exemplu, matricei :
8 0 0 0 4 0
3 3 0 0 0 0
A (2.33)
0 0 0 1 7 0
5 6 9 0 0 0
1 0 0 0 1 0
1 1 0 0 0 0
B (2.34)
0 0 0 1 1 0
1 1 1 0 0 0
care prin compactare, ocupă primii 3 octeţi ai unei descrieri, urmaţi de componentele
vectorului:
C = ( 8, 4, 3, 3, 1, 7, 5, 6, 9) (2.35)
Compactarea este procedeul care asociază un bit fiecărei cifre din forma liniarizată a
matricei B.
37
MatriceRara MatriceRara::operator +(MatriceRara & MR)
{
/* se determina dimensiunea matricei rezultat */
MatriceRara rezMR;
if((this->m!=MR.m)||(this->n!=MR.n))
return rezMR;
38
rezMR.valori = new double[rezdim];
int k=i=j=0;
while((i<this->dim)&&(j<MR.dim))
{
if(this->linii[i]<MR.linii[j])
{
rezMR.linii[k] = this->linii[i];
rezMR.coloane[k] = this->coloane[i];
rezMR.valori[k] = this->valori[i];
i++;
k++;
}
else
if(this->linii[i]>MR.linii[j])
{
rezMR.linii[k] = MR.linii[j];
rezMR.coloane[k] = MR.coloane[j];
rezMR.valori[k] = MR.valori[j];
k++;
j++;
}
else
if(this->coloane[i]<MR.coloane[j])
{
rezMR.linii[k] = this->linii[i];
rezMR.coloane[k] = this->coloane[i];
rezMR.valori[k] = this->valori[i];
i++;
k++;
}
else
if(this->coloane[i]>MR.coloane[j])
{
rezMR.linii[k] = MR.linii[j];
rezMR.coloane[k] = MR.coloane[j];
rezMR.valori[k] = MR.valori[j];
k++;
j++;
}
else
if(this->valori[i]+MR.valori[j])
{
rezMR.linii[k] = MR.linii[j];
rezMR.coloane[k] = MR.coloane[j];
39
rezMR.valori[k] = this-
>valori[i]+MR.valori[j];
k++;
j++;
i++;
}
else
{
i++;
j++;
}
}
if(i<this->dim)
for(;i<dim;i++,k++)
{
rezMR.linii[k] = this->linii[i];
rezMR.coloane[k] = this->coloane[i];
rezMR.valori[k] = this->valori[i];
}
if(j<MR.dim)
for(;j<MR.dim;j++,k++)
{
rezMR.linii[k] = MR.linii[j];
rezMR.coloane[k] = MR.coloane[j];
rezMR.valori[k] = MR.valori[j];
}
return rezMR;
}
Pentru a minimiza numărul de parcurgeri ale celor două matrice rare, în acest caz sunt
necesare doar două parcurgeri, în metoda prezentată se porneşte de la ipoteza că matricea rară
este generată prin parcurgerea pe linii a matricei iniţiale. Acest lucru asigură o ordine între
elementele matricei rare şi permite identificare mai eficientă a elementelor comune. De
asemenea, este implementată o parcurgere simultană a celor două matrice. Elementele curente
din cele două matrice sunt analizate în ordine prin prisma valorii liniei şi a coloanei. În cazul
în care elementele se găsesc pe linii diferite, elementul care are valoarea liniei mai mică este
adăugat la rezultat şi se trece la următorul element din matricea respectivă. Dacă elementele
curente din cele două matrice prezintă aceeaşi valoare pentru linie, atunci se compară valoarea
coloanelor. Pentru elementele comune, se analizează rezultatul sumei şi se memorează doar
valorile nenule.
Implementarea operatorului de scădere este absolut similară cel ui de adunare, singura
diferenţă fiind aceea că în cazul elementelor comune se calculează diferenţa lor, în locul
adunării. O altă abordare, este dată de utilizarea sumei, negând anterior valorile matricei ce se
scade. Prin utilizarea operatorului MatriceRara MatriceRara::operator *(double valoare) ce
permite înmulţirea matricei cu o valoare dată, scăderea se realizează prin supraîncărcarea
operatorului -.
40
MatriceRara MatriceRara::operator *(double valoare)
{
MatriceRara rezMR;
if(valoare)
{
rezMR = *this;
for(int i=0;i<rezMR.dim;i++)
rezMR.valori[i]*=valoare;
}
return rezMR;
}
MatriceRara MatriceRara::operator -(MatriceRara &MR)
{
return ((*this)+(MR*-1));
}
41
2.5. Înmulţirea şi inversarea matricelor rare
Pentru înmulţirea matricei rare A, (m, l) dimensională, cu matricea rară B, (l, n)
dimensională, se utilizează procedura standard, având în vedere că metoda getValoareElement
şi operatorul (i,j) permit accesul direct la elementele matricei rare.
Pentru a genera matricea rezultat, ca şi în cazul operaţiilor de adunare şi scădere, este
nevoie să se determine, anterior efectuării produsului, numărul de elemente ale rezultatului.
Acest lucru se realizează prin simularea produsului şi contorizarea numărului de valori
nenule.
if(this->n!=MR.m)
return rezMR;
for(int i=0;i<this->m;i++)
for(int j=0;j<MR.n;j++)
{
double val = 0;
for(int k=0;k<this->n;k++)
{
val+=this->getValoareElement(i,k)*MR.getValoareElement(k,j);
}
if(val) rezdim++;
}
rezMR.dim = rezdim;
rezMR.m = this->m;
rezMR.n = MR.n;
int l = 0;
for(i=0;i<this->m;i++)
for(int j=0;j<MR.n;j++)
{
double val = 0;
for(int k=0;k<this->n;k++)
{
val+=this->getValoareElement(i,k)*MR.getValoareElement(k,j);
}
if(val)
42
{
rezMR.linii[l] = i;
rezMR.coloane[l] = j;
rezMR.valori[l] = val;
l++;
}
}
return rezMR;
}
1
1: A1 = A p1 tr(A1 ) B1 I p1 * A1
1
1
2: A2 = A * B1 p 2 tr(A 2 ) B2 = I – p2 * A2
2
...
1
n-1: An-1 = A * Bn-2 p n 1 tr(A n 1 ) Bn 1 I p n 1 * A n 1
n 1
1
n: An = A* Bn-1 p n tr(A) Bn I p n * A n
n
Prin tr(A) se înţelege urma matricei A, suma elementelor diagonale, iar I reprezintă
matricea unitate de aceeaşi dimensiune cu matricea A. Aceste elemente sunt implementate în
clasa MatriceRară prin intermediul metodelor Unitate(int) şi Urma().
MatriceRara MatriceRara::Unitate(int n)
{
MatriceRara rezMR;
if(n>0)
{
rezMR.n=rezMR.m=rezMR.dim = n;
rezMR.coloane = new int[n];
rezMR.linii = new int[n];
rezMR.valori = new double[n];
for(int i=0;i<n;i++)
{
rezMR.coloane[i] = i;
rezMR.linii[i] = i;
rezMR.valori[i] = 1;
}
43
return rezMR;
}
double MatriceRara::Urma()
{
double rez = 0;
if(this->m==this->n)
{
for(int i=0;i<this->m;i++)
rez+=this->getValoareElement(i,i);
}
return rez;
}
Krâlov a demonstrat că după parcurgerea celor n paşi, Bn este o matrice identic nulă.
De aici rezultă inversa matricei A:
MatriceRara MatriceRara::Inversa()
{
MatriceRara tempMR, rezMR;
if(initialaMR.m==initialaMR.n)
{
double p = initialaMR.Urma() ;
rezMR = initialaMR - (unitateMR*p);
for(int k=2;k<initialaMR.m;k++)
{
tempMR = initialaMR*rezMR;
p = (1.0/(double)k)*tempMR.Urma();
rezMR = tempMR - (unitateMR * p);
}
tempMR = initialaMR*rezMR;
p = (1.0/(double)k)*tempMR.Urma();
rezMR = rezMR*(1.0/p);
}
return rezMR;
}
44
Avantajele acestui algoritm constau în simplitatea implementării şi precizia
rezultatelor, datorată folosirii unui număr redus de operaţii de împărţire.
Teme de control
Bibliografie
Ion IVAN, Marius POPA, Paul POCATILU - Structuri de date, Vol. 1 si 2, Editura ASE,
Bucuresti, 2008.
45
3. Articolul – structură de date neomogenă şi contiguă
După studierea acestei unităţi de învăţare, studenţii îşi vor însuşi cunoştinţe
teoretice şi vor deprinde abilităţi practice de lucru cu articole ca structuri de
date neomogene si contigue.
C 8 C 1 C 2 C 4 C 3 C 5 C 6 C 7 (3.1)
46
C5 – data ultimei aprovizionări şir caractere 9
C6 – intrări număr 6
C7 – ieşiri număr 6
C9 – stoc final număr 9
nume articol
Se observă că pe ultimul nivel se află date elementare specificate prin nume şi tip.
Structura, în ansamblul ei, are un nume şi se defineşte distinct, ceea ce urmează reprezentând
componentele de la nivelul al doilea.
unde:
47
1 reprez int ă baitul cerut de alinierea câmpului j 1
j (3.4)
0 în cazul în care câmpj 1 nu cere alinierea
01 material
02 cod
02 nume
02 um
02 preţ
02 cantitate
02 intrări
02 ieşiri
01 .................
Terminarea descrierii structurii, este marcată de apariţia unui număr de nivel care
delimitează începutul altei structuri, sau o instrucţiune de program, proprie limbajului, care
vine să marcheze începutul unei alte secvenţe program.
O altă descriere este:
7 material
8 cod
9 nume
10 um
8 preţ
10 cantitate
9 intrări
9 ieşiri
4 ..........................
unde, pentru primul nivel se utilizează numerele 4, 5, 6 sau 7, iar pentru al doilea nivel, sunt
utilizate numerele 8, 9 sau 10.
La acest nivel de descriere a articolelor putem observa că o dată elementară este un
articol cu câmpuri de acelaşi tip.
Astfel:
typedef struct a
{
int b;
};
typedef struct c
{
48
int c1;
int c2;
int c3;
int c4;
int c5;
};
şi
a x;
c w,y;
int u;
int t[5],v[5];
iar
Câmpurile unui articol se numesc membrii articolului, iar referirea lor se face folosind
operatorul de referire „.”.
În exemplul considerat, w şi y sunt variabile de tip c, iar t şi v sunt masive, fiecare
având 5 elemente. Prin w.C 4 se referă câmpul C 4 al variabilei w, iar prin y.C 1 se referă câmpul
C 1 al variabilei y.
Dacă se ia în considerare tipul de variabilă articol material şi se face definirea:
material m;
m.cod, m.preţ sunt referiri ale câmpurilor ce alcătuiesc variabila m definită ca având tipul
material .
Se observă că articolul este un tip de dată generic, pe care prin forma de definire a
câmpurilor îl putem folosi pentru a obţine tipuri derivate de date.
Astfel, material şi C sunt tipuri derivate de date corespunzătoare tipului gene ric struct .
Definirea acestui tip pune în evidenţă:
- componentele, câmpurile care-l alcătuiesc;
- natura câmpurilor;
- poziţia câmpurilor.
Altfel spus, se evidenţiază structura sau şablonul datelor.
Variabilele definite pentru un tip derivat ocupă atâta memorie cât rezultă din lungimile
câmpurilor însumate şi corectate cu alinierea şi au în componenţă exact aceleaşi elemente
folosite la descrierea tipului, ca membri ai fiecărei variabile.
Descrierea unui tip derivat nu înseamnă rezervare de memorie, ci stocare de informaţie
privind elementele care îl definesc.
Definirea variabilelor de tipul derivat descris anterior, efectuează rezervare de
memorie.
49
3.2. Structuri de date ierarhizate
Datele de tip articol sunt ierarhizate pe două sau mai multe nivele. Pentru a obţine
descrierea mai multor nivele este necesar ca la fiecare nivel inferior să fie posibilă enumerarea
de date elementare sau date de grup. Datele de grup e necesar să fie descrise deja înainte de a
le folosi, dar în unele cazuri particulare descrierea l or se efectuează ulterior folosirii.
Astfel, se definesc tipurile:
struct data
{
int zi;
int luna;
int an;
};
struct adresa
{
data data_stabilirii;
char strada[30];
int numar;
char telefon[8];
char oras[30];
};
struct persoana
{
data data_nasterii ;
adresa locul_nasterii;
int varsta;
data data_angajarii;
adresa adresa_actuala;
};
01 persoana
02 data_stabilirii
03 zi
03 luna
03 an
02 locul_nasterii
03 data_nasterii
04 zi
04 luna
04 an
03 strada
03 numar
03 telefon
03 oras
50
02 varsta
02 data_angajarii
03 zi
03 luna
03 an
02 adresa_actuala
03 data_stabilirii
04 zi
04 luna
04 an
03 strada
03 numar
03 telefon
03 oras
rezultă că adresa unui element y j de pe nivelul i aparţinând datei de grup k dintr-o structură de
tip articol, se obţine prin relaţia:
k 1 j 1
adr y j , 0 adr y1 , i lg y k , i k lg y h , i k
k 1 h 1 (3.9)
struct student
{
char nume[21];
data data_nasterii;
data data_admiterii;
};
adresa câmpului data_admiterii.an se obţine urmărind modelul grafic din figura 3.2
51
student
zi luna an zi luna an
şi după formula:
adr(student.data_admiterii.an,3)=adr(student.nume,2)+
+ lg(student.nume,2)+ 1+
+ lg(student.data_nasterii,2)+ 2+
+ lg(student.data_nasterii.zi)+ 3+
+ lg(student.data_nasterii.luna)+ 4+
+ lg(student.nume,2)+ 21+ 1+4+4+4+
+ 2+4+ 3+4+ 4=41+1=42 (3.10)
pentru că 2 = 3 = 4 = 0.
Dacă se consideră punctul operator ca orice alt operator, atunci construcţia:
a.b.c.d (3.11)
este o expresie care se evaluează ca orice altă expresie aritmetică, logică sau raţională. O
astfel de expresie o numim expresie referenţială.
Expresia referenţială se evaluează de la stânga la dreapta, pentru că în final trebuie să
se obţină o adresă. Referirea oricărui operand se face fie prin numele său, dacă abordarea este
la nivel de limbaj, fie prin adresă, dacă analiza este din punctul de vedere al programatorului
interesat să cunoască mecanismele proprii execuţiei programelor.
Interpretarea expresiei 3.11 este:
d este membru în structura de tip articol c
c este membru în structura de tip articol b
b este membru în structura de tip articol a
ceea ce ca model grafic îi corespunde:
52
câmp. Problematica devine cu atât mai importantă cu cât variabilele de tip articol se utilizează
pentru partiţionarea memoriei alocate unor buffere utilizate în operaţii de intrare/ieşire.
Neoptimizarea prelucrărilor este benefică cu condiţia ca interpretarea grupului de baiţi
să fie controlată de programator, chiar dacă citirea lor are un alt şablon decât cel utilizat la
scriere.
nume varsta facult an_studiu ··· nume varsta facult an_studiu ···
x[14].varsta
În cazul în care câmpurile unui articol sunt elemente ale unui masiv unidimensional,
definim o structură de vectori. De exemplu:
53
typedef struct student
{
char nume[30];
int note[10];
};
student x;
x.nota[3]
stud y[20];
y[16].nota[4]
Dacă matricea este privită ca vectori de vectori, extensiile sunt făcute pentru masivele
tridimensionale ca vectori de matrice sau matrice de vectori, iar pentru masivele cu patru
dimensiuni, ca masive bidimensionale de masive bidimensionale, sau vectori de masive
tridimensionale sau masive tridimensionale de vectori.
Generalizările decurg din posibilitatea de a construi structuri de structuri şi uneori de a
introduce aspectul recursiv al descrierii. Secvenţa:
54
corespunde descrierii:
int c[5][5][5];
iar:
corespunde descrierii:
int z[5][5][5][5];
c[i][j][k] (3.14)
y[i][j][k][h] (3.15)
nume_vector[i].nume_membru (3.16)
nume_structură.nume_membru[i] (3.17)
#include <windows.h>
#include <process.h>
#include <ctype.h>
#include <stdio.h>
#include <conio.h>
#include <string.h>
struct data
{
int da_year,da_day,da_mon;
};
struct adresa
{
char str[25];
int nr;
char bloc[3];
55
char sc;
int ap;
int sect;
long codp;
char loc[25];
char jud[20];
};
struct ID
{
char seria[3];
long nr;
data dataE;
int circa;
};
struct actionar
{
char cod[6];
char nume[30];
struct adresa adr;
data dataN;
struct ID BI;
char reprez[6];
int nr_act;
};
struct ind
{
char cheie[6];
long poz;
};
struct nod
{
char sir[6];
nod *st;
nod *dr;
};
ind index[1000];
char *mes[40]={
"Data trebuie sa fie numerica!",
"Ziua trebuie sa fie intre 1 si 31!",
"Luna trebuie sa fie intre 1 si 12!",
"Anul trebuie sa fie intre 1900 si 2001!",
"",
"Data trebuie sa fie cuprinsa intre 0 si 1!",
"Serie incorecta!"
56
};
long lg_fis()
{
long lung;
FILE *pf;
pf=fopen("actionar.dat","rb");
fseek(pf, 0, SEEK_END);
lung=ftell(pf);
fclose(pf);
return lung;
}
int indexare()
{
FILE *pf;
actionar act;
if ((pf=fopen("actionar.dat","rb"))==NULL)
{
printf("Fiserul cu date despre actionari nu exista!");
getch();
exit(1);
}
int k=0;
long pozitie;
while(fread(&act,sizeof(struct actionar),1,pf))
57
{
pozitie=ftell(pf)-sizeof(struct actionar);
strncpy(index[k].cheie,act.cod,6);
index[k].poz=pozitie;
k++;
}
fclose(pf);
return k;
void sortare(int n)
{
struct ind temp;
for(int i=0;i<n-1;i++)
for(int j=i;j<n;j++)
if(strcmp(index[i].cheie,index[j].cheie)>0)
{
temp=index[i];
index[i]=index[j];
index[j]=temp;
}
}
void sortareN()
{
struct actionar act,act1;
58
FILE *pf,*pt;
if((pf=fopen("actionar.dat","rb+"))==NULL)
{
printf("Fisierul cu date despre actionari nu exista!");
getch();
return;
}
pt=fopen("temp.dat","wb");
int vb,na=lg_fis()/sizeof(struct actionar);
for(int i=0;i<na;i++)
{
fread(&act,sizeof(struct actionar),1,pf);
fwrite(&act,sizeof(struct actionar),1,pt);
}
fclose(pt);
fclose(pf);
pt=fopen("temp.dat","rb+");
do
{
vb=0;
fseek(pt,0,0);
for(int i=0;i<na-1;i++)
{
fread(&act,sizeof(struct actionar),1,pt);
fread(&act1,sizeof(struct actionar),1,pt);
if (stricmp(act.nume,act1.nume)>0 )
{
fseek(pt,ftell(pt)-2*sizeof(struct actionar),0);
fwrite(&act1,sizeof(struct actionar),1,pt);
fwrite(&act,sizeof(struct actionar),1,pt);
vb=1;
}
fseek(pt,ftell(pt)-sizeof(struct actionar),0);
}
}
while(vb==1);
fclose(pt);
}
59
VB=0;
char tab[255];
fflush(stdin);
gets(tab);
if (strlen(tab)==0)
VB=1;
else
{
i=0;
while(VB==0&&i<strlen(tab))
{
if(isdigit(tab[i]))
i++;
else
VB=1;
}
}
if(VB==0)
sscanf(tab,"%ld",&val);
else
eroare(cod);
}
while(VB!=0);
}
char tab[255];
fflush(stdin);
gets(tab);
if (strlen(tab)==0)
VB=1;
else
{
i=0;
while(VB==0&&i<strlen(tab))
{
if(isdigit(tab[i]))
i++;
else
VB=1;
}
}
if(VB==0)
60
sscanf(tab,"%f",&val);
else
eroare(cod);
}
while(VB!=0);
}
char tab[255];
fflush(stdin);
gets(tab);
if (strlen(tab)==0)
VB=1;
else
{
i=0;
while(VB==0&&i<strlen(tab))
{
if(isdigit(tab[i]))
i++;
else
VB=1;
}
}
if(VB==0)
sscanf(tab,"%d",&val);
else
eroare(cod);
}
while(VB!=0);
}
61
{
i=0;
while(VB==0&&i<2)
{
if(isalpha(tab[i]))
i++;
else
VB=1;
}
}
if(VB==0)
strncpy(val,tab,3);
else
eroare(cod);
}
while(VB!=0);
}
void sortareA()
{
struct actionar act,act1;
FILE *pf,*pt;
if((pf=fopen("actionar.dat","rb+"))==NULL)
{
printf("Fisierul cu date despre actionari nu exista!");
getch();
return;
}
pt=fopen("temp.dat","wb");
int vb,na=lg_fis()/sizeof(struct actionar);
for(int i=0;i<na;i++)
{
fread(&act,sizeof(struct actionar),1,pf);
fwrite(&act,sizeof(struct actionar),1,pt);
}
fclose(pt);
fclose(pf);
pt=fopen("temp.dat","rb+");
do
{
vb=0;
fseek(pt,0,0);
for(int i=0;i<na-1;i++)
{
fread(&act,sizeof(struct actionar),1,pt);
fread(&act1,sizeof(struct actionar),1,pt);
if ((unsigned)act.nr_act<(unsigned)act1.nr_act)
{
fseek(pt,ftell(pt)-2*sizeof(struct actionar),0);
fwrite(&act1,sizeof(struct actionar),1,pt);
62
fwrite(&act,sizeof(struct actionar),1,pt);
vb=1;
}
fseek(pt,ftell(pt)-sizeof(struct actionar),0);
}
}
while(vb==1);
fclose(pt);
}
void creare()
{
int v;
char c,key[6];
FILE *pf;
actionar act;
nod *RAD=NULL;
if ((pf=fopen("actionar.dat","rb"))!=NULL)
{
printf("ATENTIE\n");
printf("Fiserul cu date despre actionari exista!\n");
printf("Datele existente se vor pierde!\n");
printf("Doriti sa-l rescrieti?[D/N]: ");
do
c=getch();
while(c!='D'&&c!='N');
if (c=='N')
{
return;
}
}
if ((pf=fopen("actionar.dat","wb"))==NULL)
{
printf("Eroare la creare!");
exit(1);
}
else
{
printf("Cod: ");
while(scanf("%6s",key)!=0)
{
v=0;
insnod(RAD,key,v);
if (v)
{
printf("\n\aArticolul cu aceasta cheie exista!");
Sleep(500);
fflush(stdin);
continue;
}
strncpy(act.cod,key,6);
63
fflush(stdin);
printf("Nume: ");
gets(act.nume);
fflush(stdin);
printf("Adresa actionarului\n");
printf("\tStrada: ");
gets(act.adr.str);
printf("\tNumarul: ");
valid_nr_i(act.adr.nr,1);
printf("\tBloc: ");
scanf("%s",act.adr.bloc);
fflush(stdin);
printf("\tScara: ");
scanf("%c",&act.adr.sc);
printf("\tApartament: ");
valid_nr_i(act.adr.ap,1);
printf("\tSector: ");
valid_nr_i(act.adr.sect,1);
printf("\tCod postal: ");
valid_nr_l(act.adr.codp,1);
fflush(stdin);
printf("\tLocalitatea: ");
gets(act.adr.loc);
printf("\tJudetul: ");
gets(act.adr.jud);
do
{
printf("Data nasterii\n");
printf("\tZiua:");
valid_nr_i(act.dataN.da_day,1);
if(act.dataN.da_day<1||act.dataN.da_day>31)
eroare(2);
}
while(act.dataN.da_day<1||act.dataN.da_day>31);
printf("\tLuna:");
do
{
valid_nr_i(act.dataN.da_mon,1);
if(act.dataN.da_mon<1||act.dataN.da_mon>12)
eroare(3);
}
while(act.dataN.da_mon<1||act.dataN.da_mon>12);
printf("\tAnul:");
do
{
valid_nr_i(act.dataN.da_year,1);
if(act.dataN.da_year<1900||act.dataN.da_year>2001)
eroare(4);
}
64
while(act.dataN.da_year<1900||act.dataN.da_year>2001);
printf("Buletin de identitate\n");
printf("\tSeria:");
valid_ab_sb(act.BI.seria,7);
printf("\tNumarul:");
valid_nr_l(act.BI.nr,1);
printf("Data eliberarii\n");
printf("\tZiua:");
do
{
valid_nr_i(act.BI.dataE.da_day,1);
if(act.BI.dataE.da_day<1||act.BI.dataE.da_day>31)
eroare(2);
}
while(act.BI.dataE.da_day<1||act.BI.dataE.da_day>31);
printf("\tLuna:");
do
{
valid_nr_i(act.BI.dataE.da_mon,1);
if(act.BI.dataE.da_mon<1||act.BI.dataE.da_mon>12)
eroare(3);
}
while(act.BI.dataE.da_mon<1||act.BI.dataE.da_mon>12);
printf("\tAnul: ");
do
{
valid_nr_i(act.BI.dataE.da_year,1);
if(act.BI.dataE.da_year<1900||act.BI.dataE.da_year>2001)
eroare(4);
}
while(act.BI.dataE.da_year<1900||act.BI.dataE.da_year>2001);
printf("\tCirca de politie: ");
valid_nr_i(act.BI.circa,1);
printf("Numarul de actiuni detinute: ");
valid_nr_i(act.nr_act,1);
printf("\n");
fwrite(&act,sizeof(struct actionar),1,pf);
printf("Cod: ");
}
}
del_arb(RAD);
fclose(pf);
}
65
void creare_date()
{
char c;
float ksoc,pfn,cota;
long int val_A;
FILE *pd;
if ((pd=fopen("date.dat","rb"))!=NULL)
{
printf("ATENTIE\n");
printf("Fiserul cu date despre profit exista!\n");
printf("Datele existente se vor pierde!\n");
printf("Doriti sa-l rescrieti?[D/N]: ");
do
c=getch();
while(c!='D'&&c!='N');
if (c=='N')
{
return;
}
}
if ((pd=fopen("date.dat","wb"))==NULL)
{
printf("Eroare la creare!");
getch();
return;
}
printf("DATE PRIVIND CAPITALUL SOCIAL SI PROFITUL\n");
printf("Valoarea unei actiuni:");
valid_nr_l(val_A,1);
fwrite(&val_A,sizeof(long int),1,pd);
printf("Capital Social:");
valid_nr_f(ksoc,1);
fwrite(&ksoc,sizeof(float),1,pd);
printf("Profit net:");
valid_nr_f(pfn,1);
fwrite(&pfn,sizeof(float),1,pd);
do
{
printf("Cota din profit repartizata la dividende [subunitara]:");
scanf("%f",&cota);
if (cota>1||cota <0)
eroare(6);
}
while(cota>1||cota <0);
fwrite(&cota,sizeof(float),1,pd);
fclose(pd);
}
66
printf("\n\nCod: %s\n",act.cod);
printf("Nume: "); puts(act.nume);
printf("Adresa actionarului\n");
printf("\tStrada:"); puts(act.adr.str);
printf("\tNumarul: %d\n",act.adr.nr);
printf("\tBloc: %s ",act.adr.bloc);
printf("Scara: %c Apartament: %d Sector:
%d\n",act.adr.sc,act.adr.ap,act.adr.sect);
printf("\tCod postal: %ld\n",act.adr.codp);
printf("\tLocalitatea: ");puts(act.adr.loc);
printf("\tJudetul: "); puts(act.adr.jud);
printf("Data nasterii:
%d/%d/%d\n",act.dataN.da_day,act.dataN.da_mon,act.dataN.da_year);
printf("Buletin de identitate\n");
printf("\tSeria: %s",act.BI.seria);
printf(" Numarul: %ld\n",act.BI.nr);
printf("\tData eliberarii:
%d/%d/%d\n",act.BI.dataE.da_day,act.BI.dataE.da_mon,act.BI.dataE.da_year);
printf("\tCirca de politie: %d\n",act.BI.circa);
void cons()
{
int n;
char key[6];
int vb,i,c;
actionar act;
n=indexare();
sortare(n);
FILE *pf=fopen("actionar.dat","rb");
while(printf("\nCod actionar:"),(c=scanf("%6s",key))!=0)
{
vb=cautare(n,i,key);
if(vb==-1)
{
printf("\n\aArticolul cu aceasta cheie nu exista!\n");
Sleep(500);
fflush(stdin);
}
else
{
fseek(pf,index[i].poz,0);
fread(&act,sizeof(struct actionar),1,pf);
afis_date(act);
getch();
}
67
}
fclose(pf);
}
void modif()
{
int n;
char key[6];
int vb,i,nr;
actionar act;
n=indexare();
sortare(n);
FILE *pf=fopen("actionar.dat","rb+");
while(printf("\nCod actionar:"),scanf("%6s",key)!=EOF)
{
vb=cautare(n,i,key);
if(vb==-1)
{
printf("\n\aArticolul cu aceasta cheie nu exista!");
Sleep(500);
}
else
{
fseek(pf,index[i].poz,0);
fread(&act,sizeof(struct actionar),1,pf);
afis_date(act);
printf("Noul numar:");
valid_nr_i(nr,1);
act.nr_act=nr;
fseek(pf,index[i].poz,0);
fwrite(&act,sizeof(struct actionar),1,pf);
}
}
fclose(pf);
}
void listA()
{
FILE *pt,*ptxt;
unsigned int tot_a=0;
long int val_a;
float ksoc;
actionar act;
sortareA();
FILE *pd=fopen("date.dat","rb");
fread(&val_a,sizeof(long int),1,pd);
fread(&ksoc,sizeof(float),1,pd);
fclose(pd);
pt=fopen("temp.dat","rb");
while(fread(&act,sizeof(struct actionar),1,pt))
tot_a+=act.nr_act;
68
rewind(pt);
ptxt=fopen("lista1.txt","w");
fprintf(ptxt,"\n\n\n\t\tLista actionarilor dupa numarul de actiuni\n");
fprintf(ptxt,"\t\t******************************************\n\n");
fprintf(ptxt,"**********************************************************
*************\n");
fprintf(ptxt,"* Cod * Nume Prenume * Numar * Ponderea
actiunilor*\n");
fprintf(ptxt,"* * * actiuni * in capitalul social*\n");
fprintf(ptxt,"**********************************************************
*************\n");
while(fread(&act,sizeof(struct actionar),1,pt))
{
fprintf(ptxt,"*%6s *%-30s*%8u *%18.3f
%%*\n",act.cod,act.nume,act.nr_act,(float)(unsigned)act.nr_act*val_a/ksoc*100);
fprintf(ptxt,"**********************************************************
*************\n");
}
fprintf(ptxt,"**********************************************************
*************\n");
fclose(pt);
fclose(ptxt);
}
void listD()
{
FILE *pt,*ptxt;
SYSTEMTIME data;
float pfn,cota;
float div;
actionar act;
sortareA();
unsigned int tot_a=0;
FILE *pd=fopen("date.dat","rb");
fseek(pd,sizeof(long int)+sizeof(float),0);
fread(&pfn,sizeof(float),1,pd);
fread(&cota,sizeof(float),1,pd);
fclose(pd);
pt=fopen("temp.dat","rb");
while(fread(&act,sizeof(struct actionar),1,pt))
tot_a+=act.nr_act;
div=(pfn*cota)/tot_a;
rewind(pt);
ptxt=fopen("lista2.txt","w");
GetSystemTime(&data);
fprintf(ptxt,"\n\t\t\t\tDividende cuvenite actionarilor la data
%d.%d.%d\n",data.wDay,data.wMonth,data.wYear);
fprintf(ptxt,"\t\t\t\t************************************************\n\n")
;
fprintf(ptxt,"Profit net:%.0f lei\n",pfn);
69
fprintf(ptxt,"Cota de profit repartizata la dividende:%.2f\n",cota);
fprintf(ptxt,"Total actiuni:%u\n",tot_a);
fprintf(ptxt,"Dividende per actiune:%.2f lei/act\n\n",div);
fprintf(ptxt,"**********************************************************
**************************************************************\n");
fprintf(ptxt,"* Nume Prenume * Data * Adresa
*Buletin de * Numar * Dividende *\n");
fprintf(ptxt,"* * nasterii * *identitate *
actiuni* *\n");
fprintf(ptxt,"**********************************************************
**************************************************************\n");
while(fread(&act,sizeof(struct actionar),1,pt))
{
fprintf(ptxt,"*%-30s*%2d.%2d.%d*Str:%-29s Nr:%-5d*%-2s
%8ld*%8u*%11.2f
*\n",act.nume,act.dataN.da_day,act.dataN.da_mon,act.dataN.da_year,act.adr.str,act.ad
r.nr,act.BI.seria,act.BI.nr,act.nr_act,(unsigned)act.nr_act*div);
fprintf(ptxt,"* * *Bl:%c%c%c Sc:%c Ap:%-3d
Sect:%-2d Cod p:%-8ld * * *
*\n",act.adr.bloc[0],act.adr.bloc[1],act.adr.bloc[2],act.adr.sc,act.adr.ap,act.adr.sect,act.a
dr.codp);
fprintf(ptxt,"* * *Loc:%-20s * *
* *\n",act.adr.loc);
fprintf(ptxt,"**********************************************************
**************************************************************\n");
}
fprintf(ptxt,"**********************************************************
**************************************************************\n");
fclose(pt);
fclose(ptxt);
}
void listAB()
{
FILE *pt,*ptxt;
actionar act;
sortareN();
pt=fopen("temp.dat","rb");
ptxt=fopen("lista3.txt","w");
fprintf(ptxt,"\n\n\n\t\t\tLista actionarilor in ordine alfabetica\n");
fprintf(ptxt,"\t\t\t***************************************\n\n");
fprintf(ptxt,"\t\t**************************************************\n");
fprintf(ptxt,"\t\t* Nr. * Nume Prenume * Numar *\n");
fprintf(ptxt,"\t\t* crt. * * actiuni *\n");
fprintf(ptxt,"\t\t**************************************************\n");
int i=0;
while(fread(&act,sizeof(struct actionar),1,pt))
{
70
fprintf(ptxt,"\t\t*%6d *%-30s*%8u *\n",++i,act.nume,act.nr_act);
fprintf(ptxt,"\t\t**************************************************\n");
}
fprintf(ptxt,"\t\t**************************************************\n");
fclose(pt);
fclose(ptxt);
}
void do_sterg()
{
int n,c;
int vb,i;
actionar act;
n=indexare();
sortare(n);
char key[6];
FILE *pf=fopen("actionar.dat","rb");
while(printf("\nCod actionar:"),scanf("%6s",key)!=0)
{
vb=cautare(n,i,key);
if(vb==-1)
{
printf("\n\aArticolul cu aceasta cheie nu exista!\n");
Sleep(500);
fflush(stdin);
continue;
}
else
{
fseek(pf,index[i].poz,0);
fread(&act,sizeof(struct actionar),1,pf);
afis_date(act);
printf("Doriti sa stergeti acest articol?[D/N]: ");
do
{
c=getch();
}
while(c!='D'&&c!='N');
if (c=='N')
{
continue;
}
}
//rescriere fisier
FILE *ptmp=fopen("tmp.dat","wb");
rewind(pf);
for(int j=0;j<index[i].poz/sizeof(struct actionar);j++)
{
fread(&act,sizeof(struct actionar),1,pf);
fwrite(&act,sizeof(struct actionar),1,ptmp);
71
}
fread(&act,sizeof(struct actionar),1,pf);
while(fread(&act,sizeof(struct actionar),1,pf))
fwrite(&act,sizeof(struct actionar),1,ptmp);
fclose(pf);
fclose(ptmp);
unlink("actionar.dat");
rename("tmp.dat","actionar.dat");
n=indexare();
sortare(n);
pf=fopen("actionar.dat","rb");
}
fclose(pf);
}
void adaug()
{
int v;
char key[6];
actionar act;
nod *RAD=NULL;
FILE *pf;
if ((pf=fopen("actionar.dat","rb+"))==NULL)
{
printf("Fiserul cu date despre actionari nu exista!");
return;
}
else
{
strncpy(act.reprez," ",6);
while(fread(&act,sizeof(struct actionar),1,pf))
insnod(RAD,act.cod,v);
//fseek(pf,0,2);
printf("Cod:\n");
while(scanf("%6s",key)!=0)
{
v=0;
insnod(RAD,key,v);
if (v)
{
72
printf("Adresa actionarului\n");
printf("\tStrada: ");
gets(act.adr.str);
printf("\tNumarul: ");
valid_nr_i(act.adr.nr,1);
printf("\tBloc: ");
scanf("%s",act.adr.bloc);
fflush(stdin);
printf("\tScara: ");
scanf("%c",&act.adr.sc);
printf("\tApartament: ");
valid_nr_i(act.adr.ap,1);
printf("\tSector: ");
valid_nr_i(act.adr.sect,1);
printf("\tCod postal: ");
valid_nr_l(act.adr.codp,1);
fflush(stdin);
printf("\tLocalitatea: ");
gets(act.adr.loc);
printf("\tJudetul: ");
gets(act.adr.jud);
do
{
printf("Data nasterii\n");
printf("\tZiua:");
valid_nr_i(act.dataN.da_day,1);
if(act.dataN.da_day<1||act.dataN.da_day>31)
eroare(2);
}
while(act.dataN.da_day<1||act.dataN.da_day>31);
printf("\tLuna:");
do
{
valid_nr_i(act.dataN.da_mon,1);
if(act.dataN.da_mon<1||act.dataN.da_mon>12)
eroare(3);
}
while(act.dataN.da_mon<1||act.dataN.da_mon>12);
printf("\tAnul:");
do
{
valid_nr_i(act.dataN.da_year,1);
if(act.dataN.da_year<1900||act.dataN.da_year>2001)
eroare(4);
}
while(act.dataN.da_year<1900||act.dataN.da_year>2001);
printf("Buletin de identitate\n");
printf("\tSeria:");
73
valid_ab_sb(act.BI.seria,7);
printf("\tNumarul:");
valid_nr_l(act.BI.nr,1);
printf("Data eliberarii\n");
printf("\tZiua:");
do
{
valid_nr_i(act.BI.dataE.da_day,1);
if(act.BI.dataE.da_day<1||act.BI.dataE.da_day>31)
eroare(2);
}
while(act.BI.dataE.da_day<1||act.BI.dataE.da_day>31);
printf("\tLuna:");
do
{
valid_nr_i(act.BI.dataE.da_mon,1);
if(act.BI.dataE.da_mon<1||act.BI.dataE.da_mon>12)
eroare(3);
}
while(act.BI.dataE.da_mon<1||act.BI.dataE.da_mon>12);
printf("\tAnul: ");
do
{
valid_nr_i(act.BI.dataE.da_year,1);
if(act.BI.dataE.da_year<1900||act.BI.dataE.da_year>2001)
eroare(4);
}
while(act.BI.dataE.da_year<1900||act.BI.dataE.da_year>2001);
printf("\tCirca de politie: ");
valid_nr_i(act.BI.circa,1);
printf("Numarul de actiuni detinute: ");
valid_nr_i(act.nr_act,1);
printf("\n");
fwrite(&act,sizeof(struct actionar),1,pf);
printf("Cod: ");
}
}
del_arb(RAD);
fclose(pf);
}
void men_fis()
{
int rasp,cont=1;
while(cont)
{
74
do
{
printf("MENIUL FISIERE\n\n");
printf("1.CREARE FISIER ACTIONARI\n");
printf("2.CREARE FISER CU DATE PRIVIND PROFITUL\n" );
printf("3.ADAUGARE ACTIONARI\n");
printf("4.MODIFICARE NUMAR DE ACTIUNI\n");
printf("5.STERGERE ACTIONARI DIN FISIER\n");
printf("6.REVENIRE LA MENIUL PRINCIPAL\n\n");
printf("Optiunea:");
valid_nr_i(rasp,5);//scanf("%d",&rasp);
}
while(rasp<1||rasp>6);
switch(rasp)
{
case 1:creare();break;
case 2:creare_date();break;
case 3:adaug();break;
case 4:modif();break;
case 5:do_sterg();break;
case 6:cont=0;break;
}
}
}
void men_sit()
{
int rasp,cont=1;
while(cont)
{
do
{
printf("MENIUL SITUATII DE IESIRE\n\n");
printf("1.LISTA ACTIONARILOR IN ORDINEA NUMARULUI DE
ACTIUNI\n");
printf("2.LISTA ACTIONARILOR IN ORDINE
ALFABETICA\n");
printf("3.LISTA DIVIDENDELOR CUVENITE FIECARUI
ACTIONAR\n");
printf("4.CONSULTARE FISIER DUPA COD ACTIONAR\n");
printf("5.REVENIRE LA MENIUL PRINCIPAL\n\n");
printf("Optiunea:");
valid_nr_i(rasp,5);//scanf("%d",&rasp);
}
while(rasp<1||rasp>5);
switch(rasp)
{
case 1:listA();break;
case 2:listAB();break;
case 3:listD();break;
case 4:cons();break;
75
case 5:cont=0;break;
}
}
}
void main()
{
int rasp,cont=1;
while(cont)
{
do
{
printf("MENIUL PRINCIPAL\n\n");
printf("1.FISIERE\n");
printf("2.SITUATII DE IESIRE\n");
printf("3.TERMINARE PROGRAM\n\n");
printf("Optiunea:");
valid_nr_i(rasp,5);//scanf("%d",&rasp);
}
while(rasp<1||rasp>3);
switch(rasp)
{
case 1:men_fis();break;
case 2:men_sit();break;
case 3:cont=0;break;
}
}
}
Teme de control
Bibliografie
Ion IVAN, Marius POPA, Paul POCATILU - Structuri de date, Vol. 1 si 2, Editura ASE,
Bucuresti, 2008.
76
4. Fişiere
Obiectivele unităţii de învăţare
După studierea acestei unităţi de învăţare, studenţii îşi vor însuşi cunoştinţe
teoretice şi vor deprinde abilităţi practice de lucru fişiere binare si text .
Studenţii vor învăţa lucrul cu funcţii de acces la resursele memorate pe
suporturile externe de date.
Masivele, listele şi arborii binari, sunt structuri de date interne, prin faptul că se află în
memoria internă a unui sistem de calcul. În momentul în care aceste structuri sunt stocate în
memoria externă – pe discuri, benzi magnetice, dischete, compact-discuri acestea sunt numite
structuri de date externe.
Memoria internă a unui calculator este imaginată ca un şir ordonat de baiţi. Fiecare
bait se defineşte prin adresă şi prin conţinut. Memoria externă este imaginată, de asemen ea, ca
un şir de baiţi, cu deosebirea că pentru calculul adresei sunt luate în considerare elementele
specifice modului de structurare a suportului.
Astfel, atunci când suportul este organizat în piste, cilindri şi problematica accesului
este rezolvată prin poziţionarea pe aceste unităţi de acces, adresarea efectuându -se luând în
calcul elemente structurale.
În plus, particularităţile de desfăşurare în plan fizic a operaţiilor de intrare/ieşire
determină analiza distinctă a raportului dintre semnificaţia instrucţiunilor I/O din programe şi
operaţiile efective de citire/scriere de pe suportul extern.
Presupunând că pentru un sistem de calcul modulele care realizează citiri/scrieri
operează cu conţinutul unor buffere de lungime L, tot timpul aceste funcţii furnizează
informaţii asupra începutului zonei ce este explorată, lungimea acesteia fiind rezultatul logicii
de organizare a datelor.
Programatorul are acces direct sau indirect la zona de memorie tampon prin
intermediul adresei sale de început sau prin int ermediul unei alte zone de memorie definită în
programul său, zonă în care este copiat conţinutul bufferului.
5 adresa de început
buffer
6 Operaţii de
Memoria c i t i r e / s c r i e r e
externă
1 4 3
zona de memorie
definită de
u t i l i z a t o r
77
Dinamica operaţiilor de intrare/ieşire determină actualizarea variabilei pointer care
delimitează partea de început a subzonei din buffer ce este copiată în zona de memorie
definită de utilizator.
În cazul funcţiilor de citire/scriere a unui caracter, variabila pointer se modifică cu o
unitate, marcând exact schimbul de informaţii în dialogul om – calculator .
În cazul citirilor/scrierilor cu format delimitatorul acceptat este analizat, iar algoritmul
de punere în corespondenţă este astfel proiectat încât acesta nu este integrat în setul
informaţiilor.
1 5 CR 1 2 0 CR 1 CR 1 CR
78
variabila pointer permite preluarea datelor din structura A, se majorează cu 120, preia datele
din structura B şi realizează o scriere fizică pe suport. Variabila pointer este apoi reiniţializată
şi va prelua datele structurii C după care efectuează a doua scriere fizică. În acest caz celor
trei scrieri logice le-au corespuns două scrieri fizice.
Există diferite modalităţi de realizare a operaţiilor de citire/scriere după cum se
folosesc sau nu factori de blocare, se definesc sau nu elemente de regăsire a informaţiilor.
Structurile de date externe nu diferă mult de structurile de date interne. Apar unele
complicaţii care nu sunt majore de altfel, prin aceea că volumul datelor este foarte mare şi
elementele repetitive abundă, ceea ce conduce la ideea că structurile de date externe sun t
privite ca structuri de structuri de date interne dispuse pe suport de memorie externă.
Pentru a realiza regăsirea într -un volum de date de dimensiuni remarcabile, este
necesară organizarea, sistematizarea datelor şi crearea acelor elemente indispensabil e
localizării, adresării.
Structurile de date externe sunt contigue, formate din elemente dispuse în continuarea
celorlalte şi necontigue, distanţa dintre elemente fiind o variabilă aleatoare a cărei lege de
repartiţie este identificabilă, dar care necesită memorarea distanţelor, întrucât nu se
construieşte un mecanism de generare cu repetare a acestora.
Structurile de date externe se regăsesc în cele mai multe cazuri sub denumirea de
fişiere. Când ating un nivel de structurare prin adrese suficient de dezvoltat, se formează
fişiere interdependente, iar în cazul unor structuri mai complexe se regăsesc sub denumirea de
baze de date .
În cazul în care conţinutul de lungime L este tratat distinct, se ia în discuţie conceptul
de înregistrare fizică. Se por neşte de la faptul că într -un buffer, de regulă sunt stocate datele
ce corespund unei structuri de tip articol , ce se recunosc sub denumirea de înregistrare logică
sau articol logic. Scopul este de a face deosebirea între modul în care se dispun informaţii le
pe suportul fizic şi modul în care sunt gândite organizările de date în raport cu prelucrările
particulare.
Introducerea factorilor de blocare vine să complice lucrurile, dar să îmbunătăţească
indicele de folosire a zonelor de memorie.
Dacă:
şi dacă există:
L
k (4.2)
L'
256
k 3,2 3 (4.3)
80
79
În cazul în care k’ = 1, pe cei 256 baiţi ai bufferului este încărcat un articol, ce este în
fişier. Deci fişierul conţine în final n articole fizice şi tot n articole logice, gradul de încărcare
cu informaţie utilă fiind:
n 80 10 (4.4)
g 1 100 100 30%
n 256 32
În cazul în care k’ = 2:
unde:
0, daca n e numar par
m
1, daca n e numar impar
Pentru k’ = 3:
n3 m 240
g 3 100 (4.6)
n 256
Se observă că:
80
Pentru simplificarea prezentării se consideră un suport extern S , căruia i se asociază un
model matriceal de dispunere a baiţilor. Baitul bij reprezintă baitul al j-lea, aflat pe pista i a
suportului. Suportul are n piste, iar pe o pistă se află m baiţi.
Pentru început se presupune că baiţii au aceeaşi destinaţie, respectiv de a memora
informaţii utile. Nu există baiţi care stochează informaţii privind structura suportului şi modul
de ocupare a acestuia.
81
consultare acel aşi fişier aparţine grupei a doua, iar dacă înaintea consultării se
efectuează actualizări, acelaşi fişier aparţine celei de a treia grupe.
e) Criteriul gestiunii fişierelor ia în considerare existenţa unui sistem de funcţii de
prelucrare care asigură efectuarea operaţiilor specifice lucrului cu fişiere, numit
sistem de gestiunea fişierelor. Acest criteriu împarte mulţimea fişierelor în:
- fişiere care sunt prelucrate prin apelarea de funcţii ale unui sistem de gestiune –
rezolvă întreaga problematică legată de organizarea şi accesul la date; funcţiilor
sistemului de gestiune a fişierelor le corespund instrucţiuni; parametrii
instrucţiunilor devin parametrii reali ai funcţiilor sistemului de gestiune;
programatorul abordează întreaga problematică numai din punct de vedere logic al
rezolvării problemei sale;
- fişiere pentru care sunt definite funcţii primitive de realizare a operaţiilor
elementare cu datele destinate suporturilor externe – programatorului îi revine
sarcina să gestioneze totalitatea informaţiilor necesare regăsirii elementelor ce
alcătuiesc fişierul; pentru programator, fişierul este o masă amorfă de baiţi, având
conţinut şi poziţii; el efectuează transferuri de baiţi din zone de memorie spre
suportul extern, indicând adrese acestor zone, lungimea zonei şi un mod de
reparare a fişierului; există situaţii în care se efectuează lucru direct în bufferul
asociat fişierului cu care se lucrează; în acest caz fişierul apare ca o resursă iar
gestiunea acestei resurse este lăsată integral la dispoziţia prog ramatorului;
- fişiere care se construiesc şi se utilizează folosind instrucţiunile limbajului de
asamblare – programatorul defineşte totalitatea condiţiilor pentru efectuarea
operaţiilor cu fişiere; sunt definite şi încărcate etichetele care sunt scrise; se
definesc bufferele şi se gestionează; se calculează adresele de pe suportul extern
unde vor fi scrise datele; programatorul preia în programele sale tot ceea ce
efectuează funcţiile primitive sau sistemul de gestiune a fişierelor; programul în
care apar f işierele gestionate de programator nu apelează alte funcţii decât cele
scrise de acesta şi foloseşte numai instrucţiuni ale limbajului de asamblare, cel
mult seturi de macroinstrucţiuni.
f) Criteriul organizării fişierelor, ia în considerare existenţa sau inexistenţa unor
informaţii de regăsire a datelor, după cum urmează:
- fişierele cu organizarea secvenţială - succesiune contiguă de articole fără existenţa
unor elemente de informare, altele decât delimitatorii de început şi de sfârşit; dacă
se ia în considerare similitudinea acestui mod de organizare cu înregistrarea pe o
casetă a şlagărelor unei formaţii rock, avem imaginea clară a tuturor posibilităţilor
de lucru cu fişierele secvenţiale; accesul la un şlagăr presupune audierea celor
care-l preced; ştergerea unui şlagăr, altul decât cel de la sfârşit presupune
existenţa a două casete; înregistrarea unui nou şlagăr, se face numai după ultimul
şlagăr anterior;
- fişierele însoţite de informaţii de tip index – presupun sortarea articolelor după
chei şi împărţirea acestora în submulţimi; punerea în corespondenţă a elementelor
din submulţimi cu adresele fizice determină realizarea într -un fişier a unei mulţimi
de subfişiere secvenţiale; cu ajutorul indecşilor se identifică subfişierul secvenţial
şi articolul căutat este preluat după ce a fost localizat prin parcurgerea articol după
articol a subfişierului; operaţiile de actualizare, vizează ştergerea de articole,
adăugarea de articole la sfârşitul fişierului sau subfişierelor, modificarea de
câmpuri, rescrier ea de articole şi inserarea de articole; subfişierele sunt masive
unidimensionale contigue, formate din articole; ştergerea este o operaţie de
dezactivare a elementelor, fără realizarea deplasării celorlalte elemente cu o
poziţie spre stânga, deci fizic ştergerea unui articol nu se produce, operaţia fiind
82
numai la nivel logic, în sensul că accesul la date este precedat de un test asupra
caracterului activ sau neactiv al articolului; inserarea de articole, pentru a menţine
criteriul ordonării elementelor care a determinat împărţirea mulţimii în
submulţimi de articole, presupune realizarea de liste înlănţuite; articolul inserat
este dispus într- o zonă disponibilă numită folcloric, zona de depăşire.
- fişiere ale căror articole sunt dispuse aleator pe suport – cunoscându-se
dimensiunile fişierului se alocă o zonă pe suportul extern printr -o operaţie numită
preformare; datele sunt puse în corespondenţă printr -un procedeu oarecare cu
adresele unde vor fi scrise; rezultă că procedeul de punere în corespondenţă este
cu atât mai performant cu cât el realizează o dispunere mai uniformă a articolelor
în zona rezervată; există posibilitatea ca folosind algoritmi de randomizare să se
obţină elemente ce intră în calculul adresei fizice a începutului zonei din suportul
extern în care se scrie un articol; pornind de la imposibilitatea să se genereze
numere diferite care să îndeplinească condiţia de apartenenţă la subintervale, pe
măsură ce se scriu articole în fişiere, are loc fărâmiţarea zonei rezervate; se
construiesc funcţii pentru gestionarea articolelor care au aceeaşi adresă fizică prin
calcul; aceste fişiere cu organizarea aleatoare, permit efectuarea întregii game de
operaţii, cu condiţia ca mecanismul de obţinere a adresei fizice să se menţină
neschimbat; fişierul organizat aleator apare ca o structură necontiguă în care
fiecare element este localizat pe baza unei formule, având ca parametru valoarea
unui câmp ce intră în structura articolului, câmp numit cheia articolului;
numeroasele lucrări care prezintă limbajul COBO L, redau imagini sugestive ale
modului în care se implementează filozofia fiecărui mod de organizare a
fişierelor, deci realitatea este alta, dacă se are în vedere organizarea matriceală a
suportului şi modul static în multe cazuri de alocare a memoriei ex terne.
g) Criteriul suportului unde este stocat fişierul împarte fişierele în:
- fişiere pe cartele perforate;
- fişiere pe bandă perforată;
- fişiere pe bandă magnetică;
- fişiere pe suport optic;
- fişiere pe disc;
- fişiere pe dischete;
- fişier în memoria internă.
Prelucrările moderne, neîngrădite de restricţiile lipsei de memorie internă, au condus
la citirea unui fişier ca un singur articol foarte mare şi prelucrarea acestuia în memorie. Logic
apare o singură instrucţiune de citire, deci apelarea o singură dată a funcţiei în realitate au loc:
L f
cf m (4.8)
L
83
care se fac mai întâi convenţiile şi apoi se stabilesc lungimile şirurilor care vor fi
scrise pe suport; fişierul apare ca o succesiune de elemente oarecare ale căror
poziţii sunt deduse, dacă se iau în calcul informaţiile pe care le oferă descriptorii
de format şi locul pe care fiecare element îl ocupă în lista de parametri ai funcţiei
apelate la scriere; este de dorit ca aceleaşi liste de variabile de la scriere să fie
utilizate şi la apelul funcţiilor de citire, iar descriptorii de format să se menţină
aceiaşi pentru a oferi succes prelucrărilor;
- fişiere de tip record în care datele sunt caracterizate prin lungime şi adresă de
început; articolele au lungime fixă sau variabilitatea lungimilor este în cadrul unei
mulţimi formate din câteva elemente; operaţiile de lucru cu fişierele de acest tip
nu sunt în general preceda te sau urmate de conversii; operaţiile ce preced
calculele sunt lăsate la dispoziţia programatorului;
De exemplu, fişierul stocurilor de materiale este:
- un fişier de tip record;
- are organizare indexată;
- se află pe disc;
- este gestionat cu funcţiile unui sistem de gestiune a fişierelor;
- are articole de lungime fixă;
- se efectuează toate operaţiile de actualizare pe el;
- conţine informaţii asupra poziţiei anumitor articole;
- articolele sunt identificate după codul materialului care joacă rol de cheie de
articol;
- fiecare material are o cheie unică;
- fişierul este sortat crescător.
Astfel, un fişier oarecare este caracterizat înscriind oricare dintre informaţiile ce
rezultă din criteriile specificate.
Fie mulţimea de elemente E 1 , E 2 , …, E n oarecare ce corespund structurilor de date
SD1 , SD2 , …, SDn definite într-un program P .
Elementele E i, i = 1, 2, …, n, sunt şiruri de baiţi caracterizate printr -un conţinut
rezultat din iniţializarea structurilor de date SDi, i = 1, 2, …, n. Pe un suport extern se alocă
elementelor E 1 , E 2 , …, E n zone de memorie de lungime:
lg SDi lg (4.9)
De obicei, lg(α) = 2. Cei doi baiţi ataşaţi fiecărui element vor conţine lungimea
articolului:
cont lg SDi
(4.11)
84
Fişierul este caracterizat printr -o adresă a articolelor. Astfel:
adr E 1 A lg (4.12)
unde, A reprezintă adresa fizică a primului bait a primului articol căruia i s -a ataşat în faţă
lg(α) baiţi pentru a memora lungimea articolului respectiv:
i 1
adr E i A cont j lg i (4.13)
j 1
sau:
i 1
adr E i A lg SDi i lg (4.14)
j 1
unde, β k reprezintă grupul de baiţi de lungime lg(α) ataşat elementului E k în fişier. Dacă:
atunci:
caz în care, în eticheta de început a fişierului se specifică tipul articolului lungime fixă. De
asemenea, se memorează şi lungimea, iar lg(β k ) devine zero.
Adresa unui element oarecare E i, este obţinută prin relaţia:
i 1
adr E i A lg SD j (4.17)
j 1
cont (E 1.x) > cont (E 2.x) > … > cont (E n.x) (4.19)
85
cont (E 1.x) cont (E 2.x) … cont (E n.x) (4.20)
deci, şirul elementelor este sortat crescător după câmpul x, care joacă rol de cheie a articolelor
în structura SD de generare.
Elementele de sortare vor fi memorate, fiecare ocupând o zonă de lungime:
lg SD lg (4.26)
86
unde, γ reprezintă o zonă în care este memorată o adresă fizică de pe suport, ţinând seama de
structurarea contiguă a suportului.
Fişierul are un fond informaţional de lungime:
L f n lg lg SD (4.27)
(cont(E i.x).a )
i (4.28)
şi rezultă o împrăştiere suficient de mare care să reducă şansele găsirii unei formule de calcul
a adresei fizice a unui articol, folosind strict ca informaţie cheia acestuia.
Dacă se acceptă ideea utilizării unui arbore binar cu 4 noduri terminale, mulţimea
elementelor E 1 , E 2 , …, E n este împărţită în 4 subşiruri, având un număr aproape identic de
elemente, reţinând adrese şi cheile elementelor ce delimitează fiecare subşir de articole:
Q2 cont E 1 m . x , am 1
(4.32)
Perechile pentru delimitarea sfârşitului pentru fiecare din cele 4 subşiruri sunt:
87
P 1 cont E m . x , am
P 2 cont E 2 m . x , a2 m
(4.35)
P 3 cont E 3m . x , a3m
P 4 cont E n . x , an
S-a considerat că fiecare subşir are m elemente, cu excepţia ultimului şir care are 1, 2
sau 3 elemente mai puţine.
Setului de date i se asociază arborele binar:
(Q1, P4)
Întrucât:
88
Trebuie realizat un echilibru între numărul de nivele ale arborelui binar şi numărul
subfişierelor, deoarece creşterea numărului de subfişiere conduce la creşterea duratei de acces
la acestea din cauza parcurgerii unui număr
număr prea mare de noduri în arborele binar.binar.
În cazul în care se doreşte inserarea unui articol E j într-un astfel de fişier se identifică
poziţia lui, astfel încât:
încât:
cont (E k
k .x) cont (E j.x) … cont (E k+1
k+1.x) (4.39)
În acest caz, articolul ca atare este memorat într- o zonă rezervată a fişierului, legătura
cu celelalte articole efectuându-se prin:
cont k ad r E j
(4.40)
cont j ad r E k 1
89
În continuare, se consideră un exemplu clas ic, foarte frecvent utilizat în descrierea
principiilor şi fundamentelor
fundamentelor de realizare a bazelor de date.
date.
Fie mulţimea M 1 a persoanelor dintr- o localitate, care se află într -una din relaţiile:
x este vecin cu y
x este fiul lui y
x este tatăl lui y
x este soţia lui y
x este soţul lui y
90
Deşi se discută foarte mult, cea mai costisitoare etapă din activitatea de manipulare a
bazelor de date o r eprezintă încărcarea acestora.
În cazul de faţă, datele nu suferă o uzură morală rapidă pentru că vizează indivizii
dintr-un cartier sau bloc. În cazul în care fenomenul are o dinamică accelerată, se observă că
la terminarea încărcării 60 – 80% din date sunt uzate moral şi baza de date practic este
inoperantă.
În cazul considerat, cele patru mulţimi conduc la date ce alimentează baza de date şi se
trage concluzia că în continuare ea este complet încărcată.
Pentru a vedea puterea de prelucrare pe care software-ul bazei de date o are,
exemplificăm câteva cereri:
- listarea locurilor de muncă ale membrilor familiei lui x;
- listarea proprietarilor de autoturisme cu culoarea z ;
- listarea tuturor celor care lucrează la locul de muncă w şi au autoturisme pentru care
plătesc taxa CASCO cuprinsă în intervalul [a, b];
- există o relaţie între capitalul social al firmelor şi uzura morală a autoturismelor pe
care le au salariaţii lor?
- listarea proprietarilor care plătesc un impozit mai mare dec ât C lei şi taxa CASCO
mai mare decât D lei;
- există meseriaşi de tipul z care au maşini de tipul u absolut noi şi care au vecini în
aceeaşi situaţie?
Dacă toate datele despre un individ, sunt înregistrate într -o structură cuprinzătoare,
care conţine elementele de descriere ale celor patru mulţimi, se observă o multitudine de
repetări, ceea ce conduce de fapt la regruparea informaţiilor. Cele 4 mulţimi nu sunt un dat, ci
sunt rezultatul unei analize a modului în care sunt sistematizate şi concentrate datele.
Pentru elementele din mulţimea M 1 se asociază arborescenţa:
persoana x
familie vecini
91
Producător
unde si este adresa articolului pentru descrierea autoturismului al cărui proprietar este
persoana ai din fişierul de persoane.
- din fişierul autoturismelor se extrage subşirul:
ceea ce înseamnă că persoanele ale căror înregistrări ocupă poziţiile k 1 , k 2 , … k m cu maşini de
culoare z şi afişarea:
92
Astfel, dacă se doreşte listarea tuturor celor care au locul de muncă w şi au autoturism
pentru care plătesc o taxă CASCO cuprinsă în intervalul [a, b] , se procedează astfel:
- se construieşte şirul adreselor persoanelor care au locul de muncă w:
Aceste limite conduc la filtrarea elementelor mulţimii C 1 , C 2 , … C h, astfel încât rezultă
submulţimea adreselor:
de regulă p < e.
Se construieşte şirul de adrese:
Poziţionarea citirii este de fapt atribuirea sau modificarea conţinutului unei variabile
de tip pointer extern, aşa fel încât ea să conţină adresa baitului de unde începe citirea/scrierea.
93
În enunţul problemei, M 1 , M 2 , M 3 şi M 4 reprezintă fişiere, adică variabile pointer care
sunt iniţializate cu adresele baiţilor de unde începe alocarea memoriei externe pentru fiecare
din cele patru fişiere. Dacă se are în vedere că fişierul are şi o etichetă de început de lungime,
cont(M )
i + reprezintă adresa primului articol din fişierul M i.
Presupunând că fişierului M i i se alocă o zonă contiguă, cuprinsă între adresele [A ,i
i , rezultă că:
B ]
cont (M )
i = Ai (4.54)
cont ( )
i = Bi – lg (SD )
i (4.55)
unde i reprezintă adresa ultimului element de structură SDi al fişierului M i care are o etichetă
de sfârşit de fişier de lungime .
Revenind la fişierele interdependente, fişierele înlănţuite, se observă că structurile de
date ce se asociază fişierelor, pe lângă informaţiile utile date de programator, se definesc încă
multe câmpuri de tip pointer extern care asigură traversările prin structura externă pe timpul
execuţiei.
Folosind definirea pointerilor extern, locul de muncă w este identificat prin pointerul
extern pw ce corespunde adresei articolului respectivului loc de muncă.
Mulţimea m1 , m2 , … me, reprezintă valori ale variabilei pointer extern r , asociat
fişierului M 2:
unde nrr( ) este o funcţie care defineşte numărul de articole existent într -un fişier:
unde F este mulţimea fişierelor, date prin valoarea iniţială a pointerilor lor externi:
94
La alegerea tipului de fişier cu care se lucrează, concură o serie de factori din care se
enumără:
- numărul de elemente care alcătuiesc fişierul;
- tipul prelucrărilor: integrale, după o cheie, prin selecţie;
- frecvenţa şi tipul operaţiilor de actualizare;
- durata impusă de prelucrare şi necesitatea sortării datelor;
- timpul disponibil pentru elaborarea programelor;
- costuri de elaborare şi utilizare;
- sistemul de calcul disponibil;
- sistemele de gestiune a fişierelor cunoscute şi disponibile.
Alegerea tipului de fişier şi comportamentul algoritmilor implementaţi pentru
regăsirea informaţiilor, sunt rezultatul unei analize statistice a datelor, înregistrate prin
urmărirea în timp a comportamentului la prelucrare.
Teme de control
Bibliografie
Ion IVAN, Marius POPA, Paul POCATILU - Structuri de date, Vol. 1 si 2, Editura ASE,
Bucuresti, 2008.
95
5. Listele – structuri dinamice necontigue
După studierea acestei unităţi de învăţare, studenţii îşi vor însuşi cunoştinţe
teoretice şi vor deprinde abilităţi practice de lucru cu liste simplu şi dublu
înlănţuite. Studenţii vor învăţa lucrul cu pointeri şi operaţiile pe liste
simple şi duble, liniare şi circulare.
O listă liniară (numită şi listă înlănţuită -”Linked List”) este o colecţie de n>=0
elemente x[1], … x[n] toate de un tip oarecare, numite noduri între care există o relaţie de
ordine determinată de poziţia lor relativă. Ea este deci o mulţime eşalonată de elemente de
acelaşi tip având un număr arbitrar de elemente. Numărul n al nodurilor se numeşte lungimea
listei. Dacă n=0, lista este vidă. Dacă n>=1, x[1] este primul nod iar x[n] este ultimul nod.
Pentru 1<k<n, x[k] este precedat de x[k- 1] şi urmat de x[k+1].
Acest tip de structură de date se aseamănă cu o structură standard: tipul tablou cu o
singură dimensiune (vector), ambele structuri conţinând elemente de acelaşi tip iar
între elemente se poate stabili o relaţie de ordine. Una dintre deosebiri constă în
numărul variabil de elemente care constituie lista liniară, dimensiunea acesteia nu trebuie
declarată şi deci cunoscută anticipat (în timpul compilării) ci se poate modifica dinamic în
timpul execuţiei programului, în funcţie de necesităţi. Astfel utilizatorul nu trebuie să fie
preocupat de posibilitatea depăşirii unei dimensiuni estimate iniţial, singura limită fiind
mărimea zonei heap din care se solicită memorie pentru noile elemente ale listei liniare. Un
vector ocupă în memorie un spaţiu continuu de memorie, pe când elementele unei liste simplu
înlănţuite se pot găsi la adrese nu neapărat consecutive de memorie.
O altă deosebire avantajează vectorii, deoarece referirea unui element se face prin
specificarea numărului de ordine al respectivului element, pe când accesul la elementele unei
liste liniare se face secvenţial, pornind de la capul listei (adresa primului nod al listei) până la
ultimul element al ei, ceea ce măreşte uneori considerabil timpul de acces la un anumit
element. Pentru o listă liniară este obligatoriu să existe o variabilă, declarată în timpul
compilării, denumită cap de listă care să păstreze adresa primului element al li stei. Pierderea
acestei valori va duce la imposibilitatea accesării elementelor listei liniare.
Pentru implementarea dinamică a unei liste liniare, folosind pointeri, nodurile listei
vor fi structuri ce conţin două tipuri de informaţie:
- câmpurile ce conţi n informaţia structurală a nodului
- câmpurile ce conţin informaţia de legătură, ce vor conţine pointeri la nodurile listei.
Înlănţuirea secvenţială a elementelor unei liste se face utilizând variabile de tip
pointer, care specifică adresa de memorie a eleme ntelor adiacente. Fiecare nod are un
predecesor şi un succesor, mai puţin elementele prim şi ultim dacă lista nu este
circulară.
Listele înlănţuite cu un singur câmp de legătură se numesc liste simplu înlănţuite
(legătura indică următorul element din listă). Fiecare nod conţine un pointer ce conţine adresa
nodului următor din listă.
96
INFO
URM NULL
Ultimul element poate conţine ca adresă de legătură fie constanta NULL fie constanta
0 (indicând astfel că ultimul nod nu are nici un succe sor).
Tipul unui nod într-o listă simplu înlănţuită se poate defini folosind o declaraţie de
forma:
class Lista;
class ElementLista
{ TINFO info;
ElementLista *urm;
public:
ElementLista(int val=0);
friend class Lista;
};
NULL PREC
INFO
URM NULL
Nodurile unei liste dublu înlănţuite au tipul definit după cum urmează, point erul urm
definind relaţia de succesor pentru nodurile listei, iar prec pe cea de predecesor:
class Lista;
class ElementLista
{ ElementLista *prec;
TINFO info;
ElementLista *urm;
public:
ElementLista(int val=0);
friend class Lista;
};
97
Relativ la o poziţie K din cadrul unei liste, putem defini următoarele tipuri de operaţii:
- accesul la cel de-al K -lea nod al listei pentru examinare sau modificare;
- ştergerea nodului aflat pe poziţia K din cadrul listei;
- inserarea unui nou nod înainte de, respectiv după nodul K .
Limitând efectuarea acestor trei operaţii la primul sau la ultimul nod al unei liste
liniare simplu înlănţuite se obţin structurile de date derivate de tip stivă şi coadă. În
ceea ce priveşte stiva, aceasta constituie o listă simplu înlănţuită în care toate inserările
şi suprimările de noduri se efectuează la un singur capăt al acesteia, numit vârful stivei.
Stivele se mai numesc structuri listă de tip LIFO, Last-In-First-Out , adică ultimul-introdus-
primul-suprimat sau liste de tip pushdown.
Relativ la structura de date de tip coadă, trebuie specificat că elementele noi vor fi
inserate la un capăt al acesteia, numit spate, iar suprimările de noduri vor avea loc la celălalt
capăt, numit faţă. Din acest motiv, cozile se mai numesc şi liste de tip FIFO, first-in-first-aut ,
adică liste de tip primul-venit-primul-servit .
Prezenţa mai multor înlănţuiri într -un acelaşi nod conferă mai multă flexibilitate
structurilor de date de tip listă, acestea devenind aşa-numitele multiliste, nodurile acestora
aparţinând în acelaşi timp la mai multe liste înlănţuite simple.
Într-o astfel de listă există întotdeauna un nod care nu mai are succesor, precum şi un
nod care nu este succesorul nici unui alt nod, aceste noduri constituind capetele listei simplu
înlănţuite. Într -o primă variantă de gestionare a acestui tip de listă v om apela la doi pointeri
prim şi ultim pentru a referiri nodurile terminale ale listei.
INFO
URM NULL
prim ultim
Dacă specificarea începutului listei prin pointerul prim este obligatorie, utilizarea
pointerului de sfârşit ultim este opţională, însă eficientă atunci când se doreşte crearea
listei în ordine naturală printr -o inserţie a noilor noduri la sfârşitul acesteia. În practică,
parcurgerea întregii liste pentru a determina ultima poziţie a acesteia este o soluţie ineficientă,
convenabilă fiind cunoaşterea, cu ajutorul pointerului ultim, a nodului terminal al acesteia. În
prezenţa lui ultim, secvenţa de program care inserează un nod la sfârşitul unei liste liniare şi
concomitent îl actualizează pe ultim este următoarea:
98
Ultim=ptr;
if (Prim==NULL) Prim=Ultim;
cout<<"Inserare la sfarsitul listei cu succes!\n";
}
Dacă elementul adăugat la începutul listei este primul element al listei (caz în care
pointerul Prim conţine valoarea NULL), vom actualiza şi valoarea pointerului Ultim,
elementul adăugat fiind în acelaşi timp ultimul şi primul element al listei.
Operaţia de inserare a unui nod într -o listă nu se reduce numai la cele două situaţii
prezentate, ci implică de asemenea posibilitatea inserării acestuia într -o poziţie oarecare a
listei, şi anume, după sau înaintea unui anumit nod specificat prin referinţa sa spec. Schematic
cele două situaţii sunt prezentate în figurile următoare, observându-se o oarecare dificultate la
operaţia de inserţie a noului nod înaintea celui specificat.
spec
99
void Lista::inserare_dupa_un_element(TINFO X, TINFO cheie)
{
ElementLista* q, *spec;
for(spec=Prim;spec&&spec->info!=cheie;spec=spec->urm);
if(spec)
{
q=new ElementLista(X);
if (q==NULL)
{
cout<<"Eroare alocare spatiu la inserare";
return;
}
if(spec==Ultim) Ultim=q;
q->urm=spec->urm;
spec->urm=q;
cout<<"Inserare dupa element cu succes!\n";
}
else cout<<"Nu exista cheia specificata!\n";
}
spec X
INFO
Inserarea noului nod înaintea celui referit prin spec se implementează ca o inserare
după acesta, noul nod preluând conţinutul câmpului info al acestuia, în care apoi se va
memora informaţia X .
100
if (q==NULL)
{
cout<<"Eroare alocare spatiu la inserare";
return;
}
if(spec==Ultim) Ultim=q;
q->info=spec->info;
q->urm=spec->urm;
spec->urm=q;
spec->info=X;
cout<<"Inserare inaintea unui element cu succes!\n";
}
else cout<<"Nu exista cheia specificata!\n";
}
Se pune problema creării unei liste înlănţuite ordonate după câmpul info, caz în care
inserarea unui nod nou nu trebuie să afecteze relaţia de ordonare existentă. Vom traversa lista
utilizând doi pointeri consecutivi şi vom identifică astfel poziţia de inserare a noului nod.
Inserarea noului nod se va face dupa nodul referit de pointerul q1.
Cei doi pointeri q1 şi q2 avansează simultan de -a lungul listei până când valoarea q1 -
info devine mai mare sau egală cu valoarea de inserat X . Inserarea noului nod va avea loc între
nodurile referite de q1 şi q2.
void Lista::inserare_ordonata(TINFO X)
{
ElementLista *q1=NULL,*q2;
ElementLista* ptr=new ElementLista(X);
if (ptr==NULL)
{
cout<<"Eroare la alocare spatiu pentru inserare";
return;
}
for(q2=Prim;q2&&q2->info<X;q1=q2,q2=q2->urm);
if(q2==Prim)
{
if(Prim==NULL) Ultim=ptr;
ptr->urm=Prim;
Prim=ptr;
}
else
{
if(q2==NULL) Ultim=ptr;
q1->urm=ptr;
ptr->urm=q2;
}
cout<<"Inserare ordonata cu succes!\n";
}
101
Identificarea unui nod în cadrul unei liste se poate efectua aplicând metoda de căutare
liniară, aceasta presupunând parcurgerea elementelor listei, nod cu nod, fie până se
localizează nodul dorit, fie până la sfârşitul listei dacă elementul căutat este in existent.
ElementLista *Lista::cautare(TINFO X)
{
ElementLista *q;
for(q=Prim;q&&q->info!=X;q=q->urm);
if(q)
return(q);
else
return(0);
}
#include<iostream.h>
class Lista;
class ElementLista
{ int info,contor;
ElementLista *urm;
public:
ElementLista(int val=0);
friend class Lista;
};
class Lista
{
protected:
ElementLista *Ultim, *Prim;
public:
Lista()
{ Prim = NULL;
Ultim = new ElementLista();
}
~Lista();
void traversare();
void inserare(int);
};
ElementLista::ElementLista(int val)
{
102
info=val;
contor=1;
urm=NULL;
}
Lista::~Lista()
{
ElementLista* ptr=Prim;
while (Prim)
{
Prim=Prim->urm;
delete ptr;
ptr=Prim;
}
}
void Lista::traversare()
{
ElementLista* p=Prim;
if (p==NULL)
cout << "\n Lista este vida! \n";
else
while (p!=NULL)
{
cout<<p->info<<" numar de aparitii: "<<p->contor<<"\n";
p=p->urm;
}
cout<<"\n";
}
void Lista::inserare(int X)
{
ElementLista *p;
for(p=Prim;p&&p->info!=X;p=p->urm);
if(p)
p->contor++;
Else
{
p=new ElementLista(X);
p->urm=Prim;
Prim=p;
}
}
void main()
{
Lista Listamea;
int cheie;
cout<<"Introduceti cheia de inserat:";
cin>>cheie;
103
while(cheie)
{ Listamea.inserare(cheie);
cout<<"Introduceti cheia de inserat:";
cin>>cheie;
}
cout<<"Inserare incheiata!\n";
cout<<"\nTraversare lista:\n";
Listamea.traversare();
}
O îmbunătăţire substanţială a procesului de căutare poate avea loc prin aplicarea aşa -
numitei metode de căutare cu reordonare. Ori de câte ori un cuvânt se caută şi se localizează
în listă, el va fi mutat la începutul acesteia, astfel încât la proxima apariţie să fie găsit imediat.
Cu alte cuvinte, lista se reordonează după fiecare căutare finalizată cu succes. Dacă un nod nu
este găsit în listă, atunci e l va fi inserat la începutul acesteia.
q2 q1 prim
3
INFO X
URM NULL
2 1 fanion
prim
void Lista::inserare(int X)
{
ElementLista *q1, *q2=NULL, *p;
for(q1=Prim;q1&&q1->info!=X;q2=q1,q1=q1->urm);
if(q1)
{
q1->contor++;
if(q2)
{
q2->urm=q1->urm;
q1->urm=Prim;
Prim=q1;
}
}
else
{
p=new ElementLista(X);
p->urm=Prim;
Prim=p;
}
}
104
Se propune eliminarea unui anumit nod, referit prin q1, din cadrul unei liste liniare
înlănţuite. Pentru aceasta vom folosi un pointer auxiliar, q2, care indică predecesorul
elementului ce urmează să fie suprimat .
Listele circulare sunt liste înlănţuite ale căror înlănţuiri se închid, în aceste condiţii
dispărând noţiunea de început şi de sfârşit al listei. Pentru gestiunea unei astfel de lista, vom
păstra un pointer spre „ultimul” element al listei.
# include <iostream.h>
# define TINFO int
class Lista;
class ElementLista
{ TINFO info;
ElementLista *urm;
public:
ElementLista(int val=0);
friend class Lista;
};
class Lista
{
protected:
ElementLista *Ultim;
public:
Lista()
{ Ultim = NULL;}
~Lista();
void traversare();
void inserare_sfarsit(TINFO);
void inserare_inaintea_unui_elem(TINFO,TINFO);
105
ElementLista *cautare(TINFO);
void suprimare(TINFO);
};
ElementLista::ElementLista(TINFO val)
{
info=val;
urm=NULL;
}
Lista::~Lista()
{
ElementLista* ptr;
if(Ultim!=NULL)
{ while (Ultim->urm!=Ultim)
{
ptr=Ultim->urm;
Ultim->urm=Ultim->urm->urm;
delete ptr;
}
delete Ultim;
}
}
void Lista::traversare()
{
ElementLista* ptr=Ultim;
if (Ultim==NULL)
cout << "\n Lista este vida! \n";
else
{ while (ptr->urm!=Ultim)
{
ptr=ptr->urm;
cout << ptr->info<<" ";
}
cout << ptr->urm->info<<" ";
cout<<"\n";
}
}
106
ptr->urm=Ultim->urm;
Ultim->urm=ptr;
Ultim=ptr;
}
else
{
Ultim=ptr;
ptr->urm=ptr;
}
cout<<"Inserare sfarsit cu succes!\n";
}
ElementLista *Lista::cautare(TINFO X)
{
ElementLista *ptr;
if(Ultim==NULL) return(0);
for(ptr=Ultim->urm;ptr!=Ultim&&ptr->info!=X;ptr=ptr-
>urm);
if(ptr->info==X)
return(ptr);
else
return(0);
}
void Lista::suprimare(TINFO X)
107
{
ElementLista *q1=Ultim, *q2;
if(Ultim==NULL)
{
cout<<"Lista vida! Suprimare fara succes!\n";
return;
}
for(q2=Ultim->urm;q2!=Ultim&&q2->info!=X;q1=q2,q2=q2-
>urm);
if(q2->info==X)
{
if(q2->urm==q2)
{
Ultim=NULL;
delete(q2);
}
else
{
if(q2==Ultim) Ultim=q1;
q1->urm=q2->urm;
delete(q2);
}
cout<<"Suprimare cu succes!\n";
}
else
cout<<"Nodul nu exista!\n";
}
void main()
{
Lista Listamea;
int opt;
TINFO valoare,cheie;
do
{
cout<<"\n Optiuni de lucru cu lista:";
cout<<"\n 1 - Afisare lista";
cout<<"\n 2 - Inserare element la sfarsitul listei";
cout<<"\n 3 - Inserare element inaintea unui elem specificat";
cout<<"\n 4 - Cautarea unui element specificat";
cout<<"\n 5 - Suprimarea unui element";
cout<<"\n 9 - Terminare lucru \n\n";
cout<<"Introduceti optiunea dorita:";
cin>>opt;
switch(opt)
{
case 1:
{
cout<<"Traversare lista:";
Listamea.traversare();
108
break;
}
case 2:
{
cout<<"Introduceti elementul de inserat:";
cin>>valoare;
Listamea.inserare_sfarsit(valoare);
break;
}
case 3:
{
cout<<"Introduceti elementul de inserat:";
cin>>valoare;
cout<<"Introduceti elem inaintea caruia
inseram:";
cin>>cheie;
Listamea.inserare_inaintea_unui_elem(valoare,cheie);
break;
}
case 4:
{
cout<<"Introduceti elementul cautat:";
cin>>valoare;
if(Listamea.cautare(valoare))
cout<<"Elementul a fost gasit!\n";
else
cout<<"Elementul nu exista in lista!\n";
break;
}
case 5:
{
cout<<"Introd elem pe care doriti sa-l
suprimati:";
cin>>valoare;
Listamea.suprimare(valoare);
break;
}
case 9: break;
default:
cout<<"\n Nu exista optiunea! \n ";
}
}while (opt!=9);
}
109
Pentru tipărirea conţinutului elementelor matricei rare, cu număr necunoscut de
elemente nenule, funcţia de parcurgere, construită recursiv este:
parcurgere (pg)
{
while (pg->poz != NULL)
{
tipareste (pg->i, pg->j, pg->val);
parcurgere (pg->poz);
}
}
Considerând lista ca mulţimea de perechi de forma (Z, ) de tip TL, w = adr( Z 1 )
conţine adresele de regăsire a elementelor listei.
110
if( ref (w1).Z = =Zk )
ref (w). = ref (w1). ;
}
Concatenarea a două liste (Z, ) şi (U, ), reprezintă ca ultimul element al primei liste
să-şi schimbe valoarea din NULL a lui n cu adresa elementului (U 1 , 1 ).
Deci: ref( Z n ). n = adr[ (U 1 , 1 )]
Funcţia care efectuează concatenarea listelor este:
concatenare((Z, ), (U, ))
{
while (ref(Z). != NULL)
{
concatenare ((ref(Z). , ),(U,Z));
}
ref(Z). = adr((U, ))
}
...
...
NULL
Lista concatenată are ca prin element (Z 1 , 1 ), iar ca ultim element (U m , m ).
Fizic, lista (U, ) nu s-a modificat. Conservând (Z, 1 ) şi (U, 1 ) se lucrează cu cele
două liste, ca şi cum concatenarea nu s -a executat. Totuşi lista concatenată, pentru operaţia de
parcurgere se comportă ca o listă cu m + n componente.
modificare (w)
{
111
if (ref (w). z != a)
modificare (ref (w). );
else
ref (w). z = b;
}
Fie lista (Z j , j ), j = 1, 2, ..., n . Se pune problema obţinerii unei liste: (Z ’ j , ’ j ), j = 1, 2,
’
... , n, astfel încât: cont(Z j ) = cont(Z j ), pentru j = 1, 2, ..., n.
copiere_lista (w, u)
{
while (ref (w). != NULL)
{
ref (u).Z = ref (w).Z;
alocare (v);
ref (u). = v;
copiere_lista (ref (w). ), v);
}
ref (u). = NULL;
}
Se spune că o listă este ordonată crescător dacă: cont ( Z j ) cont ( Z j 1 ) , pentru orice
j = 1, 2, …, n – 1.
A insera un element într-o listă, înseamnă mai întâi a găsi o valoare k Є {1, 2, …, n}
astfel încât: cont ( Zk ) a cont ( Zk 1 ) sau cont ( Zk ) a cont ( Zk 1 ) , după cum lista
este ordonată crescător sau descrescător. În aceste condiţii, inserarea elementului a, înseamnă
conform modelului grafic, a trece de la configuraţia:
... ...
( , )
la configuraţia:
112
(Zk , adr( )) (Zk+1, k+1)
... ...
( , adr (Zk+1))
Există cazuri particulare de inserare în care elementul este poziţionat fie la începutul
listei, fie la sfârşitul acesteia, operaţia numindu-se adăugare.
Dacă elementele a şi b vor fi inserate la începutul, respectiv, la sfârşitul listei, se trece
de la configuraţia:
(Z1, 1) (Zn, n)
a a
( , ) ( , )
la configuraţia:
... ...
b b NULL
( ,adr ( Z1) ( , )
113
(Zk-1, k-1) (Zk , k ) (Zk+1, k+1)
... ...
... ...
... ...
(Zk-1, k-1)
(Z j, j)
... ...
ceea ce înseamnă că la un moment dat sunt gestionate şase adrese de variabile de tip TL, ale
elementelor ce se interschimbă, precum şi a elementelor adiacente.
Fiind dată o structură de date de tip listă (Z j , j ), j = 1, 2, ..., n , funcţia de sortare
transformă această structură de date într -o nouă listă (Z’k , ’k ), k = 1, 2, ..., n astfel încât
oricărui k Є [1, n] ∩ N îi corespunde un j Є [1, n] ∩ N şi numai unul aşa încât: cont( Z’ k ) =
cont( Z’ j ) şi cont ( Zk ) cont ( Zk 1 ) pentru orice k = 1, 2, …, n – 1.
Funcţia de sortare apelează la rândul ei funcţia de interschimb a două elemente
adiacente, până când în final se obţine ordinea crescătoare sau descrescătoare a
termenilor Z i din lista iniţială.
Un exemplu simplu de sortare, fără a lua în considerare multitudinea de tehnici este:
114
sortare (w)
{
k = 1;
while (k != 0)
{
k = 0;
while (ref (w). != NULL)
{
if (ref (w).Z > ref (w).ref( ).Z)
{
k = 1;
interschimb (w, w. );
}
}
}
}
... ...
sau:
... ...
Lista dublu înlănţuită este de fapt formată din două liste (Z j , j ) şi (U j , j ) cu
proprietăţile:
adr ( Z j ) adr ( U j ) cont ( Z j ) cont ( U j )
adr ( j ) adr ( j ) lg ( j )
cont ( Z j , j ) adr ( Z j 1)
cont ( Z j , j ) adr ( Z j 1)
cont ( j 1 ) cont ( j 1 )
115
Şi cu listele dublu înlănţuite se efectuează operaţii precum:
- inserarea unui element;
- adăugarea unui element;
- ştergerea unui element;
- inversarea a două elemente;
- ştergerea listei;
- parcurgerea într-un sens şi în sensul opus;
- transformarea listei în listă circulară dublu înlănţuită.
# include <iostream.h>
# define TINFO int
class ListaDubluInlan;
class ElementLista
{ TINFO info;
ElementLista *pred, *suc;
public:
ElementLista(int val=0);
friend class ListaDubluInlan;
};
class ListaDubluInlan
{
protected:
ElementLista *Prim;
public:
ListaDubluInlan()
{ Prim = NULL;}
~ListaDubluInlan();
void traversare_inainte();
void inserare_inceput(TINFO);
void inserare_inaintea_unui_elem(TINFO,TINFO);
void inserare_dupa_elem(TINFO,TINFO);
ElementLista *cautare(TINFO);
void suprimare(TINFO);
};
ElementLista::ElementLista(TINFO val)
{
info=val;
pred=suc=NULL;
}
ListaDubluInlan::~ListaDubluInlan()
{
ElementLista* ptr;
116
while (Prim)
{
ptr=Prim;
Prim=Prim->suc;
delete ptr;
}
}
void ListaDubluInlan::traversare_inainte()
{
ElementLista* ptr=Prim;
if (Prim==NULL)
cout << "\n Lista este vida!";
else
while (ptr)
{
cout << ptr->info<<" ";
ptr=ptr->suc;
}
cout<<"\n";
}
117
ptr=new ElementLista(X);
if (ptr==NULL)
{
cout<<"Eroare alocare spatiu la inserare";
return;
}
if(p==Prim)
{
ptr->pred=NULL;
ptr->suc=p;
p->pred=ptr;
Prim=ptr;
}
else
{
ptr->pred=p->pred;
ptr->suc=p;
p->pred->suc=ptr;
p->pred=ptr;
}
cout<<"Inserare inainte element cu succes!\n";
}
else cout<<"Nu exista cheia specificata!\n";
}
118
}
ElementLista *ListaDubluInlan::cautare(TINFO X)
{
ElementLista *ptr;
for(ptr=Prim;ptr&&ptr->info!=X;ptr=ptr->suc);
if(ptr)
return(ptr);
else
return(0);
}
void ListaDubluInlan::suprimare(TINFO X)
{
ElementLista *ptr;
if(Prim==NULL)
{
cout<<"Lista vida! Suprimare fara succes!\n";
return;
}
for(ptr=Prim;ptr&&ptr->info!=X;ptr=ptr->suc);
if(ptr)
{
if(ptr==Prim)
{
Prim=Prim->suc;
if (Prim) Prim->pred=NULL;
}
else
{
ptr->pred->suc=ptr->suc;
if(ptr->suc)
ptr->suc->pred=ptr->pred;
}
delete(ptr);
cout<<"Suprimare cu succes!\n";
}
else
cout<<"Nodul nu exista!\n";
}
void main()
{
ListaDubluInlan Listamea;
int opt;
TINFO valoare,cheie;
do
{
cout<<"\n Optiuni de lucru cu lista:";
cout<<"\n 1 - Afisare lista";
119
cout<<"\n 2 - Inserare element la inceputul listei";
cout<<"\n 3 - Inserare element inaintea unui elem specificat";
cout<<"\n 4 - Inserare dupa un element specificat";
cout<<"\n 5 - Cautarea unui element specificat";
cout<<"\n 6 - Suprimarea unui element";
cout<<"\n 9 - Terminare lucru \n\n";
cout<<"Introduceti optiunea dorita:";
cin>>opt;
switch(opt)
{
case 1:
}
cout<<"Traversare lista:";
Listamea.traversare_inainte();
break;
}
case 2:
{
cout<<"Introduceti elementul de inserat:";
cin>>valoare;
Listamea.inserare_inceput(valoare);
break;
}
case 3:
{
cout<<"Introduceti elementul de inserat:";
cin>>valoare;
cout<<"Introduceti elem inaintea caruia inseram:";
cin>>cheie;
Listamea.inserare_inaintea_unui_elem(valoare,cheie);
break;
}
case 4:
{
cout<<"Introduceti elementul de inserat:";
cin>>valoare;
cout<<"Introduceti elementul dupa care inseram:";
cin>>cheie;
Listamea.inserare_dupa_elem(valoare,cheie);
break;
}
case 5:
{
cout<<"Introduceti elementul cautat:";
cin>>valoare;
if(Listamea.cautare(valoare))
cout<<"Elementul a fost gasit!\n";
else
cout<<"Elementul nu exista in lista!\n";
break;
120
}
case 6:
{
cout<<"Introd elem pe care doriti sa-l suprimati:";
cin>>valoare;
Listamea.suprimare(valoare);
break;
}
case 9: break;
default:
cout<<"\n Nu exista optiunea! \n ";
}
}while (opt!=9);
}
Teme de control
Bibliografie
Ion IVAN, Marius POPA, Paul POCATILU - Structuri de date, Vol. 1 si 2, Editura ASE,
Bucuresti, 2008.
121
6. Stive şi cozi
Obiectivele unităţii de învăţare
push pop
122
O coadă (în engleză queue) este o structură de tip FIFO (First In First Out = Primul
venit primul servit), în care toate inserările se fac la un capăt al ei (numit capul co zii)
iar ştergerile (extragerile) (în general orice acces) se fac la celălalt capăt (numit sfârşitul
cozii). În cazul cozii, avem nevoie de doi pointeri, unul către primul element al cozii (capul
cozii), iar altul către ultimul său element (sfârşitul cozii). Există şi o variantă de coadă
circulară, în care elementele sunt legate în cerc, iar cei doi pointeri, indicând capul şi sfârşitul
cozii, sunt undeva pe acest cerc.
push pop
Caracteristicile unei stive sunt puse în evidenţă prin pri n comparaţie cu structura de date de
tip listă:
- în timp ce pentru gestionarea listei se vehiculează cu variabile de tip T p, care stochează
adresa componentei (Z 1 , 1 ) a listei, componenta numită cap de listă, în cazul stivei
este memorată adresa ultimei componente (Z' n , θ' n ), numită vârful stivei;
- în timp ce cont cont(θ n ) = NULL în cazul listei, cont (θ' 1 ) = NULL în cazul stivei;
- în timp ce succ (Z j ) = Z j+1 în cazul listei, în cazul stivei succ(Z’ j )=Z’ j-1;
- în timp ce pred (Z j ) = Z j-1 în cazul listei, în cazul stivei pred(Z’ j )=Z’ j+1;
123
- în tip ce parcurgerea în cazul listei este de la (Z 1, θ1) spre (Zn, θm), în cazul stivei,
parcurgerea este de la (Z'n, θ'n) spre ( Z'1, θ'1 );
- în timp ce disciplina lucrului cu elementele listei este primul venit – primul servit
(FIFO), în cazul stivei regula de vehiculare a elementelor, este ultimul venit – primul
primul
servit (LIFO).
Se observă că stiva este o listă inversă. Totuşi, multe dintre tehnicile de rezolvare a
problemelor vehiculează
vehiculează stive şi nu liste, datorită disciplinei de gestionare a elementelor.
Regulile de creare şi accesare la nivel de stivă, prevăd că fiecărui element care se
adaugă, i se alocă o zonă de memorie şi acest element devine vârf al stivei.
Există procese în care informaţiile se stochează în ordinea opusă momentului
prelucrării, iar odată folosită, aceasta devine necesară. Stocând informaţiile
informaţiil e într -o stivă, după
utilizarea informaţiilor conţinute în vârful stivei, zona de memorie alocată este eliberată
(dealocată), vârful stivei mutându-se cu o componentă spre baza stivei.
Baza stivei este considerat elementul (Z' 1, '1). În funcţie de cerinţele de prelucrare, se
obţine o fluctuaţie a poziţiei vârfului stivei, ceea ce creează o imagine mai bună pentru
dinamica prelucrării datelor. Astfel, stivele stau la baza gestionării adreselor parametrilor ce
se transmit în funcţii, implementării mecanismelor de recursivitate din limbaje de programare.
adr (C3)
(Z'3, '3)
124
După parcurgerea pasului D, vârfului stivei coboară la (Z' 3, θ'3) şi componenta (Z'4,
θ'4) este dealocată, deci distrusă.
Folosind informaţiile stocate în pasul C, se efectuează ştergerea fişierelor (pasul E).
Vârful stivei se coboară la componenta (Z'2, θ'2), iar componenta (Z'3, θ'3) este eliberată.
e
vârf de stivă
a
baza activă
NULL
Fig. 6.4. Structura unei stive pentru inversarea caracterelor unui şir
Se execută pasul F, care este simetricul pasului C şi apoi vârful coboară spre (Z' 1, θ'1)
şi se activează componentele C 1 pentru eliberarea resurselor pas simetric pasului A.
Astfel, de exemplu, dacă dorim să inversăm poziţia elementelor unui şir {a, b, c, d, e},
prin crearea unei stive care să conţină aceste elemente, cu modelul grafic din figura 11.4 şi
prin apelarea unei funcţii de parcurgere cu imprimarea elementelor identificate, se obţine
exact şirul { e, d, c, b, a }.
În cazul calculării lui n!, se foloseşte formula de recurenţă: P(n) = P(n-1) * n, cu P(1)
= 1 , se creează o stivă care conţine expresiile ce sunt evaluate cu valorile descrescătoare ale
lui n.
Se construieşte stiva care are în informaţia utilă, de la bază spre vârf: n, n – 1, n – 2,
…, 3, 2, 1 .
Valoarea iniţială a funcţiei recursive P(1) = 1, permite prin parcurgerea stivei de la
vârf spre bază, obţinerea rând pe rând a rezultatelor: P(2), P(3),…,P(n – 2), P(n – 1 ), P(n) şi
întâlnindu-se NULL la baza stivei, procesul se încheie.
125
Programul următor, exemplifică unele dintre operaţiile care se execută cu structuri de
date de tip stivă.
#include <iostream.h>
#include <malloc.h>
class elementstiva
{
public:
int info;
elementstiva *prec;
elementstiva()
{
prec=NULL;
}
};
class stiva
{
public:
elementstiva *vs; //varful stivei
stiva()
{
vs=NULL;
}
void sterg()
{
elementstiva* aux=vs;
if(vs == NULL) cout<<"\n Stiva este vida!";
else
{
aux = vs->prec;
free(vs);
while(aux !=NULL)
{
vs=aux;
aux=aux->prec;
delete vs;
}
vs = NULL;
}
}//
void tipar()
{
elementstiva* aux;
if(vs == NULL) cout<<"\n Stiva este vida!";
126
else
{
aux = vs;
while(aux !=NULL)
{
cout<<aux->info;
aux=aux->prec;
}
}
}//
int extrag()
{
elementstiva* aux;
int el;
if (vs == NULL) return -1;
else
{
aux = vs->prec;
el = vs->info;
delete vs;
vs = aux;
return el;
}
};
void main()
{
stiva st;
char opt;
int x,el;
do
{
cout<<"\n Optiuni de lucru cu stiva ";
cout<<"\n P - Tiparire stiva";
127
cout<<"\n A - Adaugare element în stiva";
cout<<"\n E - Extragere element din stiva";
cout<<"\n T - Terminare lucru";
cin>>opt;
switch(opt)
{
case 'a':
cout<<"element:";cin>>el;
st.inserare(el);
break;
case 'e':
{
x = st.extrag();
if (x==-1)
cout<<"\n Stiva este vida!";
else
cout<<"\n Element extras"<<x;
}
break;
case 's':
st.sterg();
break;
case 'p':
st.tipar();
break;
default:
cout<<"\n Nu exista optiunea!";
}
}while (opt!='t');
}
Pe baza meniului afişat prin program, se activează funcţiile în orice ordine, iar situaţia
de stivă vidă, este marcată în mod corespunzător. Crearea stivei se realizează prin adăugări
succesive, care presupun alocări dinamice de memorie pentru elementele componente.
Procedura de ştergere eliberează toată zona de memorie alocată stivei, iar funcţia de extragere,
eliberează zona de memorie ocupată de vârful stivei şi iniţializează cu adresa noului vârf al
stivei, variabila VS.
Pentru evaluarea expresiilor matematice există diferiţi algoritmi. Unul dintre aceştia
foloseşte ca structură principală de date stiva.
Acest algoritm presupune rearanjarea expresiei într- o anumită formă astfel încât
ordinea operaţiilor să fie clară şi evaluarea să necesite o singură parcurgere a expresiei. Pentru
aceasta se poate folosi forma poloneză, inventată de matematicianul de origine poloneză Jan
Lukasiewicz. Acesta presupune scrierea operatorilor înaintea operanzilor. Această formă mai
are o variantă numită scrierea poloneză inversă în care operatorii sunt scrişi în urma
operanzilor.
128
Tabel 6.1. Forme ale scrierii unei expresii matematice
Expresia în forma Expresia în forma
Expresia matematică
poloneză (scriere poloneză inversă
(scriere infixată)
prefixată) (scriere postfixată)
4+5 +45 45+
4+5*5 +4*55 455*+
4*2+3 +*423 42*3+
După cum se vede din tabelul 6.1, ordinea operanzilor nu se schimbă, ei găsindu -se în
aceeaşi ordine ca în expresia matematică.
Forma poloneză inversă are avantaje faţă de scrierea prefixată sau infixată:
- ordinea în care se efectuează operaţiile este clară;
- parantezele nu mai sunt necesare;
- evaluările sunt uşor de efectuat cu ajutorul calculatorului.
Un algoritm de transformare din expresie matematică în scriere postfixată a fost
dezvoltat de către Edsger Dijkstra (algoritmul macazului al lui Dijkstra – Dijkstra
Shunting Algorithm). Acest algoritm utilizează o stivă în care sunt păstraţi operatorii şi din
care sunt eliminaţi şi transferaţi în scrierea postfixată. Fiecare operator are atribuită o ierarhie
după cum este prezentat în tabelul 6.2.
Algoritmul este:
se iniţializează stiva şi scrierea postfixată;
atât timp cât nu s- a ajuns la sfârşitul expresiei matematice:
- se citeşte următorul element din expresie;
- dacă este valoare se adaugă în scrierea postfixată;
- dacă este „(” se introduce în stivă;
- dacă este „)” se transferă elemente din stivă în scrierea postfixată până la „(”;
- altfel:
atât timp cât ierarhia operatorului din vârful stivei este mai mare
ierarhia operatorului curent, se trece elementul din vârful stivei în
scrierea postfixată;
se introduce operatorul c urent în stivă.
se trec toţi operatorii rămaşi pe stivă în scrierea postfixată.
Având expresia sub această formă, se face evaluarea ei. Algoritmul de evaluare
foloseşte tot o stivă pe care sunt păstraţi de această dată operanzii. Algoritmul este:
se iniţializează stiva;
129
atât timp cât nu s-a ajuns la sfârşitul scrierii postfixate:
- se citeşte următorul element;
- dacă este valoare se depune pe stivă;
- altfel (este operator):
se extrage din stivă elementul y;
se extrage din stivă elementul x;
se efectuează operaţia x operator y;
se depune rezultatul pe stivă;
ultima valoare care se află pe stivă este rezultatul expresiei.
De exemplu, pentru expresia în scriere postfixată: 4 8 2 3 * - 2 / +, algoritmul se
execută ca în figura 6.5:
Elementul citit Stiva
4 4
8 8
4
2 2
8
4
3 3
2
8
4
* 6
8
4
- 2
4
2 2
2
4
/ 1
4
+ 5
130
Mai există o clasă fisier care are rolul de a parcurge fişierul în care se află expresie
aritmetică ce trebuie evaluată.
Teme de control
Bibliografie
Ion IVAN, Marius POPA, Paul POCATILU - Structuri de date, Vol. 1 si 2, Editura ASE,
Bucuresti, 2008.
131
7. Arbori binari şi arbori de căutare
Obiectivele unităţii de învăţare
[ a, b ]
a+b a+b
[a , ] ( ,b]
2 2
3a + b 3a + b a+b a+b a + 3b a + 3b
[a , ] ( , ] ( , ] ( ,b]
4 4 2 2 4 4
Fig. 7.1. Reprezentarea grafică asociată divizării intervalelor
132
*
e
/
c d
+
x y
+
+
c d
a b
bibliotec
biblioteca
a
1xxxxx : 7xxxxx
133
cu variabila Z structura de date asociată unui nod, structură ce corespunde tripletului (Z j, j,
j) asociat nodului j dintr-un graf de formă arborescentă binară.
Volumul informaţiilor destinate localizării nodurilor, depinde de obiectivele urmărite
şi de rapiditatea cu care se doreşte localizarea unui anumit nod. Cu cât diversitatea
prelucrărilor este mai mare, cu atât se impune stocarea mai multor informaţii de
localizare.
Z j-1 j-1
j-1
j adr (Z j 1 )
Dacă tripletul (Z j, j, j) ce corespunde modelului grafic din figura 7.4 permite
baleierea de sus în jos, sau de jos în sus, pe o ramură a arborelui. Pentru a parcurge pe
orizontală arborele, este necesară atât o informaţie suplimentară de localizare j , care
corespunde nodului vecin din dreapta de pe ace laşi nivel, cât şi informaţia j pentru
localizarea nodului din stânga de pe acelaşi nivel.
Un astfel de arbore se descrie prin: ( Z j , j, j, j, j )
Descrierea completă a unui arbore, presupune crearea mulţimii tripletelor sau
cvintuplelor asociate nodurilor.
De exemplu, arborescenţei asociate evaluării expresiei: e = a + b + c prezentate în
figura 7.5 îi corespunde alocarea şi iniţializarea zonelor de memorie din figura 7.6.
=
+
e
c
+
a b
Fig. 7.5. Structura arborescentă asociată evaluării expresiei e
134
= null
+ e null
c null +
a null b null
135
figura 12.7 construirea se efectuează prin alocarea a 7 zone de memorie cu structura
( Z j , j, j ) pentru nodul din mulţimea N { A, B, C, D, E, F, G } şi se iniţializează
vectorul de pointeri pp[i] după cum urmează:
pp [1] adr (ZA)
pp [2] adr (ZB)
pp [3] adr (ZC)
pp [4] adr (ZD)
pp [5] adr (ZE)
pp [6] adr (ZF)
pp [7] adr (ZG)
Baleierea mulţimii:
A { (AB), (AC), (BD), (BE), (CF), (CG) } revine la efectuarea atribuirilor:
ref (pp[1] ). pp[2]
ref (pp[2] ). pp[4]
ref (pp[3] ). pp[6]
ref (pp[4] ). ref (pp[5] ). ref (pp[6] ). ref (pp[7] ). NULL (12.11)
ref (pp[2] ). pp[1]
ref (pp[4] ). pp[2]
ref (pp[3] ). pp[1]
ref (pp[6] ). pp[3]
ref (pp[7] ). pp[3]
ref (pp[1] ). ref (pp[1] ). NULL
B C
D
E F G
Fig. 7.7. Arbore binar
struct arbore
{
int valoare;
arbore *precedent;
arbore *urmator;
136
};
class arb
{
arbore *rad;
};
struct arbore_oarecare
{
int valoare;
arbore_oarecare **fii; //lista fiilor unui nod
int nrfii; //nr de fii ai nodului
};
class arb_oarecare
{
arbore_oarecare *rad;
};
struct arbore_binar
{
int valoare;
arbore_binar *stanga;
arbore_binar *dreapta;
};
class arb_binar
{
arbore_binar *rad;
};
Aplicaţiile concrete, asociază unor obiecte, subansamble sau procese, structuri de tip
arborescent care nu sunt binare, astfel încât se conturează ideea ca arborii binar sunt cazuri
particulare de arbori, mai ales prin frecvenţa cu care sunt identificaţi în practică. Mecanismele
de realizare şi de utilizare a arborilor binar îi fac practice, deşi în realitate au frecvenţa d e
apariţie scăzută.
Apare problema transformării unei structuri arborescente oarecare într -o structură
arborescentă binară, problema rezolvabilă prin introducerea de noduri fictive. Astfel, fiind
dată arborescenţa din figura 7 .8, transformarea ei în struct ură arborescentă binară, revine la
introducerea nodurilor fictive, x, y, u, v, w, rezultând arborele din figura 7.9.
137
A
B C D
e f g h i j k i
B x
e u C D
f g h y i
v
j w
k l
Arborele oarecare, are un număr de noduri mai mic decât arborele binar, nodurilor
fictive corespunzându-le zone de memorie structurate (NULL, γ j, θj).
Alocarea dinamică presupune, ca în zona [D i, Df ] prin dealocare să apară goluri, adică
zone libere ce sunt realocate altor variabile. Este necesară o gestionare a acestor zone şi
periodic trebuie să se efectueze o realocare prin reorganizare, aşa fel încât să dispară golurile
rezultate în procesul de alocare- dealocare multiplă. De exemplu, pentru un program P, este
necesară alocarea a 3 zone de memorie de 1500, 2000, 4000 baiţi, ce corespund arborilor
binar A, B şi C.
Alocarea este efectuată iniţializând variabilele pointer p A, p B şi pC prin apelul succesiv
al funcţiei alocare( ), (pas 1).
Dealocarea după un timp a arborelui binar B, determină apariţia unui gol între zonele
ocupate de variabilele A şi C, (pas 2).
138
Alocarea unei zone de memorie pentru arborii binari D (3000 baiţi) şi E (1000 baiţi),
revin la a dispune pe D în continuarea lui C şi a intercala arborele E între A şi C, în “golul”
rezultat din dealocarea lui E, rămânând între E şi C un “gol” de 300 baiţi, (pas 3).
Dacă se păstrează informaţiile privind modul de iniţializare a variabilelor pointer care
stochează adresele nodurilor rădăcină a arborilor A, E, C şi D, este posibilă glisar ea
informaţiilor în aşa fel încât să dispară “golul” dintre E şi C. Nu s -a luat în considerare însăşi
necontiguitatea din interiorul fiecărui arbore.
În practică, apare problema optimizării dispunerii variabilelor dinamice, dar şi cea a
alegerii momentului în care dispersia elementelor atinge un astfel de nivel, încât zona pentru
alocare dinamică este practic inutilizabilă şi trebuie reorganizată.
Un arbore binar de căutare este un arbore binar care are proprietatea că prin
parcurgerea în inordine a nodurilor se obţine o secvenţă monoton crescătoare a cheilor
(cheia este un câmp ce serveşte la identificarea nodurilor în cadrul arborelui). Câmpul
cheie este singurul care prezintă interes din punct de vedere al operaţiilor care se pot efectua
asupra arborilor de căutare.
Principala utilizare a arborilor binari de căutare este regăsirea rapidă a unor informaţii
memorate în cheile nodurilor. Pentru oric e nod al unui arbore de căutare, cheia acestuia are o
valoare mai mare decât cheile tuturor nodurilor din subarborele stâng şi mai mică decât cheile
nodurilor ce compun subarborele drept.
20
10 25
7 22 26
Structura de date folosită pentru descrierea unui nod al unui arbore binar de căutare va
fi următoarea:
struct arbore_binar
{
int cheie;
arbore_binar *stanga;
arbore_binar *dreapta;
};
class arb_binar
{
arbore_binar *rad;
};
139
Rădăcina arborelui binar de căutare va fi definită în felul următor: arb *Radacina =
NULL;
Se observă ca fiecare nod este compus din cheia asociată şi din informaţiile de
legătură care se referă eventualii fii.
Aşa cum le spune şi numele, arborii binari de căutare sunt folosiţi pentru regăsirea
rapidă a informaţiilor memorate în cheile nodurilor. De aceea căutarea unui nod cu o anumită
valoarea a cheii este o operaţie deosebit de importantă.
Căutarea începe cu nodul rădăcină al arborelui prin compararea valorii cheii căutate cu
valoarea cheii nodului curent. Dacă cele două valori coincide, căutarea s -a încheiat cu succes.
Dacă informaţia căutată este mai mică decât cheia nodului, căutarea se continuă în
subarborele stâng. Dacă cheia căutată este mai mare decât valoarea cheii nodului, căutarea se
reia pentru subarborele drept.
Crearea unui arbore binar de căutare presupune adăugarea câte unui nod la un arbore
iniţial vid. După inserarea unui nod, arborele trebuie să rămână în continuare ordonat. Din
acest motiv, pentru adăugarea unui nod se parcurge arborele începând cu rădăcina şi
continuând cu subarborele stâng sau drept în funcţie de relaţia de ordine dintre cheia nodului
şi cheia de inserat. Astfel, dacă cheia de inserat este mai mica decât cheia nodului, următorul
nod vizitat va fi rădăcina subarborelui stâng. În mod similar, dacă cheia de inserat este mai
mare decât cheia nodului, traversarea se va continua cu nodul rădăcină al subarborelui drept.
Această modalitate de traversare se continuă până în momentul în care se ajunge la un nod
fără descendent. Acestui nod îi va fi adăugat un nod fiu cu valoarea dorită a cheii.
Aplicaţiile care utilizează arbori binari de căutare pot permite sau pot interzice, în
funcţie de filozofia proprie, inserarea în cadrul arborelui a unei chei care există deja.
Inserarea în cadrul arborelui anterior a unui nou nod cu valoarea cheii egală cu 12 conduce
către următorul arbore, figura 7.11.
20
10 25
7 12 22 26
Maniera uzuală de inserare a unui nod într -un arbor e binar de căutare este cea
recursivă.
În practică, de cele mai multe ori căutarea şi inserarea se folosesc împreună. Astfel, în
cazul în care căutarea unei chei s -a efectuat fără succes, aceasta este adăugată la arborele
binar de căutare.
O altă operaţie care se poate efectua asupra unui arbore binar de căutare este ştergerea
unui nod care are o anumită valoare a cheii. Dacă valoarea cheii este găsită în cadrul
arborelui, nodul corespunzător este şters. Arborele trebuie să rămân arbore de căutare şi după
ştergerea nodului.
În ceea ce priveşte nodul care va fi şters, acesta se va încadra într -una din variantele
următoare:
a) nodul nu are subarbori (fii);
b) nodul are doar subarbore stâng;
c) nodul are doar subarbore drept;
140
d) nodul are atât subarbore stâng cât şi subarbore drept.
În cazul în care nodul nu are nici subarbore stâng dar nici subarbore drept (varianta a)
este necesară doar ştergerea nodului. Nu sunt necesare alte operaţiuni de actualizare a
arborelui.
…
Fig. 7.12. Arbore binar de căutare înainte de şterge rea unui nod
În figura 7.12 este prezentat un fragment al unui arbore binar de căutare din care
dorim să ştergem nodul din drea pta, iar în figura 7 .13 se prezintă acelaşi fragment de arbore
dar după ştergerea nodului dorit.
…
Pentru cazurile b şi c (nodul pe e dorim să -l ştergem are subarbore stâng sau drept), pe
lângă ştergerea nodului este necesară şi actualizarea legăturilor dintre no durile arborelui. În
figurile 7.14 şi 7.16 se prezintă câte un fragment dintr -un arbore binar de căutare din care
dorim să ştergem nodul evidenţiat. În figura 7.14, nodul pe care dorim să -l ştergem are numai
subarbore stâng iar cel din figura 7.16 are doar subarbore drept. În figurile 7.15 şi 7 .17 se pot
observa efectele operaţiei de ştergere.
141
…
Pentr u aceste prime trei cazuri, actualizarea arborelui se face în felul următor: fiul
nodului care va fi şters, dacă există, va deveni fiul tatălui acestuia. Actualizarea arborelui este
urmată de ştergerea nodului din memorie.
Cazul în care nodul ce se doreşte a fi şters are atât subarbore stâng cât şi subarbore
drept necesită o tratare specială. Astfel, mai întâi se localizează fie cel mai din stânga fiu al
subarborelui drept fie cel mai din dreapta fiu al subarborelui drept. Cheile acestor noduri sunt
valoarea imediat următoare cheii nodului ce se doreşte a fi şters şi valoarea precedentă.
După suprimarea nodului dorit, arborele va trebui să rămână în continuare arbore de
căutare ceea ce înseamnă că relaţia de ordine dintre nodurile arborelui va trebui să se păstreze.
Pentru aceasta, unul din nodurile prezentate anterior va trebui adus în locul nodului care se
doreşte a fi şters după care are loc ştergerea efectivă a nodului dorit.
Determinarea celui mai din dreapta fiu al subarborelui stâng se face parcurgând
subarborelui stâng prin vizitarea numai a fiilor din dreapta. Primul nod care nu are subarbore
drept este considerat ca fiind cel mai din dreapta nod al subarborelui stâng.
142
În figura 7.18 se prezintă un fragment de arbore binar de căutare din care dorim să
suprimăm nodul evidenţiat care are doi descendenţi. Presupunem ca nodul haşurat este cel
mai din dreapta fiu al subarborelui stâng. Acest nod va lua locul nodului care se va şterge. În
figura 7.19 este reprezentat fragmentul de arbore după efectuarea operaţiei de ştergere a
nodului dorit.
… … …
… … … …
7.4. Aplicaţii care utilizează structura de date de tip arbore binar de căutare
Asupra arborilor binari de căutare pot fi efectuate o serie de operaţii, dintre care o
parte sunt specifice tuturor structurilor de date compuse (adăugare element, ştergere
elemente) iar altele sunt specifice acestui tip de structură de date. De asemenea se
remarcă operaţii la nivel de element (nod), precum şi operaţii care implică întregul arbore.
Programul următor, exemplifică o modalitate de creare a unui arbore binar de căutare,
stergere noduri, traversare, numarare noduri şi de tipărire a acestuia:
#include <iostream.h>
#include <conio.h>
#include <stdio.h>
143
#include <malloc.h>
#include <string.h>
struct nod
{
int info;
nod *stg,*drt;
};
class arbbin
{
nod *rad;
int stergere(nod *&);
void stergere_nod(nod*&,int);
public:
arbbin()
{rad=NULL;};
~arbbin()
{rad=NULL;};
void traversare_srd(nod*);
void srd();
void traversare_rsd(nod *);
void rsd();
void traversare_sdr(nod *);
void sdr();
int sumaFrunze(nod*);
int sFrunza();
int numara(nod *);
int numara_nod();
void print(nod *);
void tiparire();
void salvare();
nod *inserare_nod(nod *,int );
void operator + (int);
void operator - (int);
arbbin &operator >(FILE *);
arbbin &operator <(FILE *);
void inserare_cuv(nod *& ,char*);
void insert(char *);
nod *operator [] (int);
};
144
return rad;
}
else
{
nod *p=new nod;
p->stg=NULL;
p->drt=NULL;
p->info=k;
return p;
}
}
void arbbin::srd()
{
nod *p;
p=rad;
traversare_srd(p);
}
void arbbin::rsd()
{
nod *p;
p=rad;
traversare_rsd(p);
}
145
void arbbin::traversare_sdr(nod *rad)
{
if (rad)
{
traversare_sdr(rad->stg);
traversare_sdr(rad->drt);
printf(" %d",rad->info);
}
}
void arbbin::sdr()
{
nod *p;
p=rad;
traversare_sdr(p);
}
void arbbin::tiparire()
{
nod *p;
p=rad;
print(p);
}
146
else
return 0;
}
int arbbin::sFrunza()
{
nod *p;
p=rad;
return sumaFrunze(p);
}
int arbbin::numara_nod()
{
nod *p;
int nr;
p=rad;
nr=numara(p);
return nr;
}
147
rad->info=stergere(rad->stg);
}
}
void meniu()
{
148
cout<<"\n 4.TRAVERSARI ARBORE-SRD(),RSD(),SDR()";
cout<<"\n 5.SUMA INFORMATIILOR DIN FRUNZE ";
cout<<"\n 6.NUMARUL NODURILOR DIN ARBORE";
cout<<"\n 7.AFISARE ARBORE";
cout<<"\n 0.TERMINARE";
}
void main()
{
arbbin arb;
int informatie;
char optiune;
arb+10;arb+7;arb+15;arb+9;arb+3;arb+8;arb+25;
meniu();
}
}
Teme de control
Bibliografie
Ion IVAN, Marius POPA, Paul POCATILU - Structuri de date, Vol. 1 si 2, Editura ASE,
Bucuresti, 2008.
149
8. Arbori B
După studierea acestei unităţi de învăţare, studenţii îşi vor însuşi cunoştinţe
teoretice şi vor deprinde abilităţi practice de lucru cu a rbori B.
● 50 ● 100 ●
● 20 ● 40 ● ● 70 ● 120 ● 140 ●
7 18 26 36 58 62 69 145
150
n P0 K 0 P1 K 1 P2 … Pn - 1 K n - 1 Pn
unde P0, P1, …, Pn sunt pointeri către subarbori şi K 0, K 1, … , K n – 1 sunt valorile cheilor.
Cerinţa ca fiecare nod să aibă un număr de ramificaţii mai mic sau egal decât m conduce la
restricţia n ≤ m – 1.
valorile cheilor într-un nod sunt date în ordine crescătoare: K i < K i + 1 ,
i 0, n 2
toate valorile de chei din nodurile subarborelui indicat de P i sunt mai mici
decât valoarea cheii K i, i 0, n 1 .
toate valorile de chei din nodurile subarborelui indicat de P n sunt mai mari
decât valoarea de cheie K n – 1.
subarborii indicaţi de Pi, i 0, n sunt de asemenea arbori multicăi de căutare.
Prima oară arborele B a fost descris de R. Bayer şi E. McCreight în 1972. Arborii B
rezolvă problemele majore întâlnite la implementarea arborilor de căutare stocaţi pe disc:
au întotdeauna toate nodurile frunză pe acelaşi nivel (cu alte cuvinte sunt
echilibraţi după înălţime);
operaţiile de căutare şi actualizare afectează puţin blocuri pe disc;
păstrează articolele asemănătoare în acelaşi bloc pe disc;
garantează ca fiecare nod din arbore va fi plin cu un pro cent minim garantat.
151
8.2. Operaţii de bază într-un arbore B
Fig. 8.3. Modificările dimensionale ale unui arbore B după efectuarea inserării
152
Să considerăm un arbore B de ordin 5 (deci numărul maxim de chei dintr -un nod va fi
4). În urma inserării valorilor de cheie 22, 57, 41, 59 nodul rădăcină va fi cel din figura 8.4.
22 41 57 59
Fig. 8.4. Structura nodului rădăcină după inserarea cheilor
În urma inserării cheii 54, nodul rădăcină va conţine prea multe chei, aşa că el va
fisiona, figura 8.5.
cheia
22 41 54 57 59 22 41 57 59
Fig. 8.5. Fisionarea nodului rădăcină
● 54 ●
b c
22 41 57 59
Fig. 8.6. Formarea noii rădăcini a arborelui
● 54 ●
b c
22 33 41 57 59 75 124
Fig. 8.7. Structura arborelui după inserarea cheilor 33, 75, 124
Cheia mediana 62
va promova
c c d
57 59 62 75 124 57 59 75 124
● 54 ● 62 ●
b c d
22 41 57 59 75 124
153
În urma inserării cheilor 33, 122, 123, 55, 60, 45, 66, 35 configuraţia arborelui va fi
cea din figura 13.10.
a
35 54 62 122
b f c d e
22 33 41 45 55 57 59 60 66 75 123 124
c c g
55 56 57 59 60 55 56 59 60
Fig. 8.11. Fisionarea nodului c
Oricum, nodul părinte a este deja plin şi nu poate primi noua cheie 57 şi pointerul
către nodul nou format f. Algoritmul de fisionare este aplicat din nou, dar de data aceasta
nodului a, figura 8.12.
Cheia mediana 57
va promova
a a h
35 54 57 62 122 35 54 62 122
a h
35 54 62 122
b f c g d e
22 33 41 45 55 56 59 60 66 75 123 124
Uzual, în special pentru arbori B de ordin mare, un nod părinte are suficient spaţiu
disponibil pentru a primi valoarea unei chei şi un pointer către un nod descendent. În cel mai
rău caz algoritmul de fisionare este aplicat pe întreaga înălţime a arborelui. În acest mod
arborele va creşte în înălţime, lungimea drumului de căutare crescând cu 1.
Sintetizat, algoritmul de inserare a unei valori de cheie în arbore este prezentat în
figura 8.14.
154
Adaugă noua valoare
de cheie în nodul
frunză corespunzator
OVERFLOW?
pentru o valoare mare a lui p şi p 2 este aproximativ 1). De exemplu, pentru m = 10,
probabilitatea apariţiei unei divizări este de 0.25. Pentru m = 100, probabilitatea divizării este
0.0204. Pentru m = 200, probabilitatea divizării este 0.0101. Cu alte cuvinte, cu cât ordinul
arborelui este mai mare, cu atât este mai mică probabilitatea ca inserarea unei valori de cheie
sa ducă la divizarea unui nod.
Operaţia de ştergere dintr-un arbore B este ceva m ai complicată decât operaţia de
inserare. Operaţia de ştergere se realizează simplu dacă valoarea de cheie care
urmează a fi ştearsă se află într -un nod frunză. Dacă nu, cheia va fi ştearsă logic, fiind
155
înlocuită cu o alta, vecină în inordine, care va fi ştearsă efectiv. În urma ştergerii se disting
următoarele cazuri:
m
dacă nodul conţine mai mult de 2 chei, ştergerea nu ridică probleme;
m
dacă nodul are numărul minim de chei 2 , după ştergere numărul de chei din
nod va fi insuficient. De aceea se împrumută o ch eie din nodul vecin (aflat pe
m
acelaşi nivel în arbore) dacă acesta are cel puţin 2 chei, caz în care avem de-a
face cu o partajare. Dacă nu se poate face o partajare cu nici unul din nodurile
vecine (fiecare nod vecin are numărul minim de chei), atunci cele două noduri
vecine vor fuziona, împrumutându-se o cheie şi din nodul părinte. Partajarea sau
fuzionarea trebuie eventual repetate şi pentru nivelurile superioare. În cazul cel
mai nefavorabil, dacă partajarea sau fuzionarea parcurg întreaga înălţime a
arborelui, se va forma o nouă rădăcină, înălţimea arborelui scăzând cu unu.
Probabilitatea de fuzionare a nodurilor scade o dată cu creşterea ordinului
arborelui B.
Să considerăm următoarea configuraţie de a rbore B de ordin 5 din figura 8.15.
a
57
b c
35 54 62 122
d f g h i
22 33 22 55 56 59 60 66 75 123 124
e
40 41 45 50
Fig. 8.15. Arbore B de ordin 5
b c
35 54 62 122
d e f g h i
22 33 34 41 50 55 56 59 60 66 75 123 124
Ştergerea valorii de cheie 50 necesită însă partajarea între nodurile d şi e, figura 8.17.
156
a
57
valoarea de cheie 35
coboara; valoarea de
b c
cheie 34 urca
35 54 62 122
d e f g h i
22 33 34 41 50 55 56 59 60 66 75 123 124
a
57
b c
34 54 62 122
d e f g h i
22 33 35 41 55 56 59 60 66 75 123 124
fuzionare b c
34 54 62 122
d e f g h i
22 33 35 41 55 56 59 60 66 75 123 124
Însă în urma fuzionării nodurilor d şi e, nodul b va conţine prea puţine valori de cheie,
aşa că vor fuziona şi nodurile b şi c, figura 8.20.
a
fuzionare
57
b c
54 62 122
d f g h i
33 34 35 41 55 56 59 60 66 75 123 124
157
Astfel, în final, arborele B va arăta astfel ca în figura 8 .21.
122
54 57 62
123 124
33 34 35 41 55 56 59 60 66 75
Fig. 8.21. Structura finală a arborelui B
8.3. Algoritmii C++ pentru inserarea unei valori de cheie într-un arbore B
158
}
} ; /** struct less_than */
template <>
struct less_than<char*> : public binary_function<char*, char*, bool> {
inline bool operator()( const char* first, const char* second ) {
return strcmp( first, second ) < 0 ;
}
} ; /** struct less_than<char*> */
template <>
struct equal_to<char*> : public binary_function<char*, char*, bool> {
inline bool operator()( const char* first, const char* second ) {
return strcmp( first, second ) == 0 ;
}
} ; /** struct equal_to<char*> */
public:
typedef KEY_NODE_TYPE key_node_type ;
typedef key_node_type* key_node_ptr ;
typedef btree_node<KEY_NODE_TYPE> btree_node_type ;
typedef btree_node_type* btree_node_ptr ;
typedef struct {
key_node_type key_value ;
btree_node_ptr node_ptr ;
} btree_node_tuple ;
public:
enum BTREE_NODE_STATUS {
EMPTY = 0,
UNDERFLOW,
MINIMAL,
OPERATIONAL,
FULL,
OVERFLOW
159
};
public:
btree_node( int ncapacity ) ;
virtual ~btree_node( void ) ;
protected:
static int initialize( btree_node_ptr node ) ;
int shift2right( const int start ) ;
int shift2left( const int start ) ;
protected:
btree_node( const btree_node<KEY_NODE_TYPE>& ) ;
btree_node<KEY_NODE_TYPE>& operator =( const
btree_node<KEY_NODE_TYPE>& ) ;
protected:
less_than<key_node_type> _less_than ;
equal_to<key_node_type> _equal_to ;
protected:
key_node_ptr node_keys ;
btree_node_ptr* node_childs ;
int node_capacity ;
int node_size ;
}; /** class btree_node */
160
FULL – nodul conţine numărul maxim de chei ;
OVERFLOW – nodul conţine prea multe chei.
Ordinea în care câmpurile din enumerare apar este importantă (a se vedea funcţiile de
introducere şi ştergere a unei chei din arbore). Constructorul clasei btree_node primeşte ca
parametru numărul maxim de chei care pot fi păstrate într -un nod (capacitatea nodului).
Operaţiile de copiere a unui nod nu sunt permise (este dezactivat constructorul de
copiere şi operatorul de atribuire). Câmpurile _ less_than şi _equal_to sunt folosite în testele
de egalitate şi inegalitate strictă. Câmpul node_capacity păstrează numărul maxim de chei
dintr-un nod (capacitatea nodului). Câmpul node_size menţine numărul de chei aflate la un
moment dat într-un nod. Câmpul node_keys este un vector în care vor fi păstrate valorile de
chei din nod (în implementarea de faţă, pentru implementarea mai uşoara a algoritmului de
divizare a unui nod, numărul de chei păstrate într -un nod va fi cu unu mai mare decât numărul
maxim de chei). Câmpul node_childs va păstra pointeri către descendenţi (numărul de
descendenţi ai unui nod este cu unu mai mare decât numărul de chei din acel nod).
Constructorul btree_node<KEY_TYPE>::btree_node() apelează funcţia initialize()
care iniţializează un nod al arborelui (alocă memoria necesară pentru păstrarea valorilor de
chei şi a pointerilor către descendenţi, setează numărul de chei prezente în arbore la 0):
return 0;
}
161
btree_node<KEY_NODE_TYPE>::~btree_node( void ) {
delete []this->node_keys ;
delete []this->node_childs ;
}
Pentru a se afla numărul de chei aflate la un moment dat într -un nod se foloseşte
funcţia size():
Pentru a se determina numărul maxim de chei care pot fi păstrate într -un nod se
foloseşte funcţia capacity():
Un nod poate fi interogat pentru starea în care se află folosind funcţia status():
Se observă că starea nodului este funcţie de numărul de chei aflate la un moment dat
în nod. Mai exact, dacă avem un arbore B de ordin m, atunci numărul maxim de chei dintr -
m
1
un nod va fi m – 1, iar numărul minim de chei va fi 2 . Parametrul primit de constructor
va fi m – 1, deci capacitatea nodului va fi m – 1. Cum numărul de chei aflate la un moment
dat într-un nod se obţine folosind funcţia size(), vom avea:
dacă size() returnează zero, atunci în nod nu se găseşte nici o cheie starea
nodului este EMPTY ;
numărul minim de chei din nod este capacity()/2; deci dacă
size()==capacity()/2, atunci nodul are numărul minim de chei starea
nodului este MINIMAL ;
162
dacă size() < capacity() / 2 , atunci nodul are prea puţine chei starea nodului
este UNDERFLOW ;
dacă size() == capacity(), atunci nodul are numărul maxim de chei permise
starea nodului este FULL ;
dacă size() > capacity(), atunci nodul are prea multe chei starea nodului este
OVERFLOW .
Pentru a se determina dacă nodul este un nod frunză se foloseşte funcţia is_leaf(), care
va returna true dacă nodul este o frunză (un nod este nod frunză dacă nu are nici un
descendent):
// return 0 == this->node_childs[0] ;
for( int idx = 0; idx <= size(); idx++ )
if( 0 != this->node_childs[idx] ) return false ;
return true ;
}
Ca funcţii pentru interogarea valorii unei chei şi unui descendent dintr -o anume poziţie
se folosesc funcţiile key_value() şi child():
return this->node_keys[position] ;
}
163
return this->node_childs[position] ;
}
Căutarea unei valori de cheie într -un nod se face folosind funcţia find(). Primul
parametru primit de funcţie este valoarea de cheie care se caută în nod. După cum valoarea de
cheie căutată se găseşte sau nu în nod, find() va returna true sau false, cu menţiunea ca al
doilea parametru al funcţiei ( position, care este un parametru de ieşire) va fi setat după cum
urmează:
daca valoarea de cheie căutată se găseşte în nod, atunci position este setat la
indexul la care se găseşte cheia în nod;
dacă valoarea de cheie căutată nu se găseşte în nod, position va indica indexul
subarborelui în care s- ar putea găsi valoarea de cheie căutată.
return ret_value ;
}
164
template <typename KEY_NODE_TYPE>
int
btree_node<KEY_NODE_TYPE>::push(
const key_node_type& value,
btree_node_ptr child
){
if( OVERFLOW == status() ) return -1 ;
int key_position = -1 ;
if( find( value, key_position ) ) {
/** duplicate key value */
return -1 ;
}
return 0 ;
}
Funcţia shift2right() este:
165
Pentru eliminarea unei chei dintr- o anumită poziţie se va folosi funcţia remove_at()
(practic, eliminarea presupune diminuarea cu unu a numărului de chei conţinute de nod şi o
deplasare către stânga a valorilor de cheie şi a subarborilor aflaţi în dreapta poziţiei din care se
şterge cheia).
return 0 ;
}
Funcţia shift2left() este:
return 0 ;
}
Atunci când starea unui nod este de OVERFLOW , acesta se va diviza. În urma
divizării se va obţine un nod nou. Funcţia de divizare a unui nod este split(). Parametrul
funcţiei split() este de ieşire, fiind de tipul btree_node_tuple:
typedef struct {
key_node_type key_value ;
btree_node_ptr node_ptr ;
} btree_node_tuple ;
În urma procesului de divizare a unui nod va urca în nodul părinte cheia mediană şi un
pointer către nodul nou format. Cheia mediană va fi câmpul key_value al structurii
btree_node_tuple. Pointerul către nodul nou format va fi câmpul node_ptr al structurii
btree_node_tuple. Noul nod va conţine valorile de chei şi subarborii din dreapta cheii
mediane.
166
template <typename KEY_NODE_TYPE>
int
btree_node<KEY_NODE_TYPE>::split(
btree_node<KEY_NODE_TYPE>::btree_node_tuple* tuple
){
if( 0 == tuple ) return 0 ;
if( OVERFLOW != this->status() ) return 0;
this->node_size = median_position;
tuple->node_ptr = new_node;
return 0 ;
}
public:
typedef KEY_TYPE key_type ;
typedef btree_node<KEY_TYPE> btree_node_type ;
typedef btree_node_type* btree_node_ptr ;
typedef btree_node_type::btree_node_tuple btree_node_tuple ;
typedef btree<KEY_TYPE> btree_type ;
typedef btree_type* btree_ptr ;
public:
btree( int order ) ;
virtual ~btree( void ) ;
protected:
int push_down( btree_node_tuple* tuple, btree_node_ptr current ) ;
167
int remove_down( const key_type& value, btree_node_ptr current ) ;
int replace_with_predecessor( btree_node_ptr node, int position ) ;
void restore( btree_node_ptr current, const int position ) ;
void move_left( btree_node_ptr current, const int position ) ;
void move_right( btree_node_ptr current, const int position ) ;
void combine( btree_node_ptr current, const int position ) ;
private:
btree_node_ptr root ;
int order ;
} ; /** class btree */
Clasa btree este o clasă template după tipul valorii de cheie. La fel ca la clasa
btree_node au fost folosite o serie de typedef- uri în cadrul clasei (codul este mai uşor de scris
/ citit dacă tipul btree_node<KEY_TYPE> se redefineşte ca fiind btree_node_type). Nodul
rădăcină al arborelui B este dat de câmpul root . Ordinul arborelui B este dat de câmpul order .
Constructorul btree<KEY_TYPE>::btree() primeşte ca parametru ordinul arborelui şi
setează rădăcina arborelui la 0.
Funcţia de inserare a unei valori de cheie în arbore este push(). Dacă arborele nu are
nici o valoare de cheie inserată (adică rădăcina arborelui este 0 arborele este gol), atunci
inserarea valorii de cheie este simplă: se construieşte rădăcina arborelui cu valoarea de cheie
168
care se inserează. Daca arborele nu este gol, atunci se inserează cheia în arbore recursiv
folosind funcţia push_down(), pornind de la nodul rădăcină. Funcţia push_down() va returna 1
dacă în urma procesului de inserare recursivă a valorii de cheie nodul rădăcină a fost divizat
(cheia şi un pointer către subarborele drept al cheii vor fi conţinute de câmpurile variabilei
tuple). În acest caz înălţimea arborelui creşte cu unu, formându -se o nouă rădăcină cu
valoarea de cheie dată de câmp ul kei_value al variabilei tuple; subarborele stâng va fi vechea
rădăcină a arborelui, iar subarborele drept va fi dat de câmpul node_ptr al variabilei tuple.
btree_node_tuple tuple ;
tuple.key_value = value ;
tuple.node_ptr = 0 ;
return 0 ;
}
169
câmpurile parametrului tuple vor conţine valoarea de cheie care a urcat şi un pointer
către subarborele drept corespunzător cheii ;
în nodul curent vor trebui inserate key_value şi node_ptr indicate de câmpurile
parametrului tuple.
În final, se verifică starea în care se află nodul curent. Dacă starea acestuia este de
OVERFLOW , atunci înseamnă ca acesta conţine prea multe chei şi va trebui divizat. Pentru
aceasta se foloseşte metoda split() a nodului care va primi ca parametru adresa lui tuple. În
urma apelului metodei split() conţinutul câmpurilor key_value şi node_ptr ale variabilei tuple
vor fi actualizate pentru a indica valoarea de cheie care va urca în nodul părinte al nodului
curent şi pointerul către subarborele drept; în acest caz metoda push_down() va returna 1
pentru a indica faptul că în nodul părinte vor trebui folosite câmpurile variabile tuple. Dacă
starea nodului nu este OVERFLOW , metoda push_down() va returna 0.
int key_position ;
bool duplicate_key = current->find( tuple->key_value, key_position) ;
if( duplicate_key ) {
/** signal duplicate value */
return -1 ;
}
if( current->is_leaf() ) {
current->push( tuple->key_value, tuple->node_ptr );
} else {
if( push_down( tuple, current->child( key_position ) ) )
current->push( tuple->key_value, tuple->node_ptr);
}
return 0 ;
}
8.4. Algoritmii C++ pentru ştergerea unei valori de cheie intr-un arbore B
Funcţia de ştergere a unei valori de cheie dintr -un arbore B este remove(). Intern este
folosită funcţia recursivă remove_down(). Dacă în urma ştergerii valorii de cheie nodul
rădăcină nu mai are nici o valoare de cheie (starea nodului rădăcină este EMPTY ), atunci noua
170
rădăcină a arborelui este subarborele stâng al vechii rădăcini. Vechea rădăcină se elimină din
memorie.
delete old_root ;
}
return 0 ;
}
Funcţia de ştergere recursivă a unei valori de cheie din arbore este remove_down().
Parametrii primiţi de funcţie sunt valoarea de cheie care se şterge şi un pointer către nodul
curent. Dacă la un moment dat nodul curent este 0, atunci înseamnă că valoarea de cheie
solicitată a fi ştearsă nu se găseşte în arbore. Altfel, funcţie de tipul nodului unde cheia este
găsită avem următoarele cazuri:
dacă valoarea de cheie se găseşte într -un nod frunză, atunci ştergerea înseamnă
apelarea metodei remove() a nodului frunză pentru eliminarea cheii aflată în poziţia
dată de variabila position ;
dacă valoarea de cheie a fost găsită într -un nod care nu este nod frunza, atunci
valoarea de cheie care trebuie ştearsă va fi înlocuită, în implementarea de faţă, cu
valoarea de cheie care o precede (se poate demonstra că valoarea de cheie care o
precede se găseşte într -un nod frunză). După ce se face înlocuirea folosind funcţia
replace_with_predecessor(), se va continua algoritmul de ştergere, dar de data
aceasta se va solicita ş tergerea valorii de cheie cu care s- a făcut înlocuirea.
Se observă că dacă nodul curent este valid (nu este 0) şi valoarea de cheie care se
caută nu se găseşte în nodul curent, atunci variabila position va fi poziţionată de metoda find()
a nodului ca fiind poziţia subarborelui care este posibil să conţină valoarea de cheie.
În finalul funcţiei, dacă nodul curent nu este frunză (este un nod părinte care are
descendenţi care au fost afectaţi), se testează starea nodului rădăcină pentru subarborele pe
care s-a efectuat coborârea în procesul de ştergere. Dacă starea acestui nod este
UNDERFLOW (conţine prea puţine valori de cheie), atunci va fi apelată funcţia restore() care
va restaura proprietăţile de arbore B.
171
return 0 ;
}
int position ;
if( current->find( value, position ) ) {
if( current->is_leaf() ) current->remove_at( position ) ;
else {
replace_with_predecessor( current, position ) ;
remove_down(current->key_value(position ),
current->child( position)) ;
}
} else remove_down( value, current->child( position ) ) ;
return 0 ;
}
Funcţia de înlocuire a unei valori de cheie cu valoarea de cheie care o precede este
replace_with_succesor(). Parametrii primiţi de funcţie sunt: node, un pointer către nodul care
conţine valoarea de cheie care se înlocuieşte şi position care dă poziţia su barborelui (în nodul
indicat de node) care conţine valoarea de cheie care precede valoarea de cheie care se
înlocuieşte.
return 1 ;
}
172
template <typename KEY_TYPE>
void btree<KEY_TYPE>::move_left(
btree_node_ptr current,
const int position
){
btree_node_ptr node = current->child( position - 1 ) ;
node->push(
current->key_value( position - 1 ),
current->child( position )->child( 0 )
);
current->remove_at( position - 1 ) ;
delete rnode ;
}
174