Sunteți pe pagina 1din 120

6,/9,8%Æ5=

LUCIANA MARIA MOROGAN


STRUCTURI DE DATE
'HVFULHUHD&,3D%LEOLRWHFLL1D LRQDOHD5RPkQLHL
%Æ5= 6,/9,8
Structuri de date  6LOYLX %kU]  Luciana Maria
Morogan. –%XFXUHúWL(GLWXUD)XQGD LHLRomânia de
Mâine, 2007
120p.; 23,5 cm
Bibliogr.
ISBN 978-973-725-689-8

I. Morogan, Luciana

044.422.63

©(GLWXUD)XQGD LHLRomânia de Mâine, 2007

7HKQRUHGDFWRU6LOYLX%Æ5=
&RSHUWD0DULOHQD% /$1
Bun de tipar: 15.02.2007; Coli tipar: 7,5
Format: 16/70×100
(GLWXUD)XQGD LHL5RPkQLDGH0kLQH
%XOHYDUXO7LPLúRDUDQU%XFXUHúWL6HFWRU
Tel./Fax 021/444.20.91; www.spiruharet.ro
e-mail: contact@edituraromaniademaine.ro
UNIVERSITATEA SPIRU HARET
)$&8/7$7($'(0$7(0$7,& ù,,1)250$7,&

6,/9,8%Æ5=
LUCIANA MARIA MOROGAN

(',785$)81'$ ,(,ROMÂNIA DE MÂINE


%8&85(ù7,
CUPRINS

Introducere ……………………………………………………….. 7
I. Informaţie, coduri, reprezentări …….……………………….. 9
I.1. Informaţie ………………………………………………………. 9
I.2. Elemente de codificare a informaţiei …………………………… 9
I.3. Reprezentarea datelor numerice ………………………………… 11
I.3.1. Reprezentarea numerelor naturale ………………………. 12
I.3.2. Reprezentarea numerelor întregi ………………………… 13
I.3.3. Reprezentarea numerelor reale …………………………... 15
I.4. Reprezentarea datelor nenumerice ……………………………… 17
I.5. Elemente de memorare a informaţiei …………………………… 17
II. Tablouri ……………………………………………………………… 19
II.1. Definirea tablourilor ……………………………………………. 19
II.2. Implementarea statică a tablourilor unidimensionale …………... 21
II.3. Implementarea dinamică a tablourilor unidimensionale ………... 22
II.3.1. Vectori dinamici cu implementare fixă ……………….. 22
II.3.2. Vectori dinamici cu implementare variabilă ………….. 24
II.4. Implementarea statică a tablourilor multidimensionale ………… 25
II.5. Implementarea dinamică a tablourilor multidimensionale ……… 28
II.5.1. Tablouri dinamice cu implementare fixă ……………….. 29
II.5.2. Tablouri dinamice cu implementare variabilă ………….. 30
II.6. Implementarea matricelor triunghiulare ………………………… 31
II.7. Implementarea matricelor simetrice …………………………….. 33
II.8. Implementarea matricelor bandă ………………………………... 34
II.8.1. Matrice p-diagonală ………………………………….. 34
II.8.2. Matrice triunghiulară k-diagonală …………………… 36
II.8.3. Matrice bandă ………………………………………… 39
II.9. Implementarea dinamică semicompactă a matricelor …………... 40
III. Liste liniare …………………………………………………………. 42
III.1. Definiţie, operaţii asupra listelor ……………………………….. 42
III.2. Liste liniare particulare ………………………………………… 43
III.2.1. Stiva ……………………………………………………. 43
III.2.2. Coada …………………………………………………... 44
III.2.3. Coada completă ………………………………………... 44
III.2.4. Coada cu priorităţi …………………………………….. 45
III.3. Implementarea secvenţială a listelor liniare ……………………. 46
III.3.1. Stiva secvenţială ……………………………………….. 46
III.3.2. Coada secvenţială ……………………………………… 47
5
III.3.3. Coada secvenţială completă …………………………… 49
III.3.4. Coada secvenţială cu priorităţi ………………………… 52
III.3.5. Liste secvenţiale cu spaţiu partajat ……………………. 54
III.4. Implementarea înlănţuită a listelor liniare ……………………… 56
III.4.1. Implementarea listelor liniare simplu înlănţuite ……….. 57
III.4.2. Stiva simplu înlănţuită …………………………………. 61
III.4.3. Coada simplu înlănţuită ………………………………... 62
III.4.4. Implementarea listelor liniare dublu înlănţuite ………... 65
III.4.5. Implementarea cozilor cu dublă înlănţuire …………….. 69
III.4.6. Implementarea cozilor circulare cu dublă înlănţuire …... 70
III.4.7. Implementarea cozilor cu priorităţi dublu înlănţuite …... 71
IV. Structuri de date neliniare ………………………………………… 72
IV.1. Reţele planare şi spaţiale ……………………………………….. 72
IV.2. Implementarea structurilor de liste …………………………….. 78
IV.3. Structuri arborescente ………………………………………….. 79
IV.3.1. Arbori ………………………………………………….. 79
IV.3.2. Arbori binari …………………………………….…….. 80
IV.3.3. Arbori oarecare ………………………………….……... 86
V. Căutare ………………………………………………………... 97
V.1. Căutare secvenţială ……………………………………………… 97
V.2. Căutare în structuri ordonate ……………………………………. 101
V.2.1. Căutare în vectori ordonaţi ……………………….…….. 101
V.2.2. Căutare în arbori binari …………………………………. 103
VI. Interclasarea …………………………………………………. 107
VI.1. Interclasarea a doi vectori ……………………………….……... 107
VI.2. Interclasarea a două liste liniare ………………………………... 109
VI.3. Interclasarea multiplă …………………………………………... 110
VI.4. Operaţii de tip mulţime ………………………………….……... 111
VII. Sortarea ……………………………………………………... 112
VII.1. Sortarea prin numărarea comparaţiilor ………………………... 113
VII.2. Sortarea prin inserţie …………………………………………... 115
VII.3. Sortarea prin interschimbare …………………………………... 117
VII.4. Sortarea prin interclasare ……………………………….……... 118
Bibliografie……………………………………………………….. 120

6
INTRODUCERE

Lucrarea se adresează studenţilor de la specializările


Informatică şi Matematică şi a fost redactat pentru a corespunde
cerinţelor programei analitice a disciplinei cu acelaşi nume.

Sunt prezentate o serie de elemente introductive relative la


codificarea informaţiei, modalităţi uzuale de organizare a datelor
împreună cu algoritmi specifici de acţionare asupra structurilor
descrise.

În finalul cursului sunt prezentate trei dintre cele mai


importante problematici ce intervin în prelucrarea structurilor, şi
anume: căutarea, interclasarea şi sortarea.

În scurt timp, vom edita şi o culegere de exerciţii şi


probleme pentru structuri de date.

Mulţumim domnului prof. Ioan Tomescu de la Facultatea


de Matematică a Universităţii din Bucureşti pentru îndrumările
domniei sale în realizarea acetui volum.

Autorii

7
8
I. INFORMAŢIE, CODURI, REPREZENTĂRI
Acest capitol prezintă, pe scurt, principalele noţiuni legate de modul în
care informaţia este codificată şi reprezentată în sistemele de calcul. După definirea
elementelor legate de informaţii şi a celor privind codificarea formală a acestora,
sunt abordate modurile de reprezentare a datelor elementare într-un sistem de
calcul.

I.1. Informaţie
Elementele din lumea reală pot fi împărţite în clase, entităţi şi obiecte. O
realizare a unui tip este un element particular al tipului.
Elementele sunt definite prin proprietăţi la care sunt asociate valori dintr-o
anumită mulţime, numită domeniu de valori, ce se specifică în raport cu o măsură
prin intermediul cuvintelor dintr-un alfabet dat.
Informaţiile sunt valori asociate proprietăţilor şi sunt prezentate la
prelucrarea prin intermediul sistemelor de calcul,
♦ în format extern (conform modului de percepţie prin simţuri);
♦ în format intern (prin intermediul unui alfabet p , un element al acestuia
numindu-se cifră binară sau bit).
Din punct de vedere logic, în general, memoria sistemelor de calcul se
presupune a fi formată din şiruri de elemente de 8 biţi, numite octeţi, locaţii de
memorie sau bytes (sg. byte), care sunt identificate prin intermediul unor valori
numere naturale, numite adrese de memorie.

I.2. Elemente de codificare a informaţiei

Definiţie. Dacă A este o mulţime nevidă şi a1 , a 2 ,..., a n ∈ A , atunci şirul


obţinut prin alipirea elementelor a1 , a 2 ,..., a n se numeşte cuvânt peste alfabetul
A şi se notează prin a1 a 2 ...a n . Mulţimea tuturor cuvintelor peste alfabetul A se
{
notează prin A* = a1 a 2 ...a n ai ∈ A,1 ≤ i ≤ n, n ∈ N . }
Pentru A alfabet şi n ∈ N fixat putem defini mulţimea
{ }
A = a1 a 2 ...a n ai ∈ A, 1 ≤ i ≤ n , numită mulţimea cuvintelor de lungime n
n


peste alfabetul A . Avem A = *
∪A n
, unde A 0 = {λ} este mulţimea, cu un
n=0
singur element, al cuvintelor de lungime 0; λ este numit cuvântul vid.
9
Definiţie. Fie A un alfabet şi a, b ∈ A* , a = a1 a 2 ...a n , b = b1b2 ...bm ,
două cuvinte. Spunem că a şi b sunt identice dacă n = m şi oricare ar fi i ,
1 ≤ i ≤ n avem a i = bi . În caz contrar spunem că a şi b sunt distincte.
Definiţie. Fie A şi B două alfabete. Orice aplicaţie ϕ : A → B * se
numeşte cod bloc. Pentru orice a ∈ A , ϕ(a ) ∈ B * se numeşte cuvântul cod
asociat lui a .
Definiţie. Fie A şi B două alfabete şi ϕ : A → B* un cod bloc. Pentru
k∈N fixat, aplicaţia ϕk : Ak → B* care extinde pe ϕ prin
( )
ϕ k a1 a 2 ...a k = α1α 2 ...α k , unde α i = ϕ(a i ) pentru orice i = 1,2,..,.k se
numeşte extensia de ordin k a codului bloc ϕ . Aplicaţia ϕ* : A* → B * care
( )
extinde pe ϕ prin ϕ* a1 a 2 ...a n = α1 α 2 ...α n , unde α i = ϕ(a i ) pentru orice
i = 1,..., n se numeşte cod.
Definiţie. Fie A şi B două alfabete şi ϕ : A → B * un cod bloc. Spunem
că ϕ este nesingular (nedegenerat) dacă şi numai dacă oricare ar fi a, b ∈ A ,
a ≠ b , ϕ(a ) şi ϕ(b ) sunt distincte. În caz contrar spunem că ϕ este singular
(degenerat).
Noţiunea se extinde şi la ϕ n ( ϕ * ) dacă în definiţia de mai sus considerăm
a, b ∈ A n ( a, b ∈ A* ), a şi b distincte în loc de a, b ∈ A , a ≠ b .
Definiţie. Fie A şi B două alfabete şi ϕ : A → B * un cod bloc. Spunem
că ϕ este cu decodificare unică dacă şi numai dacă, pentru orice n ∈ N , ϕ n este
nesingulară.
Definiţie. Fie A şi B două alfabete şi ϕ : A → B * un cod bloc. Spunem
că ϕ este uniform dacă şi numai dacă oricare ar fi a, b ∈ A , a ≠ b , ϕ(a ) şi
ϕ(b ) au aceeaşi lungime.
Definiţie. Un cod cu decodificare unică se numeşte cod instantaneu dacă
se poate face decodificarea oricărui cuvânt cod fără a cunoaşte simbolurile care îl
urmează.
Codificarea în care B = E 2 = {0,1} se numeşte codificare binară şi este
codificarea utilizată în reprezentarea informaţiei în sistemele de calcul.
De cele mai multe ori, în sistemele de calcul, datele sunt reprezentate prin
codificare pe un număr de 8, 16, 32 sau 64 de biţi, respectiv pe 1, 2, 4 sau 8 octeţi
şi sunt interpretate în funcţie de tipul lor. Astfel, pentru codificare este suficient să
considerăm un cod bloc ϕ : A → E 2c , unde A este alfabetul de reprezentare
externă şi E 2c = E 28 ∪ E 216 ∪ E 232 ∪ E 264 şi ϕ este nesingular, iar pentru un tip de
informaţie dat t , restricţia lui ϕ la t este un cod uniform.
10
În sistemele de calcul, fiecare informaţie codificată este caracterizată de un
triplet (a, l , t ) , unde a ∈ N este adresa de început prin care se identifică locul
unde se află primul octet din reprezentarea informaţiei, l ∈ {8,16,32,64} este
lungimea care determină numărul de biţi consideraţi în reprezentarea informaţiei şi
t este tipul de reprezentare care fixează modul de interpretare a şirului de biţi.
De cele mai multe ori a este un multiplu de l / 8 .
O proprietate importantă a codurilor binare utilizate la reprezentarea
informaţiei în sistemele de calcul este legată de necesitatea ca prin decodificare să
fie obţinută informaţia iniţială, deci ca restricţia lui ϕ la un tip de reprezentare să
fie o aplicaţie inversabilă (bijectivă).

I.3. Reprezentarea datelor numerice

Reprezentarea internă a datelor numerice priveşte modalităţile de


codificare a informaţiilor numerice care pot fi de unul din tipuri întreg şi raţional.
Datele de tip întreg pot fi reprezentate sub două forme, una directă, care
este valabilă pentru numerele naturale, şi una numită complementară la 2, pentru
reprezentarea generală a valorilor numere întregi.
Reprezentarea raţională este una prin mărime şi semn cu complementare la
2 şi este utilizată pentru valorile care sunt numere raţionale zecimale. Pentru
reprezentarea numerelor reale, acestea sunt aproximate prin lipsă cu numere
raţionale, cu o precizie dependentă de sistemul de calcul şi de subtipul de
reprezentare (precizie simplă, dublă sau extinsă).
Definiţie. Fie A un alfabet relativ la tipul de informaţie t şi B un alfabet
relativ la tipul de informaţie s . Spunem că s este subtip al tipului t dacă şi
numai dacă B ⊆ A . În acest caz spunem şi că B este restricţia alfabetului A la
subtipul de informaţie s şi scriem B = A s .
Definiţie. Fie A un alfabet relativ la tipul de informaţie t şi
ϕ t : A → E 2c un cod binar inversabil. Fie u subtip al lui t şi restricţia lui ϕ t ,
ϕ u : A u → Bu , definită prin ϕ u (a ) = ϕ t (a ) , oricare ar fi a ∈ A u şi pentru care
( )
Bu = ϕ t A u . ϕ u se numeşte restricţia inversabilă a codului ϕ t la subcodul u .
Observaţie. În condiţiile definiţiei anterioare, dacă a ∈ Au şi
b = ϕ u (a ) ∈ Bu ⊂ E 2c , atunci ϕ u−1 (b ) = ϕ t−1 (b )
Definiţie. Fie A un alfabet relativ la tipul de informaţie t numeric şi
ϕ t : A → E 2c un cod binar. Fie u şi v două subtipuri nedisjuncte ale lui t şi
ϕ u : A u → Bu şi ϕ v : A v → Bv restricţiile inversabile ale codului ϕ t la
subtipurile u şi v . Codificările ϕ u şi ϕ v se numesc echivalente cu o eroare de
ordinul k dacă şi numai dacă pentru orice a ∈ A pentru care bu = ϕ t (a ) ∈ Bu
11
şi bv = ϕ t (a ) ∈ Bv avem ϕ u−1 (bu ) − ϕ v−1 (bv ) ≤ 10 − k . Codificările ϕ u şi ϕ v se
numesc relativ echivalente dacă şi numai dacă există k ∈ N * astfel încât ele sunt
echivalente cu o eroare de ordinul k .
Definiţie. Fie A un alfabet relativ la tipul de informaţie t numeric şi
ϕ t : A → E 2c un cod binar. Fie u şi v două subtipuri nedisjuncte ale lui t şi
ϕ u : A u → Bu şi ϕ v : A v → Bv restricţiile inversabile ale codului ϕ t la
subtipurile u şi v . Codificările ϕ u şi ϕ v se numesc echivalente pe
C = Au ∩ Av dacă şi numai dacă pentru orice a ∈ A pentru care
bu = ϕ t (a ) ∈ Bu şi bv = ϕ t (a ) ∈ Bv avem ϕ u−1 (bu ) = ϕ v−1 (bv ) .
Propoziţie. Fie A un alfabet relativ la tipul de informaţie t şi
ϕ t : A → E 2c un cod binar. Fie u , v şi w trei subtipuri ale lui t , având codurile
respectiv ϕ u , ϕ v şi ϕ w . Dacă w este subtip atât pentru u , cât şi pentru v ,
atunci ϕ u şi ϕ v sunt echivalente pe A w .

I.3.1. Reprezentarea numerelor naturale

La reprezentarea numerelor naturale, în tripletul (a, l , t ) de caracterizare a


informaţiei t poate avea una din semnificaţiile byte sau word.
Semnificaţia byte corespunde unei reprezentări în care l = 8 şi care
permite codificarea pentru orice n ∈ N , 0 ≤ n ≤ 255 . Codul este o aplicaţie
ϕ N 1 : {0,1,...,255} → E 28 , definită prin ϕ N 1 (a ) = b7 b6 ...b1b0 care asociază fiecărei
valori a ∈ {0,1,...,255} un unic şir de 8 cifre binare b7 b6 ...b1b0 cu proprietatea că
7
a = ∑ bk 2 k .
k =0
Semnificaţia word corespunde unei reprezentări în care l = 16 şi care
permite codificarea pentru orice n ∈ N , 0 ≤ n ≤ 65535 . Codul este o aplicaţie
ϕ N 2 : {0,1,...,65535} → E 216 , definită prin ϕ N 2 (a ) = b15 ...b1b0 care asociază
fiecărei valori a ∈ {0,1,...,65535} un unic şir de 16 cifre binare b15 ...b1b0 cu
15
proprietatea că a = ∑b
k =0
k 2k .
Proprietăţile date pentru şirurile de cifre binare dau inversele celor două
coduri şi, în acelaşi timp, sugerează modalitatea de construcţie a codurilor. Astfel,
codurile se obţin prin aplicarea a doi paşi, şi anume:

12
♦ se realizează scrierea numărului natural din baza de numeraţie 10 în baza de
numeraţie 2;
♦ se completează rezultatul obţinut, în faţă, cu zerouri pentru a obţine lungimea
l.
După cum se poate vedea, byte este subtip al tipului word. Dacă
ϕ N 2 (a ) = b15 ...b8 b7 ...b1b0 şi a ∈ {0,1,...,255}, atunci bi = 0 pentru orice
i = 8,9,...,15 şi, fără a pierde din semnificaţie, putem omite primele 8 cifre binare,
obţinând astfel chiar ϕ N 1 (a ) = b7 ...b1b0 (deoarece
15 7 15 7
a = ∑ bk 2 k = ∑ bk 2 k + ∑ 0 ⋅ 2 k = ∑ bk 2 k ). Reciproc, dacă avem codificarea
k =0 k =0 k =8 k =0

ϕ N 1 (a ) = b7 ...b1b0 pentru a ∈ {0,1,...,255}, avem a ∈ {0,1,...,65535} şi putem


considera b15 = ... = b8 = 0 pentru a putea scrie
ϕ N 2 (a ) = 0...0b7 ...b1b0 = b15 ...b8 b7 ...b1b0 .

I.3.2. Reprezentarea numerelor întregi

La reprezentarea numerelor întregi, tripletul (a, l , t ) de caracterizare a


informaţiei t poate avea una din indicaţiile short, integer sau long.
Considerăm întâi cazul reprezentării valorilor întregi nenegative.
Pentru semnificaţia short considerăm o reprezentare în care l = 8 şi care
permite codificarea pentru orice n ∈ Z , 0 ≤ n ≤ 127 . Codul este o aplicaţie
ϕ′Z 1 : {0,1,...,127} → E 28 , definită prin ϕ′Z 1 (a ) = 0b6 ...b1b0 care asociază fiecărei
valori a ∈ {0,1,...,127} un unic şir de 8 cifre binare 0b6 ...b1b0 cu proprietatea că
6
a = ∑ bk 2 k + 0 ⋅ 2 7 . Vom nota acest tip short+.
k =0
Pentru semnificaţia integer considerăm o reprezentare în care l = 16 şi
care permite codificarea pentru orice n ∈ Z , 0 ≤ n ≤ 32767 . Codul este o
aplicaţie ϕ′Z 2 : {0,1,...,32767} → E 28 , definită prin ϕ′Z 2 (a ) = 0b14 ...b8 b7 ...b1b0
care asociază fiecărei valori a ∈ {0,1,...,32767} un unic şir de 8 cifre binare
14
0b14 ...b8 b7 ...b1b0 cu proprietatea că a = ∑ bk 2 k + 0 ⋅ 2 7 . Vom nota acest tip
k =0
integer+.
Pentru semnificaţia long considerăm o reprezentare în care l = 32 şi care
permite codificarea pentru orice n ∈ Z , 0 ≤ n ≤ 2147483647 . Codul este o
aplicaţie ϕ′Z 3 : {0,1,...,2147483647} → E 28 , definită prin

13
ϕ′Z 3 (a ) = 0b30 ...b16 b15 ...b8 b7 ...b1b0 care asociază fiecărei valori
a ∈ {0,1,...,2147483647} un unic şir de 8 cifre binare 0b30 ...b16 b15 ...b8 b7 ...b1b0
30
cu proprietatea că a = ∑b
k =0
k 2 k + 0 ⋅ 2 7 . Vom nota acest tip long+.
Urmând aceleaşi argumentări ca în paragraful I.3.1. se obţine că integer+
este subtip pentru long+ şi că short+ este subtip pentru integer+, şi astfel, este
subtip şi pentru long+. Trecerile de la un tip la altul se fac similar construcţiilor din
paragraful I.3.1. De asemenea, codurile se obţin în acelaşi mod în care se obţin cele
din paragraful I.3.1.
Comparând aceste coduri cu cele din paragraful I.3.1., putem trage
concluzia suplimentară că short+ este subtip al lui byte şi integer+ este subtip al lui
word.
Să considerăm acum cazul numerelor întregi oarecare.
Pentru semnificaţia short se va utiliza o reprezentare în care l = 8 şi care
permite codificarea oricărui n ∈ Z , − 127 ≤ n ≤ 127 . Pentru construcţia codului
folosim codul ϕ′Z 1 definit mai sus.
Pentru semnificaţia integer se va utiliza o reprezentare în care l = 16 şi
care permite codificarea oricărui n ∈ Z , − 32767 ≤ n ≤ 32767 . Pentru
construcţia codului folosim codul ϕ′Z 2 definit mai sus.
Pentru semnificaţia long se va utiliza o reprezentare în care l = 32 şi care
permite codificarea oricărui n ∈ Z , − 2147483647 ≤ n ≤ 2147483647 . Pentru
construcţia codului folosim codul ϕ′Z 3 definit mai sus.
În general, după cum se poate observa, pentru lungimea l = 8 ⋅ 2 p , în
construcţia codului se pleacă de la codul ϕ′Zp , ceea ce ne va permite specificarea
globală a codurilor ϕ Zp pentru p = 1,2,3 . Codul pentru p fixat permite
codificarea oricărui n ∈ Z , − α ≤ n ≤ α , unde α = 2 l −1 − 1 . Considerăm
ϕ Zp : {− α,−α + 1,...,0,..., α} → E 2l , definită prin
ϕ Zp (a ) = scl − 2 ...c1c0 + s = sd l −2 ...d1 d 0 , unde + semnifică operaţia obişnuită de
adunare a numerelor aplicată pentru valori date în baza de numeraţie 2,
⎧0 pentru a ≥ 0 ⎧bk pentru a ≥ 0
s=⎨ , ck = ⎨ , k = 1,2,..., l − 2
⎩1 pentru a < 0 ⎩1 − bk pentru a < 0
şi 0bl − 2 ...b1b0 = ϕ′Zp ( a ) .care asociază fiecărei valori a ∈ {− α,−α + 1,...,0,..., α}
un unic şir de l cifre binare sd l − 2 ...d1 d 0 astfel încât

14
⎧ l −2
⎪∑ d k 2 pentru a ≥ 0
k

⎪ k =0
a=⎨ l −2
.
⎪2 l −1 − d 2 k pentru a < 0
⎪⎩ ∑
k =0
k

Reprezentarea prin codul ϕ Zp poartă numele de reprezentarea numerelor


întregi prin cod complementar (la 2) şi în această codificare s este definit ca bit
de semn.
Dacă ţinem cont de modul de definire a lui s , atunci putem scrie
l −2
proprietatea pentru sd l − 2 ...d1 d 0 sub forma a = 2 l −1 s + (− 1) ∑d
s
k 2k .
k =0

Din modul de definire a codurilor ϕ Zp rezultă imediat că short+ (integer+,


long+) este subtip pentru short (respectiv, integer, long).
Deoarece short+ (integer+) este subtip pentru byte (word) şi pentru short
(integer), prin aplicarea propoziţiei date în I.3. rezultă că ϕ Z 1 ( ϕ Z 2 ) şi ϕ N 1 ( ϕ N 2 )
sunt echivalente pe {0,...,127} (respectiv, {0,...,32767} ).
În definiţia codului ϕ Zp de mai sus, componenta de forma scl − 2 ...c1c0
este numită reprezentarea prin complement direct (sau reprezentare
complementară la 1).

I.3.3. Reprezentarea numerelor reale

În general, la reprezentarea numerelor raţionale (reale), în tripletul (a, l , t )


de caracterizare t are una din semnificaţiile single ( l = 32 ) sau double ( l = 64) ,
fără însă a exclude posibilitatea codificării şi pentru alte valori multiplu de 16
pentru l .
Similar cu obţinerea codurilor ϕ Zp plecând de la codurile ϕ′Zp , construcţia
codurilor ϕ R pentru reprezentarea numerelor reale se realizează plecând de la
codurile ϕ′E definite pentru numerele reale nenegative, obţinându-se o
reprezentare a numerelor reale prin cod complementar (la 2).
Facem de la început observaţia că dacă t1 este un tip real relativ la
lungimea l1 şi t 2 este un tip real relativ la lungimea l 2 şi pentru aceste tipuri de
definesc codurile ϕ R1 şi, respectiv, ϕ R 2 , atunci ϕ R1 şi ϕ R 2 sunt relativ
echivalente, oricare ar fi lungimile l1 şi l 2 .
În definirea unui cod ϕ R se foloseşte, de obicei, codul ϕ H : E16 → E 24 ,
numit codificare binară a valorilor hexazecimale, unde
E16 = {0,1,...,9, A, B, C, D, E, F} şi ϕ H definit prin tabelul:
15
y ϕH (y)
0 0000
1 0001
2 0010
3 0011
4 0100
5 0101
6 0110
7 0111
8 1000
9 1001
A 1010
B 1011
C 1100
D 1101
E 1110
F 1111

Pentru definirea codului se consideră o valoare c ∈ N (de cele mai multe


ori egală cu 63) şi codul ϕ Nc : {0,1,...,2c} → E 2e (în majoritatea cazurilor
considerându-se e = 7 ), definit drept cod pentru tipul numere naturale (similar cu
tipul short+).
l −1− e
Fie n = şi o funcţie, numită funcţie de conversie în baza de
4
numeraţie 16, ( )
f 16 : 16 − c ,16 c ∪ {0} → {− c,...,0,..., c}× (E16 − {0}) × E16n −1 ,
definită prin f (0) = (− c,0,...,0) şi f ( x ) = ( p, a1 , a 2 ,..., a n ) astfel încât
n
x = 16 p ∑ a k ⋅ 16 − k (deci pentru care, în plus, a1 ≠ 0 ), unde p se numeşte
k =1
n
caracteristică, iar m = ∑ a 16
k =1
k
−k
( 0 < m < 1 ) se numeşte mantisă.

Realizarea codului pentru numerele reale nenule


( )
ϕ′R + : 16 − c ,16 c ∪ {0} → E 2l poate fi formalizată complet prin utilizarea funcţiilor
de proiecţie, dar este destul de greoaie. Vom considera o formă mai uşor de înţeles,
chiar dacă aceasta nu este foarte riguroasă din punct de vedere matematic şi vom
defini codul ϕ′R + prin ϕ′R + (0 ) = 0,0,...,0 şi pentru x ≠ 0,
ϕ′R + (x ) = 0ϕ Nc ( p + c )ϕ H (a1 )ϕ H (a 2 )...ϕ H (a n ) .

16
I.4. Reprezentarea datelor nenumerice

Codurile de reprezentare pentru datele nenumerice sunt generate de regula


pe lungimi de 8 sau 16 biţi. În această categorie intră, în primul rând, informaţiile
pentru a căror exprimare se foloseşte limbajul uzual, numite informaţii
alfanumerice.
Datele alfanumerice corespund unei extensii a alfabetului limbii engleze,
care conţine, pe lângă litere (simple şi majuscule), cifrele sistemului de numeraţie
zecimal (0,…,9) şi totalitatea semnelor speciale care apar, în mod current, pe
tastaturile maşinilor de scris (şi, implicit, pe tastatura sistemului de calcul).
Codificarea acestor date se face prin intermediul unor tabele de
corespondenţă, în care fiecărui element i se pune în corespondenţă un cod cu
lungimea de 8 biţi (pentru un cod normal, permiţând codificarea a cel mult 256
caractere diferite) sau de 16 biţi (pentru un cod extins).
Există mai multe convenţii (standarde) de codificare, şi anume
♦ codificarea ASCII (American Standard Code for Information Interchange),
una din cele mai utilizate codificări pentru informaţia alfanumerică
♦ codificarea EBCDIC (Extended Binary Coded Decimal Interchange Code),
folosită, în special, pe calculatoarele IBM
Modalităţi similare de codificare se folosesc şi pentru codificarea altor
tipuri de informaţii, cum ar fi cele de culoare sau de sunet.

I.5. Elemente de memorare a informaţiei

Un alt aspect important relativ la reţinerea informaţiei pentru prelucrare,


prin intermediul tehnicii de calcul, este cel al modului în care se realizează
memorarea efectivă.
Un program de calculator este o colecţie de informaţii memorate şi de
procese de prelucrare aplicate colecţiilor de date. Se disting, astfel, drept
componente:
Definiţie. O parte a unui program care conţine informaţia memorată şi
care este supusă procesului de prelucrare se numeşte segment de date.
Definiţie. O parte a unui program care este formată din descrierea
proceselor de prelucrare se numeşte segment de cod sau segment de instrucţiuni.
Folosind definiţiile de mai sus, putem spune că un program este format
dintr-unul sau din mai multe segmente de date şi dintr-unul sau din mai multe
segmente de instrucţiuni.
La începutul dezvoltării sistemelor de calcul, segmentele de date şi de
instrucţiuni din cadrul unui program nu ocupau spaţii distincte din memoria internă
a unui sistem de calcul. Datorită dimensiunilor reduse pentru memoria
calculatoarelor, pentru limbajele de programare s-a impus o limitare a
dimensiunilor fizice pentru memoria utilizată, şi anume, aceasta era de maxim 64
kilobytes. Spaţiul de memorie era acoperit atât de informaţiile efective, cât şi de
instrucţiunile de prelucrare ale acestora.

17
Necesitatea de realizare a unor prelucrări complexe a condus la două linii
de dezvoltare, aplicate simultan în cadrul limbajelor de programare.
O direcţie a fost cea prin care s-a realizat o separare clară între zonele de
memorare a datelor şi cea de reţinere internă a instrucţiunilor de prelucrare. Acest
lucra a condus la o primă extindere a spaţiului fizic utilizat pentru programe şi,
folosind aceeaşi limitare asupra spaţiului, a permis trecerea la programe de maxim
128 kilobytes de memorie din care maxim 64 erau utilizaţi pentru memorarea
segmentului de date şi maxim 64 pentru segmentul de cod.
A doua direcţie a fost reprezentată de implementarea unei divizări asupra
prelucrărilor, prin împărţirea acestora în subprocese distincte. Acest mod de lucru a
condus la noţiunea de structurare a programelor. Programele sunt formate, astfel,
din mai multe segmente de cod, activitatea de prelucrare fiind coordonată prin
intermediul apelurilor acestor segmente dintr-un segment principal numit segment
rădăcină. Fiecare segment de cod poate avea ataşat propriul segment de informaţii
utile şi, în acelaşi timp, poate face acces la datele care sunt legate de segmentul
rădăcină.
Deoarece toate segmentele de cod pot accesa informaţiile cuprinse în
segmentul de date ataşat segmentului rădăcină spunem că se formează un segment
de date comune.
Observaţie. Orice segment care formează un program este limitat la o
dimensiune maximă de 64 kilobytes de memorie internă.
Aplicarea observaţiei de mai sus conduce la o limitare drastică asupra
dimensiunilor informaţiei care poate fi procesată cu ajutorul unui sistem de calcul.
Pentru a depăşi acest impas, sistemele de operare şi limbajele de programare au
fost înzestrate cu un sistem prin care să se poată realiza segmente de date care să
depăşească spaţiul definit al programelor. Apar astfel următoarele noţiuni.
Definiţie. Datele care se definesc odată cu instrucţiunile de prelucrare a
lor şi care sunt memorate în spaţiul program se numesc date statice.
Definiţie. Datele care nu se definesc odată cu instrucţiunile de prelucrare
a lor şi care sunt memorate intern în afara corpului programului se numesc date
dinamice.
Datele statice, asociate unui segment de cod, se caracterizează prin aceea
că ocupă în memoria internă un spaţiu continuu. Acest lucru nu mai este neapărat
necesar pentru datele dinamice.
Definiţie. Dacă o structură de informaţii, care formează date dinamice,
ocupă un spaţiu continuu de memorare, spunem că ele formează o structură
dinamică compactă.
Definiţie. Dacă o structură de informaţii dinamice este memorată intern
pe componente, fără ca acestea să ocupe un spaţiu continuu de memorie, spunem
că se formează o structură dinamică divizată.
Folosirea datelor dinamice elimină limitarea arbitrară a dimensiunii
spaţiului de memorie internă utilizat de un program. Singura limită, în acest caz,
este cea legată de dimensiunea fizică a spaţiului de memorie existent într-un sistem
de calcul în momentul execuţiei programului sau a unei anumite părţi din program.

18
II. TABLOURI
Acest capitol introduce modalităţile de memorare a mulţimilor de date
uniforme. Acestea sunt importante pentru că sunt datele utilizate frecvent de toate
programele de calculator.

II.1. Definirea tablourilor

Definiţie. Se numeşte tablou o mulţime de date de acelaşi tip la care


putem face referire globală printr-un singur nume, numele tabloului, la fiecare
dată din mulţime putând face şi o referire individuală printr-o combinaţie de
numere naturale, indicii elementului.
Din punct de vedere formal putem defini un tablou ca o aplicaţie
f : A1 × A2 × ... × At → A
unde A1 , A2 , …, At sunt mulţimi finite, reprezentând mulţimile valorilor indicilor
şi A este mulţimea valorilor fiecărui element al tabloului; t este nu număr natural
t ≥ 1 şi dă numărul de dimensiuni ale tabloului care are numele f .
Pentru t = 1 se obţine un tablou de dimensiune care poartă numele de
vector. Pentru t = 2 am definit un tablou de dimensiune 2 pe care îl numim
matrice. În general, spunem că avem un tablou t − dimensional.
Dacă pentru fiecare k = 1,2,..., t considerăm că Ak = n k şi
{ }
Ak = a1k , a 2k ,..., a nkk , atunci fiecare element al tabloului f este dat prin
(
f a i11 , a i22 ,..., aitt ) notat prescurtat prin f (i1 , i 2 ,..., it ) sau f i1i2 ...it . Mulţimea
tuturor valorilor aplicaţiei f , {f i1i2 ...it } are n1 ⋅ n 2 ⋅ ... ⋅ nt elemente şi se poate
reprezenta într-un spaţiu de dimensiune t printr-un tablou t − dimensional în care
valoarea f i i ...it se găseşte la intersecţia hiperplanelor perpendiculare pe axele de
12

coordonate, definite de indicii i1 , i 2 , …, it .


Se spune că indicarea unui element al unui tablou se face prin indiciere.
Pentru t = 1 , reprezentarea aplicaţiei f : I → A, I = n,
I = {p1 , p 2 ,..., p n } se poate face prin intermediul unui vector din A n ,
( f1 , f 2 ,..., f n ) .
Pentru t = 2 , fie f : B × C → A aplicaţia de definire a tabloului de
dimensiune 2 în care B = m şi C = n . Aplicaţia poate fi reprezentată printr-o
19
matrice în care elementul f ij se găseşte la intersecţia liniei i , cu coloana j , deci
sub forma unui element din & M & m×n ( A) ,
⎛ f11 f12f1n ⎞ "
⎜ ⎟
⎜ f 21 f 22
f 2n ⎟ "
⎜ # # # ⎟ %
⎜ ⎟
⎜ f m1 f mn ⎟⎠
f m2 "

Ca alternativă constructivă a matricelor t − dimensionale putem considera
următoarea definire. Fie g k : A2 × ... × At → A , g ik ...it = f ki ...it , k = 1,..., n1 n1
2 2

matrici (t −1) − dimensionale obţinute prin fixarea câte unei valori din A1 şi fie
{
B = g 1 ,..., g
n1
}. Atunci putem defini
h : A1 → B , hi = g i . În acest mod am
definit o matrice t − dimensională ca un vector de matrici (t −1) − dimensionale.
Acest mod de specificare a matricelor este cunoscut sub numele de liniarizare
linie.
Pentru t = 2 modul alternativ de definire reprezintă considerarea
vectorilor v1 , v 2 , …, v m din A n şi formarea matricii ca un vector al vectorilor
aleşi, deci matricea scrisă sub forma (v1 , v 2 ,..., v m ) din B m cu B = A n .
În mod similar, putem considera următoarea definire. Fie
g k : A1 × ... × At −1 → A , g ik ...i = f i ...i k , k = 1,..., nt
1 t −1 1 t −1

nt matrici (t −1) − dimensionale obţinute prin fixarea câte unei valori din At şi fie
{ }
B = g 1 ,..., g nt . Atunci putem defini h : At → B , hi = g i . În acest mod am
definit o matrice t − dimensională ca un vector de matrici (t −1) − dimensionale.
Acest mod de specificare a matricelor este cunoscut sub numele de liniarizare
coloană.
Pentru t = 2 modul alternativ de definire reprezintă considerarea
vectorilor v1 , v 2 , …, v n din A m şi formarea matricii ca un vector al vectorilor
aleşi, deci matricea scrisă sub forma (v1 , v 2 ,..., v n ) din B n cu B = A m . După cum
se poate observa, acest mod de scriere corespunde modului de prezentare a unei
matrice algebrice, scrierea matricei prin vectorii coloană care o compun.
Se poate demonstra imediat că cele trei definiţii sunt echivalente.

20
II.2. Implementarea statică a tablourilor unidimensionale

Implementarea tablourilor unidimensionale ca structuri statice trebuie să


ţină cont de necesitatea de acces cât mai rapid la informaţia memorată şi de
realizarea unor operaţii specifice pentru vectorii priviţi ca elemente matematice.
Datorită definirii globale, numelui vectorului trebuie să i se asocieze o
adresă unică de memorie, reprezentând prima locaţie în care se face memorarea
corespunzătoare primului element al vectorului. Memorarea elementelor vectorului
se realizeză într-un spaţiu continuu începând cu primul element. Dimensiunea
acestui spaţiu este dată de produsul între numărul de elemente definite pentru
vector şi dimensiunea unui anumit element. Acest spaţiu este rezervat integral în
spaţiul programului, indiferent de numărul elementelor utilizate efectiv din vector.
Definiţie. Numim rang al unui element dintr-un vector valoarea naturală
prin care se dă poziţia relativă a unui element al vectorului. Rangul primului
element al vectorului se consideră egal cu 0.
Dacă B = {b1 , b2 ,..., bn } este o mulţime finită de numere naturale date,
exact în această ordine, şi f : B → A este un vector, atunci notăm
rang( f (bi )) = i − 1 poziţia relativă în vector a valorii corespunzătoare valorii bi a
indicelui şi formula care exprimă valoarea rangului o numim formulă de rang.
Dacă a reprezintă numele vectorului şi notăm cu loc(a ) adresa fizică
asociată primului element al vectorului, atunci, prin utilizarea formulei de rang şi a
locaţiei fizice a primului element, putem determina poziţia fizică a oricărui element
din vector. Dacă valoarea indicelui unui element este b ∈ B , atunci poziţia fizică
în memorie a elementului de indice cu valoarea b este dată prin
loc( f (b )) = loc(a ) + l ⋅ rang( f (b )) ,
unde l reprezintă lungimea de memorare a fiecărui element din A .
Vectorii implementaţi static conduc la ocuparea unui spaţiu de memorare
fix care are dimensiunea dată de relaţia dim(a ) = l ⋅ B bytes, indiferent de partea
utilizată efectiv a acestui spaţiu.
Presupunând acum că într-un segment de program se definesc vectorii a1 ,
a 2 , …, a n şi alte componente de date care ocupă un spaţiu de p bytes, limitarea
dimensiunii unui segment din limbajele de programare impune îndeplinirea
condiţiei
n
p + ∑ dim(ai ) ≤ 64 ⋅ 210 .
i =1

21
II.3. Implementarea dinamică a tablourilor unidimensionale

Pentru realizarea implementării dinamice a vectorilor trebuie să se ia în


considerare respectarea modalităţii de acces la elementele vectorilor, care trebuie
să fie similară celei pentru accesul la elementele vectorilor implementaţi static.
Elementele care concură la implementarea dinamică a unui vector sunt:
rezervarea unei informaţii de tip adresă, prin care să putem determina adresa fizică
de început a elementelor vectorului, definirea noţiunii de vector prin care se face
organizarea spaţiului ca structură de tip tablou unidimensional şi spaţiul efectiv de
memorare a elementelor vectorului. Se formează astfel un triplet de caracterizare a
informaţiei de forma (a, t , s ) . Singurul element care influenţează dimensiunea
datelor statice ale unui program este componenta a a acestui triplet, pentru care,
de regulă, în limbajele de programare sunt necesari 4 bytes de memorie.
Implementarea dinamică oferă avantajul de a putea avea vectorul accesibil
doar pe perioada de execuţie în care prezenţa vectorului este necesară realizării
prelucrărilor specifice. Din acest motiv, implementarea trebuie să conţină trei faze
de lucru:
- înainte de primul acces la vector, prin instrumentele de gestiune a
memoriei din sistemul de operare, se obţine spaţiul de memorie s
necesar memorării elementelor acestuia (alocarea vectorului);
- realizarea prelucrărilor specifice asupra elementelor vectorului;
- după ce vectorul nu mai este necesar, tot prin instrumentele puse la
dispoziţie de sistemul de operare, se eliberează spaţiul s afectat
vectorului care a fost prelucrat (eliberarea vectorului). Operaţia se
realizeză prin transmiterea către sistemul de operare a valorii din a şi
a dimensiunii lui s .
Există două modalităţi de implementare dinamică a vectorilor prin structuri
compacte, în funcţie de modul de specificare a componentei t , şi anume
implementarea fixă şi implementarea variabilă.
Observaţie. În limbajele de programare, definirea generică a unui tip nu
produce alocarea de memorie pentru acel tip. Alocarea memoriei se face doar la
definirea unei variabile de acel tip sau la cererea de alocare dinamică pentru tipul
respectiv.

II.3.1. Vectori dinamici cu implementare fixă

În implementarea fixă componenta t este predefinită în cadrul


programului realizat. Această definire este asociată la nivelul programului cu
informaţia de adresă a (la nivelul unui limbaj de programare spunem că lucrăm cu
o variabilă de adresă cu tip asociat), singurul element rămas nedefinit fiind cel
corespunzător componentei s .
Putem extinde noţiunea de dimensiune a unui vector la dimensiunea unui
tip vector. Dacă presupunem că v este un vector definit static de tipul t , spunem

22
că tipul vector t are aceeaşi dimensiune cu dim(v ) . Prin abuz de limbaj putem
nota dimensiunea tipului vector t prin dim(t ) .
Alocarea vectorului se realizează, în acest caz, printr-un apel în care este
specificată doar informaţia de adresă, iar prelucrarea vectorului respectă aceleaşi
reguli ca la prelucrarea statică, deoarece organizarea spaţiului s este asigurată
automat de limbajul de programare utilizat.
Observaţie. Spaţiul s poate fi alocat oriunde în memoria internă a
sistemului de calcul.
Presupunem că într-un program se definesc, în implementare fixă, vectorii
dinamici a1 , a 2 , …, a n , cu tipurile respectiv t1 , t 2 , …, t n şi că restul datelor
statice ocupă p bytes. Dacă spaţiul de memorare a unei informaţii de adresă este
de 4 bytes, atunci segmentul de date al programului are dimensiunea p + 4n .
Dacă, în plus, considerăm că segmentul de cod are dimensiunea c , atunci
dimensiunea statică a programului este de
ds = c + p + 4n bytes
(acesta este şi spaţiul de memorie necesar pentru lansarea în execuţie al
programului). Valoarea ds reprezintă astfel şi dimensiunea minimă a spaţiului de
memorie folosit de programul în execuţie.
Presupunem că, la un moment dat ( α ) al execuţiei, se folosesc vectorii
1α 2α α
{ }
dinamici a i , a i , …, a im α , i1α , i 2 ε ,..., imα α ⊂ {1,2,..., n}. Atunci, în acest
moment al execuţiei, dimensiunea spaţiului de memorie utilizat de program este
dată de relaţia

( )

dd (α ) = c + p + 4n + ∑ dim t ikα
k =1
(şi ţinând cont de observaţia de mai sus, acest spaţiu nu este, în mod necesar,
continuu).
Să notăm cu start ( stop ) momentul de început (terminare) al
programului. Cum dimensiunea spaţiului de memorie utilizat de program depinde
de momentul execuţiei, are sens să determinăm dimensiunea maximă a spaţiului de
memorie utilizat de program. Avem astfel

∑ dim(t ).

dM = max dd (α ) = ds + max ikα


start ≤ α ≤ stop start ≤ α ≤ stop
k =1

23
II.3.2. Vectori dinamici cu implementare variabilă

Practica abordării problemelor reale în informatică demonstrază că, în


general, doar o parte a vectorilor definiţi în program este utilizată efectiv şi astfel,
atât la implementarea statică, cât şi la implementarea dinamică fixă, există spaţii de
memorie neutilizate, dar ocupate. Acest lucru poate fi eliminat la implementarea
dinamică prin alocarea vectorilor care să fie reduşi la dimensiunea utilizată efectiv
la momentul execuţiei.
La realizarea implementării se pleacă de la definirea generică a unui spaţiu
maximal care să acopere dimensiunea maximă a unui vector pentru un tip de dată
considerat. Acest spaţiu are dimensiunea calculată ca în paragraful anterior. Astfel,
dacă t este tipul asociat unui vector maximal pentru datele considerate, numărul
de octeţi necesari pentru spaţiul maximal definit generic va fi egal cu dim(t ) .
În implementarea variabilă, singura componentă definită într-un program
este cea relativă la adresa asociată unui vector ( a ). Componenta t este definită pe
parcursul execuţiei şi este o restricţie a lui t .
Astfel, dacă t este tipul unui vector v : {i1 , i2 ,..., in } → A cu n
componente şi dacă din acest vector sunt definite efectiv doar primele p
componente ( p ≤ n ), atunci putem scrie că t = t p , adică t este restricţia la
primele p componente a lui t , deci t este tipul unui vector
v : {i1 , i 2 ,..., i p } → A , cu vi = vi pentru orice i ∈ {i1 , i 2 ,..., i p }.
Realizarea efectivă a implementării impune alocarea unui spaţiu s , având
dimensiunea în bytes egală cu dim(t ) şi care poartă numele de buffer. Acest
spaţiu, de regulă, nu este un spaţiu accesibil direct elementelor vectorului, ci un
spaţiu cu aceeaşi dimensiune cu a vectorului şi peste care acesta se suprapune.
Comparativ cu alocarea statică şi cu cea dinamică fixă, accesul la un
element al vectorului nu se mai face prin specificarea directă a adresei de început a
elementului, ci prin procese de acces:
- în scriere, prin care trecerea valorii în spaţiul unei componente se face
cu descompunerea reprezentării în bytes componenţi;
- în citire, prin care valoarea din spaţiul unei componente se obţine prin
recompunenerea reprezentării plecând de la bytes componenţi.
Remarcăm faptul că acest mod de implementare a vectorilor, chiar dacă
produce unele complicaţii relative la programarea în sine, are avantajul de utilizare
strictă a spaţiului de memorare dinamic.

24
II.4. Implementarea statică a tablourilor multidimensionale

Datorită liniarităţii memoriei sistemelor de calcul, reprezentarea spaţială a


tablourilor multidimensionale nu este posibilă. Acelaşi motiv face ca
implementările tablourilor multidimensionale la nivelul limbajelor de programare
să se facă prin una din cele două forme alternative de specificare a tablourilor, prin
liniarizare.
De exemplu, limbaje de programare ca PASCAL (DELPHI) şi C++
(JAVA) folosesc implementarea tablourilor multidimensionale prin liniarizarea
linie, în timp ce limbaje ca FORTRAN (F) utilizează liniarizarea coloană.
Plecând de la modul de liniarizare, implementarea statică a tablourilor
multidimensionale are la bază implementarea statică a vectorilor, limbajele de
programare asigurând suprapunerea tabloului multidimensional peste un vector de
dimensiune corespunzătoare.
Dacă f : B1 × B2 × ... × B p → A este definirea unui tablou
p
p − dimensional, numărul total de elemente ale tabloului este egal cu ∏B
i =1
i =n,
astfel că, pentru realizarea implementării, numărul de elemente ale vectorului peste
care se face suprapunerea tabloului trebuie să fie egal cu n , deci f se suprapune
peste un vector v : {1,2,..., n} → A . Dimensiunea spaţiului alocat tabloului
multidimensional f va fi dată de formula
p
dim p ( f ) = l ⋅ ∏ Bi .
i =1
Definiţie. Numim rang al unui element dintr-un tablou multidimensional
f numărul natural care indică poziţia elementului considerat din f în vectorul
de liniarizare peste care se suprapune f . Rangul elementului pentru care, pentru
fiecare i , 1 ≤ i ≤ p , indicele din poziţia i este primul element al mulţimii Bi , are
valoarea egală cu zero.
Observaţie. Rangul elementului f i i ...i p depinde atât de valorile indicilor
12

i1 , i 2 , …, i p , cât şi de numerele n1 , n2 , …, n p , reprezentând respectiv numărul


de elemente ale mulţimilor B1 , B2 , …, B p .
Datorită observaţiei de mai sus, putem nota rangul elementului f i i
1 2 ...i p

prin rang n n
1 2 ...n p
(i , i ,..., i ). Deoarece pentru un tablou multidimensional dat
1 2 p f
valorile n1 , n 2 , …, n p reprezintă constante de definire, prin abuz de notaţie,
pentru rangul elementului f i1i2 ,...,i p vom utiliza notaţia prescurtată

25
rang(i1 , i2 ,..., i p ) (când acest lucru nu produce confuzie), sau rang p (i1 , i 2 ,..., i p )
(pentru o evidenţiere clară a numărului de dimensiuni ale tabloului).
Deoarece un vector este un tablou cu p = 1 , pentru rangul unui element al
vectorului definit mai sus vom putea utiliza şi notaţia rang1 (b ) în loc de
rang( f (b )) .
Formula prin care se obţine rangul unui element se va numi formulă de
rang.
Determinarea formulei de rang pentru un tablou multidimensional ţine cont
atât de definirea tabloului, cât şi de modul de liniarizare a acestuia.
Să considerăm liniarizarea prin linii. Amintim că pentru un vector
v : B → A , B = {b1 , b2 ,..., bk } pentru orice b ∈ B , rang(b ) = i − 1 dacă şi numai
dacă b = bi .
Fie acum {
B1 = b11 , b21 ,..., bn11 , } {
B2 = b12 , b22 ,..., bn22 } şi tabloul
bidimensional f : B1 × B2 → A . Folosind modul de definire alternativ de
liniarizare prin linii, tabloul bidimensional poate fi dat ca

1
n i
1
( ) ( ( ) ( )
i
1 2
i
v d : B1 → A 2 , v d bi1 = v d1 b12 , v d1 b22 ,..., v d1 bn2 ,
2 2
( ))2
i1
cu v d2 : B2 → A . Conform acestei exprimări pentru tabloul bidimensional f se
poate exprima sub forma
(( )( ) ( ))
f = f 11 , f 12 ,..., f 1n2 , f 21 , f 22 ,..., f 2 n2 ,..., f n11 , f n1 2 ,..., f n1n2 ,
formă în care parantezele interioare pot fi ignorate şi, astfel, f poate fi echivalent
cu un vector g : B → A cu B = n1 ⋅ n2 , cu elementele lui B obţinute printr-o
( 1 2
)
corespondenţă bi1 , bi2 6 bi pentru care se consideră suplimentar că elementele
k
lui B sunt date într-o ordine care corespunde ordinii lexicografice pentru perechile
din B1 × B2 .
Datorită celor prezentate, implementarea lui f va fi echivalentă cu
implementarea lui g , şi astfel rangul unui element în implementarea tabloului prin
f va fi egal cu rangul elementului corespunzător din implementarea tabloului prin
g.
Din scrierea lui f în forma alternativă de mai sus se observă că trecerea
de la un element la altul al vectorului v d corespunde în g unui salt corespunzător
1

dimensiunii lui B2 şi astfel putem scrie că


( ) ( )
rang 2 b1 , b 2 = B2 rang1 b1 + rang1 b 2 , ( )
deci
( ) ( )
rang 2 b1 , b 2 = n 2 rang1 b1 + rang1 b 2 . ( )
26
Să generalizăm rezultatul obţinut mai sus.
Fie tabloul p − dimensional f : B1 × B2 × ... × B p → A . Scrierea acest
tablou prin liniarizare linii. Astfel, fie
g i : B2 × ... × B p → A , g i (i2 ,..., i p ) = f (i, i 2 ,..., i p )
pentru orice i ∈ B1 . Este evident faptul că g i ∈ A k , unde k = n2 ⋅ ... ⋅ n p . Fie
acum
(
h : B1 → A k , h = g i1 , g i2 ,..., g in .
1
)
Avem că f şi h sunt echivalente.
Rezultă că putem considera m : B1 × C → A , unde C = B2 × ... × B p .
Folosind formula de rang obţinută pentru un tablou bidimensional, avem
( )
rang 2 b1 , c = C rang1 b1 + rang1 (c ) . ( )
Ţinând cont de modul de definire a elementelor şi de echivalenţa
definiţiilor, această relaţie se scrie
( ) ( ) (
rang p b1 , b 2 ,..., b p = n2 ...n p rang1 b1 + rang p −1 b 2 ,..., b p . )
Prin înlocuiri repetate se obţine
( ) ( ) ( )
rang p b1 , b 2 ,..., b p = n2 ...n p rang1 b1 + n3 ...n p rang 1 b 2 + ... + rang1 b p ( )
care se poate scrie prescurtat prin
⎛ p ⎞
( ) ( )
p
rang lp b1 , b 2 ,..., b p = ∑ ⎜⎜ ∏ n j ⎟⎟ rang1 b i ,
i =1 ⎝ j = i +1 ⎠
unde indicele superior l prezent la funcţia rang indică liniarizarea prin linie.
Considerând adresa de regăsire în memorie a unui element al tabloului
pentru care adresa de memorare a primului element este a , avem relaţia
( ( )) (
loc l f b1 , b 2 ,..., b p = a + x ⋅ rang lp b1 , b 2 ,..., b p , )
unde x este dimensiunea de memorare a unui element din tablou şi l indică
liniarizarea linie.
După cum se poate observa, pentru implementarea tablourilor statice
multidimensionale prin liniarizare linie este necesară cunoaşterea tuturor
dimensiunilor domeniilor de indici, cu excepţia primului.
Pentru liniarizarea coloană, printr-un raţionament similar celui de mai sus
obţinem pentru funcţia de rang exprimarea
⎛ i −1 ⎞
( ) ( )
p
rang cp b1 , b 2 ,..., b p = ∑ ⎜⎜ ∏ n j ⎟⎟ rang1 b i
i =1 ⎝ j =1 ⎠
cu o formulă corespunzătoare pentru determinarea adresei de regăsire în memorie a
unui element al tabloului.

27
În implementarea statică a unui tablou f , dimensiunea totală a spaţiului
ocupat de acesta are o valoare constantă dată prin
p
dim( f ) = x∏ ni .
i =1
Presupunând acum că într-un segment de program se definesc tablourile
a1 , a 2 , …, a n (unidimensionale sau multidimensionale) şi alte componente de
date care ocupă un spaţiu de y bytes, limitarea dimensiunii unui segment din
limbajele de programare impune îndeplinirea condiţiei
n
y + ∑ dim(a i ) ≤ 64 ⋅ 210 .
i =1
Această condiţie restrânge foarte mult posibilităţile de definire a matricelor
utilizate în cadrul unui program.
Observaţie. În practica realizării programelor, dimensiunea efectivă a
tablorilor este, de regulă, mai mică decât cea de definire a acestora. În acest
context, o mare parte a spaţiului alocat unui tablou nu este utilizat. Datorită
modului de specificare a tablourilor şi a aplicării formulelor de rang, spaţiile
neutilizate se găsesc în memorie în zone interioare ale tabloului, în finalul fiecărei
componente liniare.
De exemplu, pentru o matrice (tablou bidimensional) f : B × C → A ,
definit pentru dimensiunile m = B şi n = C (matrice cu m linii şi n coloane),
considerată în implementare cu liniarizare linie, dacă valorile efective pentru care
matricea se foloseşte sunt p ≤ m linii şi q ≤ n coloane, atunci în finalul fiecărei
linii rezervate matricei se găsesc n − q elemente nefolosite (deci un spaţiu
neutilizat de x(n − q ) byte). În plus, în finalul zonei rezervate în memorie pentru
matrice nu se folosesc m − p linii, fiecare ocupând xn bytes. Astfel, numărul
total de bytes nefolosiţi este egal cu
x[ p(n − q ) + (m − p )n] bytes.

II.5. Implementarea dinamică a tablourilor multidimensionale

Pentru realizarea implementării dinamice a tablourilor trebuie să se ia în


considerare respectarea modalităţii de acces la elementele acestora, care trebuie să
fie similară celei pentru accesul la elementele tablourilor implementate static.
Elementele care concură la implementarea dinamică a unui tablou sunt:
rezervarea unei informaţii de tip adresă, prin care să putem determina adresa fizică
de început a elementelor tabloului, definirea noţiunii de tablou prin care se face
organizarea spaţiului ca structură de tip tablou multidimensional, spaţiul efectiv de
memorare a elementelor tabloului şi specificarea formulei de rang utilizate în
regăsirea informaţiei. Se formează astfel un sistem de patru elemente de
caracterizare a informaţiei de forma (a, t , s, rang ) . Singurul element care
28
influenţează dimensiunea datelor statice ale unui program este componenta a a
acestui sistem, pentru care, de regulă, în limbajele de programare sunt necesari 4
bytes de memorie.
Implementarea dinamică oferă avantajul de a putea avea tabloul accesibil
doar pe perioada de execuţie în care prezenţa lui este necesară realizării
prelucrărilor specifice. Din acest motiv, implementarea trebuie să conţină trei faze
de lucru:
- înainte de primul acces la tablou, prin instrumentele de gestiune a
memoriei din sistemul de operare, se obţine spaţiul de memorie s
necesar memorării elementelor acestuia (alocarea tabloului);
- realizarea prelucrărilor specifice asupra elementelor tabloului;
- după ce tabloul nu mai este necesar, tot prin instrumentele puse la
dispoziţie de sistemul de operare, se eliberează spaţiul s afectat
tabloului care a fost prelucrat (eliberarea tabloului). Operaţia se
realizeză prin transmiterea către sistemul de operare a valorii din a şi
a dimensiunii lui s .
Există mai multe modalităţi de implementare dinamică a tablourilor prin
structuri compacte, în funcţie de modul de specificare a componentei t . Dintre
acestea, pentru memorarea integrală a tablourilor se folosesc implementarea fixă şi
implementarea variabilă.

II.5.1. Tablouri dinamice cu implementare fixă

În cadrul acestui tip de implementare dinamică a tablourilor


multidimensionale, componenta de tip, t , poate fi predefinită, realizându-se o
asociere directă a tipului la componenta a din sistemul de caracterizare a
tabloului.
Suplimentar, lucrul normal pe un limbaj de programare permite utilizarea
directă a unei formule de rang implicite, conforme cu modul de liniarizare adoptat
pentru implementarea statică a tablourilor. Accesul la informaţie se realizează ca la
implementarea statică prin specificarea directă a configuraţiei de indici prin care se
face calculul adresei de regăsire a informaţiei.
Fixarea componentelor a , t şi rang din sistemul de caracterizare a
tabloului face ca doar componenta s să fie cea care are un comportament strict
dinamic.
Această variantă de implementare dinamică respectă, din punctul de vedere
al ocupării memoriei, consideraţiile prezentate la implementarea dinamică fixă a
vectorilor dinamici. De asemenea, rămâne valabilă observaţia relativă la spaţiul
nefolosit, dată la implementarea statică a tablourilor.
Ca alternativă la implementarea cu predefinirea componentei t , există şi
posibilitatea ca definirea tipului să corespundă definiţiei unui vector. Acest lucru
este posibil datorită liniarizării care trebuie aplicată pentru memorarea tabloului,
pentru care, în acest caz, alegerea nu mai este una implicită, legată de limbajul de
programare, ci reprezintă o alegere a realizatorului de programe. Datorită acestui
29
aspect, o astfel de implementare impune specificarea explicită a formulei de rang
aplicată la regăsirea informaţiei din tablou.
Indicarea directă a formulei de rang are avantajul că în formarea acesteia
putem utiliza direct dimensiunile efective în locul dimensiunilor de definire a
tipului tablou. Acest lucru permite ca pentru aceeaşi definire de vector de memorie
să putem aplica mai multe definiţii de tablouri, condiţia care trebuie îndeplinită
fiind aceea de a avea produsul dimensiunilor efective mai mic sau egal cu numărul
de componente vectoriale definite.
Un avantaj al implementării tablourilor prin vectorii de liniarizare este de
grupare a spaţiului neutilizat în finalul tablourilor, acest lucru fiind important
pentru cazul trecerii la o implementare dinamică variabilă.
Un dezavantaj al implementării prin vectorii de liniarizare este de
specificare explicită a formulei de rang, deoarece acest lucru face ca pentru accesul
la un element al tabloului să avem două etape:
- calculul explicit al formulei de rang pentru determinarea elementului
corespondent în vectorul de liniarizare
- accesul efectiv la elementul dorit
Dezavantajul constă, în special, în faptul că procesele de prelucrare devin
mult mai laborioase.

II.5.2. Tablouri dinamice cu implementare variabilă

Din analiza modului de implementare fixă a tablourilor dinamice, a


rezultat că varianta de implementare prin vectorii de liniarizare este cea prin care
spaţiul neutilizat pentru un tablou efectiv dintr-un tablou definit este plasat în
finalul zonei de memorie care se alocă tablourilor.
Prin restricţionarea vectorului prin care se face implementarea la
dimensiunea strict necesară memorării tabloului (vezi vectorii dinamici cu
implementare variabilă) se obţine un tablou dinamic cu implementare variabilă.
Acum se aplică atât cele prezentate la vectorii dinamici cu implementare
variabilă, cât şi cele prezentate mai sus privind tablourile dinamice cu
implementare fixă în varianta de memorare prin vectorul de liniarizare.
Astfel,
- pentru asigurarea operaţiei de scriere în memorie a valorii unui
element, acesta se descompune în bytes componenţi, se aplică formula
de rang conformă modului de liniarizare ales, se calculează locaţia
primului byte al zonei de memorie afectate elementului şi se depun
bytes componenţi în memorie începând cu locaţia calculată.
- Pentru asigurarea operaţiei de citire din memorie a valorii unui
element, se aplică formula de rang, se calculează locaţia primului byte,
se transferă de la această adresă un număr de bytes corespunzători
tipului de informaţie şi se recompun pentru formarea valorii dorite.
Chiar dacă procesele descrise mai sus fac ca implementarea tablourilor
dinamice variabile să pară dificilă, ea are avantajul de a elimina complet alocarea
de spaţii de memorie fără ca acestea să fie folosite efectiv.
30
II.6. Implementarea matricelor triunghiulare
Matricele triunghiulare sunt matrici cu o formă specială în care un număr
foarte mare de elemente din matrice sunt egale cu zero şi se poate evita memorarea
acestor elemente. Acest fapt conduce la forme speciale de memorare care au la
bază implementarea dinamică a vectorilor.
În continuare vom considera matricele pătrate, care sunt definite pe
{1,2,..., n}× {1,2,..., n}. Facem acest lucru pentru simplificarea expunerii. În
general, prezentarea făcută este valabilă şi pentru matricile definite pe B × C
pentru care B = C = n
În funcţie de locul de plasare a zerourilor, matricele triunghiulare sunt
superioare şi inferioare. Din punct de vedere matematic aceste matrici au
următoarele definiţii:
Definiţie. O matrice pătrată A , de dimensiune n , definită astfel încât
pentru orice i < j , 1 ≤ i ≤ n , 1 ≤ j ≤ n , a ij = 0 se numeşte matrice superior
triunghiulară. Analog, o matrice pătrată B , de dimensiune n , definită astfel
încât pentru orice i > j , 1 ≤ i ≤ n , 1 ≤ j ≤ n , a ij = 0 se numeşte matrice
inferior triunghiulară.
Propoziţie. Într-o matrice superior sau inferior triunghiulară numărul
n(n − 1)
coeficienţilor egali cu zero este mai mare sau egal cu .
2
Observaţie. Fie A o matrice superior triunghiulară. Dacă pentru orice i şi
j , 1 ≤ i ≤ n , 1 ≤ j ≤ n , i ≥ j , a ij ≠ 0 , atunci numărul de coeficienţi zero din
n(n − 1)
matrice este exact . Un rezultat similar are loc pentru matricele inferior
2
triunghiulare.
Datorită observaţiei făcute, pentru reducerea spaţiului de memorare a unei
matrice superior sau inferior triunghiulare, matricea se poate defini doar prin
elementele diferite de zero, şi astfel funcţia de definire va fi
f : {1,2,..., n}× {1,2,..., n} → A , dată prin
⎧ f ij pentru i ≥ j
f (i, j ) = ⎨
⎩0 pentru i < j
pentru matricea superior triunghiulară, respectiv
⎧ f ij pentru i ≤ j
f (i, j ) = ⎨
⎩0 pentru i > j
în cazul matricea inferior triunghiulare.

31
Pentru a realiza o implementare a acestei matrice se poate folosi varianta
utilizării unui vector de liniarizare de dimensiune
n(n − 1) n(n + 1)
m = n2 − = .
2 2
În acest vector se reţin doar coeficienţii matricei care pot fi nenuli.
Modul de implementare a vectorului de liniarizare va determina şi modul
de implementare a matricei superior sau inferior triunghiulare. Determinarea
valorii pentru un element al matricei se face pe baza formulei de definire a matricei
date mai sus.
Să considerăm cazul matricei superior triunghiulare şi modalitatea de
liniarizare linie.
Matricea de coeficienţi f ij = f (i, j ) , 1 ≤ j ≤ n , 1 ≤ i ≤ n , redusă la
valorile pentru care i ≤ j ≤ n , este memorată prin vectorul v(k ) care conţine
(f,..., f 1n , f 22 ,.., f 2 n ,..., f n−1, n−1 , f n −1,n , f nn ) .
11

Pentru o valoare de linie i considerată sunt memorate n coeficienţi din prima


linie, n − 1 coeficienţi din a doua linie, …, n + 2 − i coeficienţi din linia i − 1 ,
deci un total de
n + (n − 1)+, , ,+ (n + 2 − i ) = (i − 1)(n + 2 − i ) + (0 + 1 + ... + i − 2 ) =

= (i − 1)(n + 2 − i ) +
(i − 2)(i − 1) =
2
=
(i − 1)(2n + 2 − i )
2
de elemente, astfel că pentru regăsirea unui coeficient al matricei f ij cu i ≤ j ≤ n
şi 1 ≤ i ≤ n , formula de rang este
(i − 1)(2n + 2 − i ) +
l (i , j ) =
rang sup j −1,
2
unde prin l am indicat liniarizarea linie, iar prin sup faptul că matricea este
superior triunghiulară.
Prin raţionamente similare se obţin funcţiile de rang pentru:
- o matrice superior triunghiulară liniarizată coloană
j ( j − 1)
c (i , j ) =
rang sup + i −1;
2
- o matrice inferior triunghiulară liniarizată linie
i (i − 1)
l (i , j ) =
rang inf + j −1;
2
- o matrice inferior triunghiulară liniarizată coloană
( j − 1)(2n + 2 − j ) + i − 1
c (i , j ) =
rang inf
2

32
unde prin c am indicat liniarizarea coloană, iar prin inf faptul că matricea este
inferior triunghiulară.
Pentru implementarea unei matrice inferior sau superior triunghiulare este
necesar să dispunem de spaţiul unui vector de dimensiune corespunzătoare şi de
implementarea formulei de rang pentru accesul la valoarea unui element al
matricii, valoarea urmând a fi obţinută prin îndeplinirea condiţiilor de definire a
acestor matrice.
Variantele posibile de implementare corespund modului de implementare
pentru vectori şi astfel, vom avea:
- implementare statică,
- implementare dinamică fixă,
- implementare dinamică variabilă.

II.7. Implementarea matricelor simetrice

Un alt caz special de matrice este matricea simetrică.


Definiţie. Fie f : {1,2,..., n}× {1,2,..., n} → B o matrice pătrată de ordin
n . Dacă pentru orice i ≠ j , 1 ≤ i ≤ n , 1 ≤ j ≤ n , f (i , j ) = f ( j , i ) , spunem că
matricea f este simetrică.
Definiţie. Fie f : {1,2,...n}× {1,2,..., n} → B o matrice pătrată de ordin
n . Matricea pătrată g : {1,2,..., n}× {1,2,..., n} → B , definită prin
⎧ f (i, j ) pentru i ≤ j
g (i, j ) = ⎨
⎩0 pentru i > j
se numeşte componentă inferior triunghiulară a matricei f .
Datorită modului de definire, cunoaşterea coeficienţilor matricei care
formează componenta inferior triunghiulară a matricei este suficientă pentru
definirea completă a acesteia. Acest lucru permite ca implementarea matricelor
simetrice să se realizeze în acelaşi mod în care s-a făcut implementarea matricelor
n(n + 1)
inferior triunghiulare, deci printr-un vector cu m = elemente.
2
Fie g o implementare de forma corespunzătoare unei matrice inferior
triunghiulare, conform celor prezentate în paragraful anterior. Atunci, matricea
simetrică în care g este componenta inferior triunghiulară este definită prin relaţia
⎧ g (i, j ) pentru i ≤ j
f (i, j ) = ⎨ .
⎩ g ( j , i ) pentru i > j
O implementare a unei matrici simetrice se poate face exact în acelaşi mod
în care s-a prezentat anterior implementarea matricelor inferior triunghiulare.
O variantă de implementare este cea în care se redefineşte formula de rang,
astfel încât pentru valorile i şi j ale indicilor să se obţină aceeaşi valoare de
indice pentru vectorul de liniarizare a matricei. Acest lucru are ca efect eliminarea
33
aplicării relaţiei de mai sus relative la corespondenţa între matricea simetrică şi
componenta sa inferior triunghiulară. Se obţine formula de rang pentru
- liniarizarea linie
⎧ i (i − 1)
⎪ + j − 1 pentru i ≤ j
⎪ 2
rang l (i, j ) = ⎨
sim
;
⎪ j ( j − 1) + i − 1 pentru i > j
⎪⎩ 2
- liniarizarea coloană
⎧ ( j − 1)(2n + 2 − j )
⎪ + i − 1 pentru i ≤ j
⎪ 2
rang c (i, j ) = ⎨
sim

⎪ (i − 1)(2n + 2 − i ) + j − 1 pentru i > j


⎪⎩ 2

II.8. Implementarea matricelor bandă

II.8.1. Matrice p − diagonală

Definiţie. Fie 1 ≤ k << n (unde << are sensul de mult mai mic decât) şi
f : {1,2,..., n}× {1,2,..., n} → A o matrice pătrată. Spunem că matricea A este o
matrice (2k −1) − diagonală, dacă elementele nenule ale matricei sunt elementele
f ij pentru care i − j ≤ k − 1 .
Datorită modului de definire a matricelor (2k −1) − diagonale, rezultă că
numărul de elemente diferite de zero din aceste matrice este egal cu
m = n + 2(n − 1) + ... + 2(n + 1 − k ) = n(2k − 1) − k (k − 1) .
Deoarece formulele de rang sunt complicate în cazul general,
implementarea matricelor diagonale este indicată atunci când reducerea spaţiului
este semnificativă, să spunem de peste 50% din spaţiul ocupat de matricea
m 1
completă (deci ≤ ).
n2 2
De exemplu, dacă k = 2 , implementarea matricelor 3 − diagonale este
justificată pentru n ≥ 6 , iar pentru k = 3 , implementarea matricelor 5 − diagonale
este benefică pentru n ≥ 9 .
Pentru cazul k = 1 , matricea diagonală obţinută are elemente nenule doar
pe diagonala sa principală, iar implementarea se realizează printr-un vector v de
n componente, având
⎧v(i ) pentru i = j
f (i, j ) = ⎨ .
⎩0 pentru i ≠ j
34
Pentru evidenţierea elementelor zero, definirea matricei se poate scrie sub
forma
⎧⎪ f ij pentru i − j ≤ k − 1
f (i, j ) = ⎨ .
⎪⎩0 pentru i − j > k − 1
Pentru implementarea unei astfel de matrice este necesar un vector de
dimensiune m şi de specificarea unei formule de rang corespunzătoare, modul de
lucru fiind similar cu cel prezentat la implementarea matricelor superior sau
inferior triunghiulare.
Fie k = 2 şi astfel m = 3n − 2 . Să considerăm că implementarea matricei
3 − diagonale foloseşte liniarizarea linie. Un element nenul aflat în prima linie are
rangul egal cu cel din vectorul de liniarizare şi astfel
rang l3− diag (1, j ) = j − 1 .
Pentru memorarea elementelor din primele n − 1 linii se utilizează m − 2 = 3n − 4
elemente şi astfel un element nenul aflat în ultima linie se regăseşte în vectorul de
liniarizare în poziţia
rang l3− diag (n, j ) = 2n + j − 3 .
Fie acum un element nenul din linia i , 2 ≤ i ≤ n − 1 . În prima linie sunt 2
elemente nenule, iar în celelalte i − 2 linii anterioare sunt câte 3 elemente nenule,
astfel că numărul total de elemente memorate în vectorul de liniarizare înaintea
primului element nenul din linia i este 3i − 4 . Cum i − j ≤ 1 , rezultă
rang l3− diag (i, j ) = 2i + j − 3 .
Observăm că formula generală este verificată şi pentru valorile i = 1 şi i = n .
Rezumând, am obţinut formula de rang
rang l3− diag (i, j ) = 2i + j − 3
definită pentru orice 1 ≤ i ≤ n , 1 ≤ j ≤ n pentru care i − j ≤ 1 .
Printr-un raţionament similar, pentru liniarizarea coloană, formula de rang,
definită pentru orice 1 ≤ i ≤ n , 1 ≤ j ≤ n pentru care i − j ≤ 1 , este
rang 3c − diag (i, j ) = i + 2 j − 3
Fie k = 3 , deci m = 5n − 6 . Să considerăm implementarea linie a
matricei 5 − diagonale.Un element aflat în prima linie a matricei are rangul egal cu
cel din vectorul de liniarizare, deci
rang l5− diag (1, j ) = j − 1 .
Numărul de elemente nenule din prima linie este 3 şi astfel primul element din a
două linie ocupă în vectorul de liniarizare poziţia relativă 3, restul elementelor
având rangul dat prin
rang l5− diag (2, j ) = 2 + j

35
Pentru memorarea valorilor din primele n − 2 linii sunt folosite m − 7 = 5n − 13
elemente şi cum primul coeficient nenul din linia n − 1 are indicele de coloană
n − 3 , rezultă că rangul unui element din linia n − 1 este dat prin
rang 5l−diag (n − 1, j ) = 4n + j − 10
Pentru memorarea valorilor din primele n − 1 linii sunt folosite m − 3 = 5n − 9
elemente şi cum primul coeficient nenul din linia n are indicele de coloană n − 2 ,
rezultă că rangul unui element din linia n este dat prin
rang l5− diag (n, j ) = 4n + j − 7 .
Fie acum un element nenul din linia i , 3 ≤ i ≤ n − 2 . Numărul de valori memorate
din primele 2 linii este 7 şi în fiecare linie k , 3 ≤ k ≤ i − 1 , se reţin câte 5 valori.
Astfel, numărul de elemente nenule din primele i − 1 linii este egal cu 5i − 8 .
Primul coeficient nenul din linia i are indicele de coloană i − 2 . Ca urmare,
pentru orice i , 3 ≤ i ≤ n − 2 şi j , 1 ≤ j ≤ n , astfel încât i − j ≤ 2 , obţinem
rang l5− diag (i, j ) = 4i + j − 6
Observăm că ultima relaţie este verificată şi pentru valorile i = 2 şi i = n − 1 , dar
nu este verificată pentru i = 1 şi i = n . Putem rezuma că pentru orice 1 ≤ i ≤ n şi
1 ≤ j ≤ n pentru care i − j ≤ 2 , funcţia rang este definită prin
⎧ j −1 pentru i = 1

rang 5 − diag
l (i, j ) = ⎨4i + j − 6 pentru 2 ≤ i ≤ n − 1
⎪4n + j − 7 pentru i = n

Printr-un raţionament similar, pentru liniarizarea coloană formula de rang
definită pentru orice 1 ≤ i ≤ n , 1 ≤ j ≤ n pentru care i − j ≤ 2 este
⎧i − 1 pentru j = 1

rang 5 − diag
c (i, j ) = ⎨4 j + i − 6 pentru 2 ≤ j ≤ n − 1
⎪4n + i − 7 pentru j = n

Exemplele de mai sus ilustrează modalitatea de obţinere a formulelor de
rang pentru matricile p − diagonale, pentru care însă nu vom da şi o formulă
generală pentru formula de rang, deoarece aceasta are o formă complicată, având
p − 2 ramuri de definire.

II.8.2. Matrice triunghiulară k − diagonală

Definiţie. Fie f : {1,2,..., n}× {1,2,..., n} → A o matrice pătrată k << n .


Spunem că f este o matrice superior k − diagonală dacă f (i, j ) ≠ 0 pentru
1 ≤ i ≤ n şi i ≤ j ≤ min (i + k − 1, n ) şi f (i, j ) = 0 în rest.

36
Observaţie. O matrice f este triunghiular k − diagonală dacă f este
superior triughiulară şi f este (2k −1) − diagonală.
Similar este definită matricea inferior t k − diagonală.
Observaţie. O matrice 1 − diagonală este, în acelaşi timp, o matrice
superior 1 − diagonală şi o matrice inferior 1 − diagonală. O matrice superior
(inferior) triunghiulară este, în acelaşi timp, o matrice superior (inferior)
n − diagonală.
Numărul de elemente nenule dintr-o matrice superior/inferior
k (2n − k + 1)
k − diagonală este m = .
2
Din modul de definire a matricelor superior (inferior) k − diagonale,
rezultă că modul de implementare a lor în liniarizare linie sau în liniarizare coloană
este o combinaţie între implementarea matricelor superior (inferior) triunghiulare şi
cea a matricelor diagonale.
Să considerăm că f este o matrice superior k − diagonală, liniarizată
linie. Să obţinem formula de rang. În primele n − k + 1 linii numărul de elemente
nenule este k , astfel că implementarea matricei este echivalentă cu implementarea
unei matrice având k coloane. Rezultă că pentru orice 1 ≤ i ≤ n + 1 − k şi
i ≤ j ≤ i + 1 − k avem
rang sup l
− k − diag
(i, j ) = k (i − 1) + j − i = (k − 1)(i − 1) + j − 1 ,
formulă care rămâne valabilă şi pentru linia i = n − k + 2 şi pentru orice
n − k + 2 ≤ j ≤ n , linia conţinând k − 1 valori nenule.
Fie n − k + 3 ≤ i ≤ n . În linia n − k + 2 avem k − 1 valori nenule, în linia
n − k + 3 sunt k − 2 elemente nenule, şi aşa mai departe, în linia i − 1 avem
n + 2 − i coeficienţi nenuli, astfel că în primele i − 1 linii din matrice avem
k (n + 1 − k ) +
(i + k − n − 2)(n + 1 − k − i )
2
valori nenule. Atunci, pentru i ≤ j ≤ n ,

rang sup
l
− k − diag
(i, j ) = k (n + 1 − k ) + (i + k − n − 2)(n + 1 + k − i ) + j − i − 1 .
2
Formula de rang rezultată este
⎧(k − 1)(i − 1) + j − 1 pentru 1 ≤ i ≤ n − k + 2

rang sup
l
− k − diag
(i, j ) = ⎪⎨ (i + k − n − 2)(n + 1 + k − i ) + pentru n − k + 3 ≤ i ≤ n
⎪ 2
⎪⎩ + k (n + 1 − k) + j − i −1
Prin raţionamente similare se obţine formula de rang pentru
- matrice superior k − diagonală liniarizată coloană

37
⎧ j ( j − 1)
⎪ + i −1 pentru 1 ≤ j ≤ k
⎪ 2
rang sup − k − diag
(i, j ) = ⎨
⎪ k (k − 1) + (k − 1)( j − k + 1) + i pentru k + 1 ≤ j ≤ n
c

⎪⎩ 2

- matrice inferior k − diagonală liniarizată linie


⎧ i (i − 1)
⎪ + j −1 pentru 1 ≤ i ≤ k
⎪ 2
sup − k − diag
rang c (i, j ) = ⎨
⎪ k (k − 1) + (k − 1)(i − k + 1) + j pentru k + 1 ≤ i ≤ n
⎪⎩ 2
- matrice inferior k − diagonală liniarizată coloană
⎧(k − 1)( j − 1) + i − 1 pentru 1 ≤ j ≤ n − k + 2

sup − k − diag
rang c (i, j ) = ⎪⎨ ( j + k − n − 2)(n + 1 + k − j ) + pentru n − k + 3 ≤ j ≤ n
⎪ 2
⎪⎩ + k (n + 1 − k ) + i − j − 1

O alternativă pentru implementarea matricelor superior sau inferior


k − diagonale este reprezentată de o liniarizare diagonală. Această liniarizare
impune ca vectorul de liniarizare să fie format din diagonala principală a matricei
şi fiecare paralelă la această diagonală, având aceeaşi implementare atât pentru o
matrice superior triunghulară, cât şi pentru o matrice inferior triunghiulară.
De exemplu, o matrice superior 2 − diagonală f este implementată
printr-un vector de liniarizare diagonală de forma
( )
v = f 11 , f 22 ,..., f nn , f 12 , f 23 ,..., f n −1,n ,
iar o matrice inferior 2 − diagonală este implementată printr-un vector de
liniarizare
(
v = f 11 , f 22 ,..., f nn , f 21 , f 32 ,..., f n ,n −1 . )
Formula de rang pentru matricele superior k − diagonale pentru acest tip
de liniarizare este dată de relaţia:
rang sup − k − diag
(i, j ) = ( j − i )(n + 1 − j + i ) + ( j − i )( j − i − 1) + i − 1 ,
d
2
iar pentru matricele inferior k − diagonale avem:
rang inf − k − diag
(i, j ) = (i − j )(n + 1 − i + j ) + (i − j )(i − j − 1) + j −1.
d
2

38
II.8.3. Matrice bandă

Fie f : {1,2,..., n}× {1,2,..., m} → A , n ≠ m o matrice. Dacă n < m , fie


matricea g : {1,2,..., m}× {1,2,..., m} → A definită prin
⎧ f (i, j ) pentru 1 ≤ i ≤ n
g (i, j ) = ⎨
⎩0 pentru n + 1 ≤ i ≤ m
pentru orice 1≤ j ≤ m . Analog, dacă n > m, fie matricea
g : {1,2,..., n}× {1,2,..., n} → A definită prin
⎧ f (i, j ) pentru 1 ≤ j ≤ m
g (i. j ) = ⎨
⎩0 pentru m + 1 ≤ j ≤ n
pentru orice 1 ≤ i ≤ n . Matricea g este numită extensia matricei f la o matrice
pătrată.
Definiţie. Spunem că o matrice f : {1,2,..., n}× {1,2,..., m} → A , n ≠ m
este matrice bandă dacă extensia matricei f la o matrice pătrată este o matrice
superior sau inferior n − m − diagonală.
În definiţia de mai sus avem următoarele situaţii:
- dacă n < m , atunci extensia matricei f la o matrice pătrată este o
matrice superior (m − n ) − diagonală,
- dacă n > m , atunci extensia matricei f la o matrice pătrată este o
matrice inferior (n − m ) − diagonală.
Implementarea matricelor bandă se poate realiza prin liniarizare linie,
coloană sau diagonală.
Pentru realizarea implementării prin liniarizare linie sau coloană a
matricelor bandă, formula de rang trebuie să ţină cont nu doar de modul de
liniarizare, ci şi de relaţia dintre n şi m . Ca principiu general, formula de rang
este identică cu formula de rang pentru matricea n − m − diagonală pentru
extensia corespunzătoare a matricei la o matrice pătrată.
Pentru liniarizarea diagonală,
- dacă n < m , atunci considerarea diagonalelor se face în sensul de la stânga la
dreapta, formula de rang fiind rang nd − m −band (i, j ) = n( j − i ) + i − i ;
- dacă n > m , atunci considerarea diagonalelor se face în sensul de sus în jos,
formula de rang fiind rang nd − m −band (i, j ) = m(i − j ) + j − 1 .

39
Rezumând, putem scrie că formula de rang este dată în general de relaţia:
⎧n( j − i ) + i − 1 pentru n < m
rang nd − m −band (i, j ) = ⎨
⎩m(i − j ) + j − 1 pentru n > m
sau, pe scurt
rang nd − m −band = min (n, m ) i − j + min (i, j ) − 1 .

II.9. Implementarea dinamică semicompactă a matricelor

În cele de mai sus am văzut modul de implementare, statică şi dinamică,


pentru tablouri în care memoria ocupată de acestea reprezenta un spaţiu continuu.
Reamintim însă că, datorită restricţiilor impuse de limbajele de programare, spaţiul
ocupat în memoria centrală a unui sistem de calcul pentru o entitate dată este
limitată la dimensiunea de maxim 64 kilobytes. Datorită acestui fapt, şi
dimensionalizarea tablourilor este, la rândul ei, limitată.
În acest paragraf este dată o modalitate de implementare dinamică a
matricelor, posibil de extins şi la tablourile generale, în care se reţin în spaţii
continue structurile parţiale ale matricei (o linie sau o coloană a matricei ocupă
singură un spaţiu continuu, fără ca acest lucru să fie necesar pentru întreaga
matrice). Acest mod de implementare îl vom numi implementare dinamică
semicompactă.
Să considerăm implementarea linie a unei matrice. În acest caz, o structură
parţială a matricei este linia, astfel că este necesar ca spaţiul afectat unei linii din
matrice să fie continuu. Pentru realizarea implementării este necesară cunoaşterea
adresei pentru fiecare linie a matricei, deci de un vector de locaţii, vloc şi de
rangul fiecărei valori din linie, formula de rang fiind cea definită pentru vectori.
Din punctul de vedere al sistemelor de calcul, această implementare este o
combinaţie între adresarea indirectă, necesară accesului la o linie, şi cea indexată,
pentru accesul la o valoare din linie.
Dacă se consideră o matrice f : {1,2,..., n}× {1,2,..., m} → A , ea este
tratată sub forma de liniarizare linie, considerând vectorii f i : {1,2,..., m} → A ,
f i ( j ) = f (i, j ) , 1≤ i ≤ n, şi vectorul vloc : {1,2,..., n} → & N & ,
vloc(i ) = loc( f i ) .
Matricea se identifică printr-un sistem (a, vloc, f , s v ) , unde a este adresa
de început a vectorului vloc , f este un sistem de implementare a unei linii a
matricei şi s v este spaţiul ocupat de vectorul vloc .
Sistemul de implementare a unei linii este de forma (t, s ) , unde t este
tipul de informaţie memorat în matrice şi s este spaţiul ocupat de o linie a
matricei. Pentru identificarea matricei am folosit sistemul (a, vloc, f , s v ) deoarece
acest sistem este foarte simplu de generalizat pentru tablouri multidimensionale

40
oarecare. Pentru matrice, dacă ţinem cont de definirea sistemului f , putem scrie
direct identificarea sub forma (a, vloc, t , s, s v ) .
Implementarea constă din implementarea vectorului vloc (static sau, de
preferat, dinamic) şi implementarea dinamică a fiecărei linii printr-o implementare
de vector.
Pentru accesul la o valoare din matrice sunt urmaţi următorii paşi:
- se determină locaţia în vloc a adresei de început a liniei în care se găseşte
valoarea;
- se reţine adresa de început a liniei şi se determină folosind această adresă,
adresa de început a valorii dorite
- se accesează valoarea
În această implementare, ţinând cont de limitările segmentelor de
memorie, s v ≤ 64 ⋅ 210 bytes. Cum informaţia din vectorul vloc este una de
adresă, pentru fiecare componentă din vectorul vloc se folosesc 4 bytes, de unde
rezultă că numărul de componente din acest vector, şi astfel numărul de linii pentru
care poate fi definită matricea, este de maxim 16 ⋅ 210 = 16 ⋅ 1024 = 16384 linii.
Fiecare linie din matrice este un vector cu elementele de tipul t , pentru care este
necesar un spaţiu de dim(t ) bytes, astfel că numărul total de elemente pentru
⎡ 64 ⋅ 210 ⎤
fiecare linie este dat de relaţia m = ⎢ ⎥ . Rezultă că în această implementare
⎣ dim(t ) ⎦
numărul maxim de elemente care pot fi memorate este 16384m , corespunzător
unei matrice cu maxim 16384 linii şi m coloane.
Generalizarea celor prezentate mai sus constă din adaptarea componentei
f din sistemul de identificare a matricei la modalitatea aleasă pentru memorarea
unei substructuri a tabloului considerat. Astfel, această componentă poate fi
considerată fie o identificare pentru implementarea compactă a unui tablou cu
dimensiunea cu 1 mai mică decât tabloul considerat, deci o identificare de tipul
(t , s, rang ) , fie o altă specificaţie de implementare semicompactă care nu conţine
( )
însă şi prima componentă a specificaţiei, deci de forma vloc f , g , s f , unde acum
g reprezintă o specificaţie de implementare pentru un tablou cu dimensiunea cu 1
mai mică decât dimensiunea pentru substructura f .

41
III. LISTE LINIARE
Acest capitol prezintă structurile de date cu utilizarea cea mai frecventă,
listele liniare. După definirea noţiunii de listă liniară şi a operaţiilor specifice, vor
fi tratate diferite moduri de implementare a listelor liniare cu operaţiile specifice
lor.

III.1. Definiţie, operaţii asupra listelor


Definiţie. O listă liniară este o înşiruire de elemente de acelaşi tip în care
se disting un prim element şi un ultim element, nu neapărat distincte şi în care
fiecare element, în afară de primul element, are un predecesor, şi fiecare element,
în afară de ultimul element, are un succesor. O listă care nu conţine nici un
element se numeşte listă vidă.
Fie A o mulţime de valori de acelaşi tip şi H = {1,2,..., n} o mulţime
finită de numere naturale consecutive. O funcţie f : H → A este o listă liniară cu
n elemente. Dacă H = ∅ , atunci lista liniară este lista vidă şi, în acest caz, notăm
f = nill . Pentru o listă nevidă f notăm first ( f ) = f (1) , last( f ) = f (n ) . Dacă
listă este vidă, notăm cu λ absenţa elementului şi avem
first (nill) = last(nill) = λ .
Se observă că funcţiile first şi last aplicate la toate listele f cu elemente
dintr-o mulţime A au valori în A ∪ {nill}.
De asemenea, pentru o listă nevidă f cu n elemente, notăm
⎧nill pentru i = 1
pred ( f (i )) = ⎨
⎩ f (i − 1) pentru i ≠ 1
şi
⎧ f (i + 1) pentru i ≠ n
succ( f (i )) = ⎨ .
⎩nill pentru i = n
Se observă că pentru o listă liniară dată f , funcţiile pred şi succ sunt
definite pe Im f cu valori în Im f ∪ {nill} . Din modul de definire avem
pred(first ( f )) = nill şi succ(last( f )) = nill .
Asupra listelor liniare se pot efectua mai multe tipuri de operaţii, cum ar fi:
- accesul la un element dat pentru prelucrarea lui,
- inserarea unui element nou într-o poziţie dată,
- eliminarea unui element din listă,
- combinarea a două sau mai multe liste în una singură,
42
- spargerea unel liste în două sau mai multe liste,
- copierea unei liste,
- ordonarea elementelor după anumite criterii,
- Căutarea unor elemente care îndeplinesc anumite criterii.
Modul de reprezentare a listelor liniare este determinat de frecvenţa cu
care sunt efectuate diverse operaţii asupra listelor şi de necesitatea de a obţine cea
mai mare eficienţă atât din punctul de vedere al spaţiului ocupat, cât şi al timpului
necesar pentru realizarea operaţiilor.
Reprezentarea listelor liniare se poate clasifica în:
- reprezentare secvenţială,
- reprezentare înlănţuită.

III.2. Liste liniare particulare

Operaţiile care se realizează frecvent asupra listelor liniare sunt cele de


adăugare de elemente noi şi de eliminare a elementelor existente la unul din
capetele listei. Acest mod de operare conduce la tipuri particulare de liste liniare,
cum sunt stiva, coada şi coada completă.

III.2.1. Stiva

Definiţie. O listă liniară pentru care accesul se realizează doar la unul din
capetele listei se numeşte stivă.
Principiul de funcţionare al unei stive este notat LIFO (Last In - First
Out), adică ultimul element care este adaugat în listă este primul element eliminat
din aceasta.
Poziţia în care se realizează operaţiile de adăugare şi eliminare poartă
numele de vârful stivei, iar elementul aflat la capătul opus se numeşte baza stivei.
O stivă care nu conţine nici un element se numeşte stivă vidă,
La scoaterea unui element din stivă, după citirea valorii corespunzătoare
din vârful stivei, acest element este eliminat şi următorul element devine vârful
stivei. La depunerea unui element nou, acesta devine vârf al stivei.
Dacă întregul spaţiu alocat unei stive este ocupat şi dorim să mai adăugăm
un element nou intervine fenomenul numit overflow (depăşire superioară) şi
operaţia nu poate fi realizată. Dacă stiva este vidă şi vrem să eliminăm un element,
fenomenul care apare se numeşte underflow (depăşire inferioară) şi, de asemenea,
operaţia nu poate fi realizată.

43
III.2.2. Coada

Definiţie. O listă circulară pentru care elementul accesibil al listei se află


la unul din capete, iar depunerea unui element nou se poate face doar la capătul
opus se numeşte coadă.
Principiul de funcţionare al unei cozi este notat FIFO (First In - First
Out), adică primul element depus în coadă este primul care este eliminat.
Elementul accesibil al cozii la care se poate aplica operaţia de eliminare se
numeşte vârful cozii, iar cel aflat la celălat capăt, unde se poate realiza operaţia de
introducere de elemente noi se numeşte baza cozii. O coadă care nu conţine nici un
element se numeşte coadă vidă.
La scoaterea unui element, după citirea valorii corespunzătoare vârfului,
acest element este eliminat şi următorul element devine vârful cozii. Elementul
eliminat din coadă este cel mai vechi dintre elementele cozii. La adăugarea unui
element nou, acesta devine baza cozii.
În cazul în care întregul spaţiu alocat unei cozi este ocupat şi dorim să mai
adăugăm un element intervine fenomenul de overflow, operaţia neputându-se
realiza. Dacă coada este vidă şi vrem să scoatem un element apare fenomenul de
underflow şi, din nou, operaţia nu poate fi realizată.

III.2.3. Coada completă

Definiţie. O lista liniară în care operaţiile de depunere şi extragere sunt


permise la oricare din capetele listei se numeşte coadă completă.
Elementul aflat la unul din capelele listei se numeşte capătul stâng al cozii
complete, iar cel aflat în celălalt capăt se numeşte capătul drept al cozii complete.
Dacă o coadă completă nu conţine nici un element, spunem: coada completă este
vidă.
La extragerea unui element din coada completă, după citirea valorii
corespunzătoare, elementul se elimină şi următorul element devine capăt (stâng sau
drept, în funcţie de locul unde se realizează operaţia de extragere). La depunerea
unui element nou acesta devine noul capăt (stâng sau drept, corespunzător locului
unde se face adăugarea elementului).
Dacă tot spaţiul alocat unei cozi complete este ocupat şi dorim să adăugăm
un element nou apare fenomenul de overflow şi operaţia de adăugare nu este
posibilă. În cazul în care coada completă este vidă şi vrem să extragem un element
apare fenomenul de underflow şi, de asemenea, operaţia nu se poate realiza.
Dacă la unul dintre capetele cozii complete nu sunt permise operaţii de
depunere (extragere) spunem că avem o coadă completă restricţionată la intrare
(ieşire) la acel capăt. Folosind această noţiune putem spune că o stivă este o coadă
completă restricţionată la intrare şi la ieşire la unul din capete (baza stivei). De
asemenea, coada este o coadă completă restricţionată la intrare la un capăt (vârful
cozii) şi restricţionată la ieşire la celălalt capăt (baza cozii).

44
III.2.4. Coada cu priorităţi

Definiţie. Fie H = {1,2,..., n} o submulţime de numere naturale nenule şi


P = {0,1,..., m} . O aplicaţie f : H → A × P , f (k ) = (v(k ), p(k )) pentru care
avem p (1) ≥ p (2 ) ≥ ... ≥ p (n ) se numeşte coadă cu priorităţi. v(k ) este valoarea
corespunzătoare elementului aflat în poziţia k , iar p(k ) este prioritatea acestui
element.
După cum este natural, un element este cu atât mai prioritar, cu cât
valoarea priorităţii sale este mai mică. Elementul cu prioritatea 0 este cel mai
prioritar, cel cu prioritatea m este cel mai puţin prioritar.
Folosind definiţia de mai sus putem considera că o coadă este o coadă cu
priorităţi în care P = {0} .
Capătul în care se realizează operaţia de extragere poartă numele de vârful
cozii cu priorităţi. Capătul opus vârfului se numeşte baza cozii cu priorităţi. O
coadă cu priorităţi care nu conţine nici un element se numeşte vidă.
La extragerea unui element, după citirea valorii corespunzătoare, elementul
se elimină şi următorul elemet devine vârf al cozii cu priorităţi. Depunerea unui
element se face ţinând cont de valoarea priorităţii elementului, după cum urmează:
• dacă prioritatea elementului nou q nu apare ca prioritate în coada cu priorităţi
existentă,
- dacă q este mai mică decât prioritatea oricărui element existent,
depunerea se realizează la vârful cozii cu priorităţi;
- dacă q este mai mare decât prioritatea oricărui elementse existent,
depunerea se face la baza cozii cu priorităţi;
- în celelalte cazuri, se determină k astfel încât p(k ) < q < p (k + 1) şi
depunerea se realizează între elementele k şi k + 1
• dacă în coada cu priorităţi existentă există elemente cu prioritatea egală cu
prioritatea q a elementului de introdus,
- dacă p(1) = q , depunerea se realizează la baza cozii cu priorităţi;
- altfel, se determină cea mai mică valoare k pentru care p (k ) = q şi
depunerea se face între elementele k − 1 şi k .
Dacă toate locaţiile alocate unei cozi cu priorităţi sunt ocupate şi dorim
adăugarea unui element nou, apare fenomenul de overflow şi operaţia nu se poate
realiza. Dacă coada cu priorităţi este vidă şi vrem să extragem un element, apare
fenomenul de underflow şi, de asemenea, operaţia nu poate fi realizată.

45
III.3. Implementarea secvenţială a listelor liniare

Cel mai simplu mod de implementare a listelor liniare este cel în care
elementele listei sunt memorate unul după altul, în ordinea în care apar în listă, în
locaţii de memorie consecutive, numit implementare secvenţială. Acest mod de
implementare se realizează practic prin intermediul vectorilor, care pot fi
consideraţi la rândul lor, liste liniare.
Pentru fiecare structură particulară, în cadrul implementării se vor
prezenta:
- modul de iniţializare a structurii (definirea iniţială a structurii ca structură
vidă),
- realizarea operaţiei de depunere a unui element nou în structură,
- modul de extragere a unui element din structură.

III.3.1. Stiva secvenţială

Considerăm că spaţiul pentru implementarea secvenţială a unei stive S


este alocat prin intermediul unui vector A de m elemente. Pentru indicare
vârfului stivei considerăm o variabilă V numită poiterul stivei.
Operaţia de iniţializare a stivei, notă S = ∅ , este dată de:
V =0
Depunerea unui element x în stivă, desemnată prin x ⇒ S , este realizată
prin:

dacă V = m
atunci overflow
altfel V = V + 1 şi A(V ) = x

Extragerea unei element din stivă într-o variabilă z , notată z ⇐ S ,


corespunde procesului:

dacă V = 0
atunci underflow
altfel z = A(V ) şi V = V − 1

Observaţie. Cazuri de excepţii overflow şi underflow trebuie tratate în


funcţie de aplicaţia la care se utilizează stiva. Efectele posibile sunt de oprire a
efectuării altor operaţii, de realizare a unor prelucrări excepţie. În cazul overflow
soluţiile pot cuprinde şi alocarea de spaţiu suplimentar pentru stivă.

46
III.3.2. Coada secvenţială

Considerăm că spaţiul necesar implementării secvenţiale a unei cozi C


este alocat prin intermediul unui vector A de dimensiune m .
Cea mai simplă modalitate de implementare secvenţială este cea în care se
consideră că, dacă coada nu este vidă, întotdeauna unul din capetele cozii coincide
cu elementul memorat în A(1) . Pentru a menţine această presupunere în fiecare
moment este necesar ca la realizarea unei operaţii în acest capăt al cozii să se
realizeze o translatare a elementelor, astfel:
- dacă A(1) reprezintă baza cozii şi se realizează o operaţie de depunere, aceasta
este precedată de o decalare la dreapta a elementelor existente înainte de
depunere,
- dacă A(1) reprezintă vârful cozii şi se realizează o operaţie de extragere,
atunci aceasta este urmată de o decalare la stânga a elementelor rămase după
eliminare.
Această implementare va fi numită implementare secvenţială cu decalare
unitară.
Pentru desemnarea elementului aflat în celălalt capăt al cozii este necesară
prezenţa unei variabile suplimentare P numită poiter-ul cozii.
Indiferent de modul în care considerăm elementul A(1) , iniţializarea cozii,
C = ∅ , se realizează prin:
P=0
În cazul în care A(1) reprezintă baza cozii (deci P este vârful cozii),
depunerea unui element x în coadă, notată x ⇒ C , se face prin procesul:

dacă P = m
atunci overflow
altfel
{decalare la dreapta}
pentru i = P, P − 1,...,1 execută
A(P + 1) = A(P )
P = P +1
{depunere}
A(1) = x ,

iar extragerea într-o variabilă z , notată z ⇐ C , se realizează prin:

dacă P = 0
atunci underflow
altfel z = A(P ) şi P = P − 1 ,

47
În cazul când vârful cozii este reprezentat de A(1) (deci P este baza
cozii), depunerea elementului x , x ⇒ C , este dată de procesul:

dacă P = m
atunci overflow
altfel P = P + 1 şi A(P ) = x ,

iar extragerea într-o variabilă z , z ⇐ C , se face prin:

dacă P = 0
atunci underflow
altfel
{extragere}
z = A(1)
{decalare}
pentru i = 1,2,..., P execută A(i ) = A(i + 1)
P = P −1.

O implementare alternativă încearcă să reducă numărul operaţiilor de


decalare, prin aplicarea unei decalări multiple, aplicate doar în cazul în care o
depunere suplimentară ar implica ieşirea din spaţiul vectorului alocat pentru coadă.
Vom spune că avem o implementare secvenţială cu decalare multiplă.
Implementarea de acest tip este utilă doar dacă extragerea se face spre A(1) , iar
depunerea se realizează spre A(m ) .
Pentru a indica baza cozii va fi nevoie de o variabilă B , iar pentru vârful
cozii de V .
Operaţia de iniţializare a cozii, C = ∅ , este:
B = 0 şi V = 0 .
La depunerea unui element x în coadă, x ⇒ C , se utilizează secvenţa:

dacă B = m
atunci dacă V = 0
atunci overflow
altfel
{decalare}
pentru i = V + 1, V + 2,..., B execută
A(i − V ) = A(i )
B = B − V şi V = 0
B = B + 1 şi A(B ) = x .

48
Pentru a extrage un element din coadă într-o variabilă z , z ⇐ C ,
procesul de prelucrare este descris prin:

dacă B = V
atunci underflow
altfel V = V + 1 şi z = A(V ) .

Un mod de implementare care evită complet operaţiile de decalare


foloseşte o funcţie de tip modulo pentru determinarea elementului unde se fac
operaţiile de depunere şi extragere din coadă. Datorită circularităţii produse de
funcţia modulo, această implementare poartă numele de implementarea
secvenţială circulară a cozilor, o coadă implementată în acest mod fiind cunoscută
sub numele de coadă secvenţială circulară.
Pentru funcţia de tip modulo considerăm o aplicaţie
& modulo m : N → {1,2,..., m}& definită prin modulo m (k ) = 1 + r , unde r este
restul împărţirii lui k la m .
Pentru implementarea cozii secvenţiale circulare trebuie să cunoaştem baza
cozii, memorate printr-o variabilă B , şi vârful cozii, pentru care folosim o
variabilă V .
Iniţializarea cozii secvenţiale circulare, C = ∅ , se realizeză prin:
B = m şi V = m
Depunerea unui element x într-o coadă circulară, x ⇒ C , se face prin
procesul:

dacă V = modulo m (B + 1)
atunci overflow
altfel B = modulo m (B + 1) şi A(B ) = x .

Pentru extragerea unei valori într-o variabilă z , z ⇐ C , se utilizează


procesul:

dacă B = V
atunci underflow
altfel V = modulo m (V + 1) şi z = A(V ) .

III.3.3. Coada secvenţială completă

Ca în cazul cozilor secvenţiale, şi pentru cozile secvenţiale complete se


regăsesc mai multe modalităţi de implementare, similare modalităţilor prezentate
mai sus. Deoarece în cazul cozilor complete operaţiile de depunere şi extragere
sunt permise în oricare din capetele cozii complete, implementările din acest
paragraf cuprind:
49
- iniţializarea cozii secvenţiale complete,
- depunerea unui element la capătul stâng,
- depunerea unui element la capătul drept,
- extragerea unei valori din capătul stâng,
- extragerea unei valori din capătul drept.
Implementările secvenţiale cele mai frecvente pentru cozile complete sunt
implementarea secvenţială cu decalare unitară şi cea circulară.
Pentru realizarea implementărilor secvenţiale ale cozilor complete D se
presupune că se alocă un spaţiu de memorie prin intermediul unui vector A de
dimensiune m .
Datorită posibilităţii de acţionare la ambele capete, activitatea relativă la
coada completă poate fi considerată simetrică, astfel că nu va conta modul în care
definim fizic activitatea la capătul stâng, DL , şi cea la capătul drept, DR .
Pentru implementarea secvenţială cu decalare unitară să considerăm, ca
uzual, că A(1) reprezintă capătul stâng al cozii complete. Atunci, pentru a
cunoaşte capătul drept al cozii complete este necesară o variabilă suplimentară P ,
numită pointer-ul cozii complete.
Procesul de iniţializate al cozii complete, D = ∅ este reprezentat prin
P=0
Pentru operaţia de depunere a unui element nou x ,
- în cazul introducerii în capătul stâng, x ⇒ DL , se face prin:

dacă P = m
atunci overflow
altfel
{decalare}
pentru i = P, P − 1,...,1 execută A(i + 1) = A(i )
P = P +1
{depunere}
A(1) = x

- în cazul adăugării în capătul drept, x ⇒ DR , este dată de:

dacă P = m
atunci overflow
altfel P = P + 1 şi A(P ) = x

Pentru extragerea valorii corespunzătoare într-o variabilă z ,


- în cazul eliminării în capătul stâng, z ⇐ DL , procesul de lucru este descris
prin:

50
dacă P = 0
atunci underflow
altfel
{extragere}
z = A(1)
{decalare}
pentru i = 2,3,..., P execută A(i − 1) = A(i )
P = P −1

- în cazul ieşirii în capătul drept, z ⇐ DR , procedura de lucru este dată prin:

dacă P = 0
atunci underflow
altfel z = A(P ) şi P = P − 1

La implementarea secvenţială circulară a cozii complete, când spunem că


avem o coadă completă circulară, pentru indicarea capătului stâng, DL , folosim o
variabilă L , iar pentru capătul drept, DR , utilizăm R . De asemenea, pentru
asigurarea circularităţii, în definirea proceselor de lucru se va utiliza funcţia
modulo m definită în paragraful precedent.
Procedura de iniţializare a cozii complete circulare, D = ∅ , este:
L = M şi R = M .
Pentru depunerea unui element nou x ,
- în cazul introducerii în capătul drept, x ⇒ DR , procesul este dat prin:

dacă L = modulo m (R + 1)
atunci overflow
altfel R = modulo m (R + 1) şi A(R ) = x

- în cazul adăugării în capătul stâng, x ⇒ DL , procedeul de lucru este:

dacă modulom (L −1) = R


atunci overflow
altfel A(L ) = x şi L = modulo m (L − 1) .

51
Pentru extragerea valorii corespunzătoare într-o variabilă z ,
- în cazul ieşirii din capătul stâng, z ⇐ DL , procesul este descris prin:

dacă L = R
atunci underflow
altfel L = modulo m (L + 1) şi z = A(L )

- în cazul eliminării din capătul drept, z ⇐ DR , procedeul de lucru este dat


prin:

dacă L = R
atunci underflow
altfel z = A(R ) şi R = modulo m (R − 1) .

III.3.4. Coada secvenţială cu priorităţi

Deoarece în cazul cozilor cu priorităţi depunerea unui nod nou se poate


produce în orice punct al listei reprezentând structura, fenomenul de decalare nu
poate fi evitat sau redus, aşa cum s-a procedat în cazul cozilor şi cozilor complete.
Pentru realizarea implementării secvenţiale a unei cozi cu priorităţi C o
problemă suplimentară este legată de modul de memorare internă a perechii de
informaţii formate din valoarea efectivă ce trebuie memorată şi de prioritatea
asociată valorii respective. Rezolvările posibile pentru această situaţie sunt:
- implementarea prin intermediul a doi vectori V şi P de aceeaşi dimensiune
m , caz în care, pentru un rang dat i , V (i ) reprezintă valoarea memorată
pentru un element, iar P(i ) prioritatea corespunzătoare valorii;
- definirea unui ansamblu de tip articol format din câmpurile V şi P şi
implementarea prin intermediul unui vector A de dimensiune m în care
fiecare element este de tipul articol, caz în care pentru un rang dat i , valoarea
elementului este A(i ).V , iar prioritatea asociată este A(i ).P .
Pentru ambele moduri de implementare considerăm că baza cozii cu
priorităţi este dată de elementul de rang 1. Astfel, este necesară o variabilă
suplimentară, E care să indice rangul vârfului cozii cu priorităţi.
Iniţializarea cozii cu priorităţi, C = ∅ , valabilă în ambele moduri de
implementare este:
E = 0.
La implementarea prin doi vectori
- depunerea unui element nou x cu prioritatea y , ( x, y ) ⇒ C , se realizează
prin procesul

52
dacă E = m
atunci overflow
altfel
{poziţie inserare}
P(E + 1) = −1 şi k = 1
pentru P(k ) > y execută k = k + 1
{decalare}
pentru i = E , E − 1,..., k execută
V (i + 1) = V (i ) şi P(i + 1) = P(i )
E = E +1
{depunere}
V (k ) = x şi P(k ) = y

- extragerea valorii corespunzătoare într-o variabilă z , z ⇐ C , se face prin


procedeul:

dacă E = 0
atunci underflow
altfel z = V (E ) şi E = E − 1 .

La implementarea printr-un singur vector cu structură de articol


- depunerea unui element nou x cu prioritatea y , ( x, y ) ⇒ C , se realizează
prin procesul

dacă E = m
atunci overflow
altfel
{poziţie inserare}
A(E + 1).P = −1 şi k = 1
pentru A(k ).P > y execută k = k + 1
{decalare}
pentru i = E , E − 1,..., k execută A(i + 1) = A(i )
E = E +1
{depunere}
A(k ).V = x şi A(k ).P = y

53
- extragerea valorii corespunzătoare într-o variabilă z , z ⇐ C , se face prin
procedeul:

dacă E = 0
atunci underflow
altfel z = A(E ).V şi E = E − 1 .

III.3.5. Liste secvenţiale cu spaţiu partajat

În unele aplicaţii există posibilitatea utilizării mai multor structuri liniare


care să fie implementate în acelaşi spaţiu alocat. Vom spune că spaţiul alocat este
partajat de structurile care îl utilizează în comun. Pentru exemplificarea acestui
fenomen vom considera că toate structurile implicate sunt de tipul stivă. În cazul
general, singura restricţie care se aplică este ca toate informaţiile memorate în
structurile care partajează un spaţiu de memorie să fie de acelaşi tip de informaţie.
Considerăm că este alocat un spaţiu de m componente prin intermediul
unui vector A . Considerăm că o aplicaţie poate lucra simultan cu n stive, S1 , S 2 ,
…, S n , care împreună, la un moment dat, nu pot avea mai mult de m elemente.
Presupunem că prima stivă nevidă are baza în A(1) şi că stivele se află una
în continuarea celeilalte. Astfel, dacă stivele S i şi S i +1 sunt nevide şi vârful stivei
S i se găseşte în A( j ) , atunci baza stivei S i +1 se găseşte în A( j + 1) . Această
presupunere ne permite să memorăm suplimentar doar vârfurile stivelor, pentru
care este necesar un vector V . Pentru o prelucrare uniformă a cazurilor de
extragere, fără a fi necesară tratarea ca excepţie a primei stive, vom considera o
stivă fictivă S 0 , pentru care vârful stivei se găseşte în A(0 ) , element de asemenea
fictiv, şi astfel vârful stivei S i va fi memorat în V (i + 1) , vectorul V având n + 1
elemente. De asemenea, pentru tratament uniform în cazul depunerilor de elemente
noi, este necesară considerarea unei stive fictive S n +1 . Acest lucru face ca vectorul
de memorare a vârfurilor stivelor memorate în acelaşi spaţiu de memorie să fie de
n + 2 valori.
Procedura de iniţializare vizează simultan toate cele n stive pentru care
avem scrierea {S i = ∅}i =1,n . Procesul este descris prin

Pentru i = 1,2,..., n + 2 execută V (i ) = 0 .

54
Pentru depunerea unui element nou în stiva S i , notată x ⇒ S i , procesul
este descris prin:

dacă V (n + 2 ) = m
atunci overflow
altfel
{decalare începând cu baza stivei S i +1 }
pentru j = V (n + 2 ), V (n + 2 ) − 1,..., V (i + 1) + 1
execută A( j + 1) = A( j )
{actualizarea adreselor vârfurilor}
pentru j = i + 1, i + 2,..., n + 1
execută V ( j + 1) = V ( j + 1) + 1
{depunere valoare nouă}
A(V (i + 1) + 1) = x şi V (i + 1) = V (i + 1) + 1 .

Pentru extragerea valorii corespunzătoare din vârful stivei S i într-o


variabilă z , desemnată prin z ⇐ S i , procesul se poate descrie prin:

dacă V (i ) = V (i + 1)
atunci underflow
altfel
{extragere valoare şi eliminare}
z = A(V (i + 1)) şi V (i + 1) = V (i + 1) − 1
{decalare începând cu baza stivei S i +1 }
pentru j = V (i + 1) + 2, V (i + 1) + 3,...,V (n + 2 )
execută A( j − 1) = A( j )
{actualizarea adreselor vârfurilor}
pentru j = i + 1, i + 2..., n + 1
execută V ( j + 1) = V ( j + 1) − 1 .

Un caz special de partajare a spaţiului alocat secvenţial pentru liste liniare


este cel în care numărul de liste este egal cu 2. Procedând ca şi în cazul anterior,
vom exemplifica fenomenul prin partajarea unui spaţiu de m locaţii prezente sub
forma unui vector A de către două stive, S1 şi S 2 .
Implicarea a doar două structuri permite ca fiecare să se găsească la unul
din capetele fizice ale spaţiului alocat. Fiind vorba de două stive vom putea
considera că A(1) va reprezenta baza stivei S1 şi A(m ) este locul în care este

55
memorată baza stivei S 2 . Pentru a identifica cele două vârfuri ale stivelor vom
utiliza variabilele V 1 şi V 2 .
Iniţializarea stivelor, deci operaţia S1 = S 2 = ∅ , se face prin:
V 1 = 0 şi V 2 = m + 1 .
Considerăm acum depunerea unei valori noi x .
- Dacă operaţia pe care dorim s-o realizăm este x ⇒ S1 , secvenţa de lucru este:

dacă V 1 + 1 = V 2
atunci overflow
altfel V 1 = V 1 + 1 şi A(V 1) = x .

- Dacă se realizează depunerea x ⇒ S 2 , procesul de lucru este descris prin:

dacă V 1 + 1 = V 2
atunci overflow
altfel V 2 = V 2 − 1 şi A(V 2 ) = x

În cazul în care urmează a realiza extragerea valorii dintr-o stivă într-o


variabilă z
- pentru z ⇐ S1 avem:

dacă V 1 = 0
atunci underflow
altfel z = A(V 1) şi V 1 = V 1 − 1 ;

- pentru z ⇐ S 2 procesul este:

dacă V 2 = m + 1
atunci underflow
altfel z = A(V 2 ) şi V 2 = V 2 + 1 .

III.4. Implementarea înlănţuită a listelor liniare

O alternativă la implementarea listelor liniare prin intermediul vectorilor


de locaţii succesive de memorie o constituie cazul în care spaţiul de memorare
alocat listei nu mai este într-un spaţiu continuu, cel puţin în ceea ce priveşte
trecerea de la un element la următorul din listă.
Deoarece următorul element al listei nu se mai regăseşte în locaţia de
memorie care urmează într-o ordine naturală, accesul de la un element la altul
trebuie să se realizeze prin alt instrument decât o funcţie predefinită de localizare a
unei valori în memoria unui calculator. Cea mai facilă alternativă este de
56
conectare a valorilor dintr-o listă prin specificarea, alături de valoare, a adresei
pentru următorul element, procedeu care conduce la implementarea înlănţuită.
Se formează astfel o pereche de forma (v; l ) , v reprezentând informaţia
care se prelucrează, numită şi informaţie utilă, iar l informaţia prin care se asigură
trecerea la următorul element al listei, numită şi informaţia de legătură. O pereche
de forma (v, l ) se numeşte celulă.
Dacă o celulă este un capăt al listei, deci nu există o valoare următoare a
listei, atunci partea de legătură trebuie să nu poată face referire la o locaţie de
memorie validă. Pentru a desemna o astfel de adresă, formal vom nota informaţia
de legătură prin Λ . Din punct de vedere practic, în limbajele de programare
evoluate, la utilizarea ca informaţie de legătură a datelor de tip adresă de memorie
directă, sunt definite constante specifice pentru a indica o adresă inexistentă
(NULL în limbajul C, NILL pentru limbajul PASCAL). Dacă pentru informaţia de
legătură se folosesc valori de tip indice, practic se foloseşte o valoare cu 1 mai
mică decât valoarea obişnuită ca primă valoare de indice (-1 pentru C, limita
minimă definită ca domeniu -1 pentru PASCAL).
Schema de memorare a datelor într-o ordine logică determinată de legături
se numeşte schemă de memorare a listelor.
Pentru a avea acces la elementele unei liste în implementare înlănţuită este
necesară cunoaşterea adresei unui element în care să fie indicată adresa pentru
următorul element, deci un element de tip adresă. În cazul în care lista este vidă,
deci nu există nici un element, acest lucru poate fi indicat direct prin memorarea
valorii Λ ca adresă de prim element.
Datorită liniarităţii listelor liniare, deplasarea se poate face pe o dreaptă,
deci pe două direcţii. Pentru a asigura deplasarea pe o singură direcţie este
suficient ca informaţia de legătură să fie formată de o singură valoare de adresă,
lista care se obţine purtând numele de listă simplu înlănţuită. Pentru deplasarea pe
două direcţii informaţia de legătură trebuie să conţină două valori de adresă, lista
purtând numele de listă dublu înlănţuită.
Indiferent de modul ales pentru deplasarea într-o listă, pentru a accesa un
element aflat la i elemente distanţă de capătul listei este nevoie de trecerea prin
cele i − 1 elemente anterioare, ceea ce creează dezavantajul unei parcurgeri lente.
Alt dezavantaj constă într-un consum de memorie mai mare decât la
implementările prin intermediul vectorilor, acest lucru fiind datorat prezenţei
informaţiei de legătură alături de informaţia utilă. Avantajul major al listelor
înlănţuite este datorat posibilităţilor de alocare dinamică a datelor.

III.4.1. Implementarea listelor liniare simplu înlănţuite

În cele ce urmează vom considera că lista simplu înlănţuită este


reprezentată prin celule de tipul (v, l ) , unde v este informaţia utilă (valoarea
prelucrabilă din celulă), iar l este o valoare de tipul adresă (informaţia de
legătură).
57
Implementarea listelor se poate realiza în două moduri distincte şi anume:
- într-un spaţiu de memorare predefinit pentru un număr de m celule,
numerotate cu adresele 1 , 2 , …, m
- în spaţiul exterior programului, folosind facilităţile de alocare dinamică
specifice sistemelor de operare şi pentru care există în limbajele de programare
rutine specifice.
În cazul lucrului pe spaţii predefinite, celulele libere vor fi organizate, la
rândul lor, ca o structură de tip listă liniară, mai exact ca o stivă numită stiva
celulelor disponibile, pe care o vom nota cu DISP .
Procesele de lucru pe stiva celulelor disponibile sunt după cum urmează:
Iniţializarea stivei celulelor disponibile este realizată prin algoritmul:

pentru i = 1,2,..., m − 1 execută l (i ) = i + 1


l (m ) = Λ
DISP = 1 .

Obţinerea unei celule din stiva celulelor disponibile, notată prin


GETCEL(k ) , se realizează prin procesul:

dacă DISP = Λ
atunci overflow
altfel k = DISP şi DISP = l (DISP ) .

Trecerea în stiva celulelor disponibile a unei celule eliberate din lista


liniară, notată prin FREECEL(k ) , implică aplicarea algoritmului:
l (k ) = DISP şi DISP = k .
În cazul operaţiei GETCEL(k ) , inexistenţa unei celule disponibile,
semnalată prin overflow, este rezolvată în funcţie de aplicaţie, prin alocarea de
spaţiu suplimentar, prin interzicerea efectuării unor operaţii sau prin semnalarea
apariţiei fenomenului, punând k = Λ , utima variantă fiind cea adoptată la
prezentările ce vor urma.
În cazul lucrului cu spaţii alocate dinamic în afara spaţiului program,
operaţia de iniţializare a spaţiului disponibil nu este necesară. Operaţiile
GETCEL(k ) şi FREECEL(k ) sunt asigurate prin rutine existente în limbajele de
programare şi prin care sunt accesate operaţii de gestiunea memoriei din sistemul
de operare.
Prezentăm, în continuare, principalele operaţii care se realizează asupra
unei liste liniare simplu înlănţuită generală L , pentru care adresa primului element
este dată prin variabila V .
Iniţializarea unei liste, notată L = ∅ , se face prin:
V = Λ.
58
Modul de depunere a unei valori x într-o listă simplu înlănţuită depinde
de locul în care depunerea este realizată:
- dacă depunerea se face ca prim nod în ordinea logică a elementelor, notăm
x ⇒ first (L ) şi avem:

GETCEL(k )
dacă k = Λ
atunci overflow
altfel v(k ) = x , l (k ) = V şi V = k .

- pentru depunerea ca ultim nod, notăm x ⇒ last (L ) şi se foloseşte algoritmul:

GETCEL(k )
dacă k = Λ
atunci overflow
altfel {completare nod nou}
v(k ) = x şi l (k ) = Λ
dacă V = Λ
atunci {depunere în listă vidă}
V =k
Altfel {deplasare la finalul listei}
i =V
cât timp l (i ) ≠ Λ execută
i = l (i )
{legare nod nou} l (i ) = k

- dacă introducerea se face după nodul din poziţia i , notăm x ⇒ next i (L ) şi


avem:

{deplasare la nodul i }
j = V şi n = 1
cât timp j ≠ Λ şi n < i execută j = l ( j ) şi n = n + 1
dacă j = Λ
atunci overlist
altfel
GETCEL(k )
dacă k = Λ
atunci overflow
altfel v(k ) = x , l (k ) = l ( j ) şi l ( j ) = k .
59
Observăm că la introducerea unui nod nou după cel din poziţia i apare un
fenomen nou, notat overlist. Acesta ne spune că lista existentă conţine un număr de
noduri strict mai mic decât i şi operaţia nu poate fi executată.
Obţinerea valorii corespunzătoare unei celule într-o variabilă z , cu
eliberarea acesteia depinde, de asemenea, de poziţia celulei în lista simplu
înlănţuită:
- nodul din care se extrage valoarea este primul din listă, operaţie notată
z ⇐ first (L ) , implică algoritmul:

dacă V = Λ
atunci underflow
altfel k = V , z = v(k ) , V = l (k ) şi FREECEL(k )

- dacă extragerea se face din ultimul nod operaţia este notată z ⇐ last (L ) şi
avem:

dacă V = Λ
atunci underflow
altfel
{deplasare ultimul nod}
u = Λ, i =V
cât timp l (i ) ≠ Λ
execută u = i şi i = l (i )
{închidere listă}
dacă u = Λ
atunci V = Λ
altfel l (u ) = Λ
{extragere}
z = v(i ) şi FREECEL(i ) .

- pentru extragerea valorii din nodul din poziţia i în ordinea logică notăm
operaţia prin z ⇐ next i (L ) şi procesul se realizează prin:

dacă V = Λ
atunci underflow
altfel
{deplasare nod i }
u = Λ, j =V , t =1
cât timp j ≠ Λ şi t < i
execută u = j , j = l ( j ) , t = t + 1

60
dacă j = Λ
atunci overlist
altfel
{exclude nod}
dacă u = Λ
atunci V = Λ
altfel l (u ) = l ( j )
{extrage}
z = v( j ) şi FREECEL( j ) .

III.4.2. Stiva simplu înlănţuită

O primă structură de listă particulară este structura de tipul stivă.


Implementarea acestei structuri în contextul înlănţuirii simple conduce la ceea ce
numim stivă simplu înlănţuită. Anterior, am prezentat deja o exemplificare a
implementării unei stive, stiva celulelor disponibile. Acesta este un exemplu de
stivă definită pentru un spaţiu predefinit de elemente în care întotdeauna vârful
stivei coincide cu prima celulă alocată sau eliberată.
Formatul uzual al stivelor, în special cele construite cu celule obţinute
strict prin alocarea dinamică, este de a considera celulele drept cupluri
(inf , prev ) , unde inf reprezintă informaţia utilă a unui nod din stivă, iar prev
este adresa nodului anterior (previous). Pentru desemnarea vârfului stivei S vom
folosi o variabilă V .
Iniţializarea S = ∅ este produsă prin
V = Λ.
Depunerea unei valori x , x ⇒ S se realizează prin

GETCEL(a )
dacă a = Λ
atunci overflow
altfel inf (a ) = x , prev(a ) = V şi V = a .

Extragerea valorii corespunzătoare unui nod într-o variabilă z , z ⇐ S


implică algoritmul

dacă V = Λ
atunci underflow
altfel
a = V , V = prev(a ) ,
z = inf (a ) şi FREECEL(a ) .

61
III.4.3. Coada simplu înlănţuită

La implementarea structurii de coadă prin înlănţuirea simplă a nodurilor o


inportanţa majoră o are modalitatea de alegere a bazei şi vârfului cozii.
Ţinând cont de existenţa unei singure informaţii de legătură, de realizarea
operaţiei de depunere la bază şi a celei de extragere la vârf, varianta optimă de
implementare a unei cozi în sistemul de înlănţuire simplă se obţine considerând că
baza se găseşte la finalul listei.
Considerăm coada C dată prin cuplul (inf , next ) , unde inf semnifică
informaţia prelucrabilă din celulă, iar next indică adresa următoarei celule în
sensul ieşirii din coadă, cu baza indicată prin variabila B şi vârful indicat prin V .
Iniţializarea cozii, C = ∅ , este obţinută prin
V = Λ şi B = Λ .
Depunerea unei valori noi x , x ⇒ C este realizată prin

GETCEL(a )
dacă a = Λ
atunci overflow
altfel
{completare nod}
inf (a ) = x şi next (a ) = Λ
{legare nod nou}
dacă V = Λ
atunci V = a
altfel next (B ) = a
B = a.

Extragerea valorii din vârful cozii într-o variabilă z , z ⇐ C implică


aplicarea algoritmului:

dacă V = Λ
atunci underflow
altfel
{actualizare coadă}
a = V , V = next (a )
dacă V = Λ atunci B = Λ
{extragere informaţie}
z = inf (a ) , FREECEL(a ) .

62
Considerarea inversă a semnificaţiei pentru baza şi vârful cozii conduce la
o implementare în care indiferent dacă se memorează sau nu adresa pentru vârful
cozii, această informaţie este inutilă la extragerea unei valori deoarece nu se poate
cunoaşte adresa nodului care după extragere va deveni noul vârf. Considerând
lucrurile în acest mod va fi necesară parcurgerea cozii de la bază până la vârf
pentru a determina atât adresa vârfului, cât şi pe cea a noului vârf după extragere.
Considerăm astfel coada C dată prin cuplul (inf , succ ) , unde inf
conţine informaţia utilă iar succ reprezintă adresa nodului succesor în ordinea de
la bază către vârf. Pentru accesul la coadă se reţine doar baza cozii prin intermediul
unei variabile B .
Iniţializarea C = ∅ se realizează prin atribuirea
B=Λ
Pentru depunerea unei valori x , x ⇒ C se foloseşte secvenţa:

GETCEL(a )
dacă a = Λ
atunci overflow
altfel inf (a ) = x , succ(a ) = B şi B = a .

Extragerea valorii din vârful cozii într-o variabilă z , z ⇐ C se rezolvă


prin algoritmul:

dacă B = Λ
atunci underflow
altfel
{parcurgerea cozii până la vârf}
u = Λ şi i = B
cât timp succ(i ) ≠ Λ execută
u = i şi i = succ(i )
{extragere}
dacă u = Λ
atunci B = Λ
altfel succ(u ) = Λ
z = inf (i ) şi FREECEL(i )

Aşa cum am văzut la implementarea secvenţială a cozilor şi pentru cozile


simplu înlănţuite existe posibilitatea unei implementări în care se face legătura
între vârful cozii şi baza acesteia, ceea ce se obţine purtând numele de coadă
circulară simplu înlănţuită.

63
Indiferent dacă elementul punctat este vârful sau baza cozii, datorită
simplei înlănţuiri va fi necesară o parcurgere a cozii pentru realizarea operaţiilor de
depunere şi extragere. Pentru a evita parcurgerea atât la depunere, cât şi la
extragere, varianta care este adoptată este de a marca vârful cozii.
Considerăm acum o coadă C , dată printr-un cuplu (inf , succ ) , având
semnificaţia anterioară, pentru care se indică, printr-o variabilă V , vârful cozii.
Iniţializarea cozii, C = ∅ , se realizează prin atribuirea:
V = Λ.
Pentru depunerea unei valori x în coadă, x ⇒ C , se foloseşte procedeul

GETCEL(a )
dacă a = Λ
atunci overflow
altfel
inf (a ) = x
dacă V = Λ
atunci V = a şi succ(a ) = a
altfel succ(V ) = a .

Pentru extragerea într-o variabilă z a valorii din vârful cozii, z ⇐ C ,


algoritmul este

dacă V = Λ
atunci underflow
altfel
z = inf (V )
dacă succ(V ) = V
atunci FREECEL(V ) şi V = Λ
altfel
{parcuregere}
a =V ;
cât timp succ(a ) ≠ V execută
a = succ(a )
{legare nouă}
b := V ;
succ(a ) = succ(b ) şi V = a
FREECEL(b ) .

64
Conform prezentărilor din acest paragraf şi algoritmilor de extragere şi de
depunere din cazul general al implementării înlănţuite a listelor liniare, se obţin
uşor modalităţile specifice de lucru pentru cozile complete simplu înlănţuite şi
pentru listele cu priorităţi simplu înlănţuite. Procedurile efective de realizare a
operaţiilor rămân ca exerciţiu pentru cititor.
Important este, în primul rând, faptul că algoritmii de prelucrare în listele
simplu înlănţuite au performanţe relativ scăzute din cauza necesităţii parcurgerii
integrale a cel puţin unei structuri în cazul în care lista liniară nu este o stivă.
Pentru evitatea acestui neajuns, listele liniare, cu excepţia stivelor, sunt
implementate prin alt mod de înlănţuire.

III.4.4. Implementarea listelor liniare dublu înlănţuite

Înlănţuirea nodurilor unei liste liniare prin intermediul a două legături are
ca principal avantaj posibilitatea de parcurgere a unei liste atât de la stânga la
dreapta, cât şi invers. O astfel de parcurgere nu este justificată în cazul stivelor,
implementarea acestora ca liste cu dublă înlănţuire putând fi doar un exerciţiu.
Ca prezentare generală vom da, în cele ce urmează, principalele operaţii de
depunere şi extragere dintr-o listă dublu înlănţuită. Vom considera o listă L în
care nodurile sunt formate ca triplete (llink , inf , rlink ) , unde inf reprezintă
informaţia utilă din nod, llink este legătura spre stânga listei, iar rlink cea spre
dreapta listei.
Similar cazului listelor simplu înlănţuite, implementarea se poate realiza
fie pe un spaţiu static de tip vector, fie direct din spaţiul de memorie dinamic. În
cazul implementării în memoria statică, este necesară organizarea unei stive de
noduri disponibile pentru care lucrul se organizează în acelaşi mod cu stiva
nodurilor disponibile pentru liste simplu îmlănţuite, doar că în legarea nodurilor în
stivă se va utiliza doar unul din câmpurile de legătură ale nodurilor listei dublu
înlănţuite, să presupunem llink . Pentru obţinerea unui nod din stiva de
disponibilităţi vom folosi, în continuare, notaţia GETCEL(a ) , iar pentru trecerea
unui nod în această stivă, FREECEL(a ) . Dacă se alege lucru în memoria
dinamică, vom nota la fel procedurile de obţinere a spaţiului de memorie necesar.
Dacă x reprezintă informaţia nouă
- pentru depunerea la capătul stâng al listei, notată x ⇒ LEFT (L ) , se utilizează
procesul
a. dacă adresa capătului stâng este indicată prin variabila AL

GETCEL(a )
dacă a = Λ
atunci overflow
altfel inf (a ) = x , llink (a ) = Λ , rlink (a ) = AL şi
AL = a
65
b. dacă pentru accesul la listă se indică doar adresa capătului drept, AR

GETCEL(a )
dacă a = Λ
atunci overflow
altfel
{completare nod}
inf (a ) = x şi llink (a ) = Λ
dacă AR = Λ
atunci rlink (a ) = Λ şi AR = a
altfel
{deplasare capăt stâng}
p = AR
cât timp llink ( p ) ≠ Λ execută
p = llink ( p )
{legare nod}
llink ( p ) = a şi rlink (a ) = p

- pentru introducerea după nodul cu adresa specificată prin t în sensul de la


stânga la dreapta, notată x ⇒ NEXT (t , L ) , algoritmul este

GETCEL(a )
dacă a = Λ
atunci overflow
altfel
{completare nod}
inf (a ) = x , llink (a ) = t şi rlink (a ) = rlink (t )
{legare la stânga}
rlink (t ) = a
{legare la dreapta}
p = rlink (a )
dacă p ≠ Λ
atunci llink ( p ) = a
altfel {*} AR = a

Alternatica notată {*} se aplică doar în cazul în care pentru lista dublu
înlănţuită se memorează adresa capătului drept al listei prin variabilă AR .
Dacă în definirea listei capătul drept nu este memorat, atunci linia
corespunzătoare acestei alternative nu este prezentă în algoritmul prezentat.
Pentru cazul în care introducerea nodului nou se face înaintea celui indicat prin
66
adresa t , considerată tot în sensul de la stânga la dreapta, se va proceda în mod
similar.
- pentru depunerea la capătul drept al listei, x ⇒ RIGHT (L ) , procedăm după
cum urmează:
a. dacă adresa capătului drept este dată prin variabila AR

GETCEL(a )
dacă a = Λ
atunci overflow
altfel inf (a ) = x , rlink (a ) = Λ , llink (a ) = AR şi
AR = a

b. dacă pentru accesul la listă se indică doar adresa capătului stâng AL

GETCEL(a )
dacă a = Λ
atunci overflow
altfel {completare nod}
inf (a ) = x şi rlink (a ) = Λ
dacă AL = Λ
atunci llink (a ) = Λ şi AL = Λ
altfel {deplasare capăt drept}
p = AL
cât timp rlink ( p ) ≠ Λ execută
p = rlink ( p )
{legare nod}
llink (a ) = p şi rlink ( p ) = a .

Dacă extragerea unei valori din listă se face într-o variabilă z ,


- pentru extragerea din capătul drept, notată z ⇐ RIGHT (L )
a. dacă se cunoaşte adresa capătului drept, AR

dacă AR = Λ
atunci underflow
altfel a = AR , z = inf (a ) , AR = llink (a )
FREECEL(a )
dacă AR ≠ Λ
arunci rlink ( AR ) = Λ
altfel {**} AL = Λ
67
Ultima linie a procesului de extragere notată prin {**} apare doar
în cazul în care, pe lângă adresa capătului drept, se reţine pentru lista dublu
înlănţuită şi capătul din stânga, desemnat printr-o variabilă AL .
b. dacă se cunoaşte doar adresa capătului stâng AL

dacă AL = Λ
atunci underflow
altfel
{deplasare la capătul drept}
p = AL
cât timp rlink ( p ) execută p = rlink ( p )
q = llink ( p )
{extragere nod}
z = inf ( p ) şi FREECEL( p )
{stabilire capăt drept nou}
dacă q = Λ
atunci AL = Λ
altfel rlink (q ) = Λ ;

- pentru extragerea din capătul stâng, notată z ⇐ LEFT (L ) , se realizează într-o


manieră similară cu cea prezentată la extragerea din capătul drept.
Considerăm suplimentar operaţia de eliminare din lista dublu înlănţuită a
unui nod pentru care se indică adresa prin t ; considerăm că s-au realizat deja
eventualele operaţii de prelucrare a informaţiei din nod. Vom considera că pentru
accesul la lista dublu înlănţuită se reţin atât capătul stâng AL , cât şi cel drept AR .
Procesul este descris prin algoritmul:

q = llink (t )
dacă q = Λ
atunci AR = rlink (t )
altfel rlink (q ) = rlink (t )
q = rlink (t )
dacă q = Λ
atunci AL = llink (t )
altfel llink (q ) = llink (t )
FREECEL(t ) .

68
Prezentăm, în continuare, modalităţile specifice implementării listelor
dublu înlănţuite particulare. Facem observaţia că implementarea unei stive ca listă
dublu înlănţuită nu este justificată datorită lucrului la un singur capăt, caz în care
activităţile specifice implică un singur sens de parcurgere.

III.4.5. Implementarea cozilor cu dublă înlănţuire

Vom considera că o coadă C este implementată ca o listă dublu înlănţuită


şi că pentru acces se reţin baza cozii B , considerată la capătul stâng al listei, şi
vârful cozii V , considerat la capătul drept al listei.
Iniţializarea cozii, C = ∅ , se realizează prin atribuirile
B = Λ şi V = Λ
Depunerea în coadă a unei informaţii x , x ⇒ C implică procesul

GETCEL(a )
dacă a = Λ
atunci overflow
altfel
inf (a ) = x , llink (a ) = Λ , rlink (a ) = B şi
B=a
dacă V = Λ
atunci V = B .

Extragerea valorii din vârful cozii într-o variabilă z , z ⇐ C se realizează


prin

dacă V = Λ
atunci underflow
altfel
a = V , z = inf (a ) ,
V = llink (a ) şi FREECEL(a )
dacă V = Λ
atunci B = Λ
altfel rlink (V ) = Λ .

În cazul inplementării unei cozi complete, operaţiile de depunere şi


extragere se realizează la ambele capete ale listei dublu înlănţuite, procesele de
depunere la vârf şi de extragere la bază fiind similare celor prezentate mai sus.

69
III.4.6. Implementarea cozilor circulare cu dublă înlănţuire

Avantajul major al cozilor circulare implementate prin liste dublu


înlănţuite este acela al reţinerii unei singure informaţii pentru accesul la coadă.
Acest avantaj este datorat posibilităţii de parcurgere a elementelor în ambele
sensuri. Vom considera coada C , la care accesul se face prin variabila B care
indică baza cozii, şi că trecerea spre vârful cozii este dat prin legăturile rlink .
Astfel, vârful cozii va fi nodul dat de legătura llink a bazei.
Iniţializarea cozii, c = ∅ , se obţine prin iniţializarea
B=Λ
Depunerea unei informaţii x în coadă, x ⇒ C , se realizează prin

GETCEL(a )
dacă a = Λ
atunci overflow
altfel
inf (a ) = x
dacă B = Λ
atunci
B = a , rlink (a ) = a
llink (a ) = a
altfel
rlink (a ) = rlink (B ) ,
llink (a ) = B
rlink (llink (B )) = a ,
llink (B ) = a .

Extragerea valorii din vârful cozii într-o variabilă z , z ⇐ C , se face prin

dacă B = Λ
atunci underflow
altfel
a = llink (B ) şi z = inf (a )
dacă llink (a ) = rlink (a )
atunci B = Λ
altfel
rlink (llink (a )) = B
llink (B ) = llink (a )
FREECEL(a )
70
III.4.7. Implementarea cozilor cu priorităţi dublu înlănţuite

Considerăm că o coadă cu priorităţi CP are nodurile de forma


(llink , inf , prior , rlink ) , unde, faţă de o coadă definită anterior, intervine câmpul
prior de specificare a priorităţilor pentru informaţia din nod.
Implementarea simplă a cozilor cu priorităţi este printr-o listă liniară dublu
înlănţuită în care baza cozii este indicată prin variabilă B , iar vârful este indicat
prin V .
Iniţializarea CP = ∅ este dată prin iniţializările
B = Λ şi V = Λ .
Extragerea informaţiei aflate în vârful cozii cu priorităţi coincide cu
procesul descris pentru cozile simple.
Depunerea informaţiei noi x cu prioritatea p , notată ( x, p ) ⇒ CP , se
realizează prin algoritmul:

GETCEL(a )
dacă a = Λ
atunci oberflow
altfel
inf (a ) = x şi prior (a ) = p
dacă B = Λ
atunci
llink (a ) = Λ , rlink (a ) = Λ .
B = a, V = a
altfel
p=B
cât timp llink ( p ) ≠ Λ şi inf (llink (a )) > p execută
p = llink ( p )
dacă llink ( p ) = Λ şi inf (llink (a )) > p
atunci
llink ( p ) = a , rlink (a ) = p
llink (a ) = Λ , V = a
altfel
llink (a ) = p , rlink (a ) = rlink ( p )
rlink ( p ) = a , p = rlink (a )
dacă p = Λ
atunci B = p
altfel llink ( p ) = a .

71
IV. STRUCTURI DE DATE NELINIARE
Acest capitol introduce modalităţile de memorare a unor structuri de date
neliniare dintre care putem enumera: arborii binari şi oarecare, structurile de tip
reţea planară şi spaţială şi structurile de liste.

IV.1. Reţele planare şi spaţiale

Un prim exemplu de structură neliniară este structura în care informaţia


poate fi plasată în punctele determinate de produsul cartezian {1,..., n}× {1,..., m} .
Deoarece punctele acestui produs cartezian pot fi reprezentate ca puncte în plan,
vom spune că o astfel de structură este de tip reţea planară. În mod similar,
informaţia poate fi plasată în puncte determinate de produsul cartezian
{1,..., n}× {1,..., m}× {1,..., p} pentru care fiecare punct poate fi reprezentat ca
punct în spaţiu, obţinându-se astfel o structură numită reţea spaţială.
Pentru implementarea structurilor planare deplasarea de la un nod la altul
se poate realiza atât pe orizontală, cât şi pe verticală. Similar reprezentărilor în
plan, pentru fiecare modalitate de deplasare se poate stabili un sens pozitiv şi un
sens negativ al deplasării.
Dacă pentru deplasarea în reţeaua planară într-o implementare a acesteia se
consideră doar sensurile pozitive de deplasare (să spunem deplasare la dreapta
pentru deplasarea pe orizontală şi deplasare sus pentru deplasarea pe verticală),
atunci pentru trecerea de la un nod la un vecin al său sunt necesare două informaţii
de legătură, una pentru legarea pe orizontală şi unul pentru legarea pe verticală. În
acest mod un nod al structurii trebuie să fie de forma (inf , xlink , ylink ) , unde
inf este informaţia din nod, xlink este legătura logică la următorul nod în sens
pozitiv pe orizontală şi ylink este legătura logică la următorul nod în sens pozitiv
pe verticală. O structură de acest tip va fi numită structură reţea planară simplu
înlănţuită.
Similar reţelelor planare, pentru reţelele spaţiale considerarea doar a
sensurilor pozitive de deplasare (deplasare la dreapta, deplasare sus şi deplasare în
faţă, adică deplasarea pe o a treia axă de coordonate) conduce la noduri de forma
(inf , xlink , ylink , zlink ) , unde inf este informaţia din nod, xlink este legătura
logică la următorul nod în sensul deplasării la dreapta, ylink este legătura logică
la următorul nod în sensul deplasării sus şi zlink este legătura logică la următorul
nod în sensul deplasării în faţă. O structură de acest tip va fi numită structură reţea
spaţială simplu înlănţuită.

72
Dacă într-o structură planară se consideră ambele sensuri de deplasare pe
fiecare axă (deplasarea la dreapta şi deplasare la stânga pentru legarea pe axa
orizontală; deplasare sus şi deplasare jos pentru legarea pe axa verticală), atunci
un nod al structurii este de forma (inf , xnlink , xplink , ynlink , yplink ) . O astfel
de structură va fi numită structură reţea planară dublu înlănţuită. Similar, pentru
structura spaţială în care intervin în plus deplasarea în faţă şi deplasarea în spate
(deplasările pe a treia axă de coordonate) se obţin noduri de forma
(inf , xnlink , xplink , ynlink , yplink , znlink , zplink ) şi structura se va numi
structură reţea spaţială dublu înlănţuită.
Structuri planare şi cele spaţiale sunt utile pentru memorarea unor reţele
întâlnite în lumea reală, de exemplu, memorarea unei reţele stradale dintr-un oraş
se poate realiza prin intermediul unei reţele planare, în timp ce memorarea unei
reţele electrice pentru un imobil se poate face prin intermediul unei reţele spaţiale.
Un caz particular de structură reţea planară poate fi considerat cazul
implementării unei matrici în care deplasarea orizontală este trecerea de la o
coloană la alta, în timp ce deplasarea verticală corespunde trecerii de la o linie la
alta. Sensurile pozitive de deplasare pot fi considerate ca fiind corespondente
creşterii indicilor de coloană şi linie.
Pentru a realiza implementarea unei matrici sub forma unei reţele planare
simplu înlănţuite vom considera că nodurile sunt de forma (inf , nextc, nextr ) .
Astfel, dacă un nod a corespunde elementului din matrice de indici (i, j ) , care are
valoarea inf (a ) , şi dacă elementul nu se află în ultima coloană (caz în care
nextc = Λ ), atunci nextc(a ) identifică elementul din matrice de indici (i, j + 1) .
Similar, dacă elementul nu se găseşte în ultima linie (când nextr = Λ ), atunci
nextr (a ) se referă la elementul din matrice de indici (i + 1, j ) . Pentru accesul la
elementele matricei va fi necesar, ţinând cont de cele de mai sus, să specificăm
adresa unde se găseşte elementul de indici (1,1) ceea ce se realizează printr-o
variabilă M .
Principiul de creare a unei matrici, implementată ca o structură reţea
planară, este de generare a primei linii (coloane) şi extinderea matricii cu linia
(coloana) următoare prin bordare. Prezentăm modalitatea de creare a matricei prin
linii, considerarea coloanelor producând un algoritm similar. Considerăm că
matricea are n linii şi m coloane.

{generarea primului element}


GETCEL(a ) , M = a , inf (a ) = b(1,1) ,
nextr (a ) = Λ , nextc(a ) = Λ , p = a , t = a
{generarea restului primei linii}

73
pentru j = 2,..., m execută
GETCEL(a ) , inf (a ) = b(1, j ) , nextc(a ) = Λ ,
nextr (a ) = Λ , nextc( p ) = a , p = a
{aplicarea bordării cu linii}
pentru i = 2,..., n execută
{primul element al liniei i }
GETCEL(a ) , inf (a ) = b(i,1) , nextr (t ) = a
p =a, q =t, t =a
nextc(a ) = Λ , nextr (a ) = Λ
{restul liniei i }
pentru j = 2,..., m execută
GETCEL(a ) , inf (a ) = b(i, j ) , q = nextc(q )
nextc(a ) = Λ , nextr (a ) = Λ , nextr (q ) = a
nextc( p ) = a , p = a .

Facem observaţia că în algoritmul de mai sus am considerat că valorile


matricei care se creează se găsesc elementele b(i, j ) . La fel de bine putem
considera că b(i, j ) reprezintă o aplicaţie (funcţie) de obţinere a valorilor matricei.
Bordarea matricei cu o nouă linie poate fi realizată uşor, considerând doar
o singură aplicare a ciclului de bordare, precedată de parcurgerea primei coloane,
adresa ultimului element al acestei coloane fiind reţinută în variabila t .
Considerând o matrice implementată ca mai sus, dăm în continuare
procesul de determinare a adresei elementului din linia i şi coloana j într-o
variabilă r . Presupunem, de asemenea, că nu s-a reţinut dimensiunea matricei şi că
dacă dimensionalizarea este depăşită adresa obţinută este Λ .

dacă M = Λ
atunci r = Λ
altfel
a = M , k =1
{parcurgerea liniilor}
cât timp a ≠ Λ şi k < i execută
a = nextr (a ) şi k = k + 1
dacă a = Λ
atunci r = Λ
altfel
k =1
{parcurgerea coloanelor}

74
cât timp a ≠ Λ şi k < j
execută
a = nextc(a )
k = k +1
r = a.

În capitolul II am văzut modalităţile de implementare a tablourilor cu


formate speciale, în care un număr mare de elemente sunt egale cu zero (matrici
triunghiulare şi bandă), dar aceste zerouri sunt grupate în zone compacte ale
matricei. Un caz special de matrice cu mulţi coeficienţi egali cu zero este cel al
matricelor rare, matrici în care numărul de coeficienţi nenuli este relativ foarte mic
comparativ cu dimensiunea matricii.
Implementarea matricilor prin structura reţea planară conduce la o
implementare a matricilor rare dacă prin legăturile stabilite evităm nodurile în care
valoarea memorată este zero. În acest caz, pentru a nu pierde controlul indicilor
corespunzători fiecărei valori, este necesar ca aceştia să fie memoraţi împreună cu
valoarea coeficientului din matrice. Vom obţine astfel noduri de forma
(inf , col, raw, nextc, nextr ) , unde nextc şi nextr sunt ca mai sus, iar inf ≠ 0
reprezintă valoarea coeficientului din matrice de indici (raw, col ) .
Considerând că matricea are coeficienţii reali, nodul de memorare a
matricilor rare are dimensiunea de 18 bytes, deci de 3 ori spaţiul necesar
memorării unei valori reale. Astfel, ca implementarea unei matrici, sub forma pe
care o propunem, să fie eficientă din punct de vedere al memoriei utilizate, este
necesar ca numărul maxim al coeficienţilor nenuli din matrice să fie mai mic decât
o treime din produsul dimensiunilor matricii.
Pentru a putea optimiza timpul de acces la valorile matricii, vom considera
abordarea în care se realizează memorarea integrală a primei linii şi coloane din
matrice, cu adresa matricii memorată într-o variabilă R .
O primă operaţie este de generare a matricii On ,m (matricea cu toţi
coeficienţii egali cu zero formată cu n linii şi m coloane). Pentru aceasta se
foloseşte procedeul:

GETCEL(R ) , inf (R ) = 0 , col (R ) = 1 , raw(R ) = 1


nextc(R ) = Λ , nextr (R ) = Λ
{restul primei linii}
a=R
pentru i = 2,..., m execută
GETCEL(b ) , nextc(a ) = b , a = b
inf (b ) = 0 , col (b ) = i , raw(b ) = 1
nextc(b ) = Λ , nextr (b ) = Λ

75
{restul primei coloane}
a=R
pentru i = 2,..., n execută
GETCEL(b ) , nextr (a ) = b , a = b
inf (b ) = 0 , col (b ) = 1 , raw(b ) = i
nextc(b ) = Λ , nextr (b ) = Λ .

Determinarea valorii corespunzătoare coeficientului (i, j ) , 1 ≤ i ≤ n ,


1 ≤ j ≤ m , din matrice, valoare depusă într-o variabilă z , se realizează prin:

a = R, z =0
pentru k = 2,..., j execută a = nextc(a )
cât timp nextr (a ) ≠ Λ şi raw(a ) < i execută a = nextr (a )
dacă raw(a ) = i
atunci z = inf (a ) .

Modificarea valorii nenule a coeficientului (i, j ) , 1 ≤ i ≤ n , 1 ≤ j ≤ m ,


din matrice printr-o valoare nenulă x utilizează procedeul:

a=R
pentru k = 2,..., j execută a = nextc(a )
cât timp raw(a ) < i execută a = nextr (a )
inf (a ) = x .

Modificarea valorii nule a coeficientului (i, j ) , 1 ≤ i ≤ n , 1 ≤ j ≤ m , din


matrice printr-o valoare nenulă x (inserarea unei noi valori nenule) implică
aplicarea algoritmului:

{poziţionare coloană j }
a=R
pentru k = 2,..., j execută a = nextc(a )
dacă i = 1
atunci inf (a ) = x
{poziţionare linie i }
b=R
pentru k = 2,..., i execută b = nextr (b )
dacă j = 1

76
atunci inf (b ) = x
{coeficient interior}
dacă i ≠ 1 şi j ≠ 1
atunci
{deplasare pe linii}
dacă nextr (a ) ≠ Λ
atunci an = a
cât timp nextr (an ) ≠ Λ şi raw(an ) < i execută
a = an şi an = nextr (an )
{deplasare pe coloane}
dacă nextc(b ) ≠ Λ
atunci bn = b
cât timp nextc(bn ) ≠ Λ şi col (bn ) < j execută
b = bn şi bn = nextc(bn )
{inserare nod}
GETCEL( p ) , inf ( p ) = x ,
col ( p ) = j , raw( p ) = i
nextc( p ) = nextc(b ) , nextc(b ) = p
nextr ( p ) = nextr (a ) , nextr (a ) = p .

Modificarea valorii nenule a coeficientului (i, j ) , 1 ≤ i ≤ n , 1 ≤ j ≤ m ,


din matrice prin valoarea zero (eliminarea unei valori nenule) se face prin aplicarea
procesului:

{poziţionare coloană j }
a=R
pentru k = 2,..., j execută a = nextc(a )
dacă i = 1
atunci inf (a ) = 0
{poziţionare linie i }
b=R
pentru k = 2,..., i execută b = nextr (b )
dacă j = 1
atunci inf (b ) = 0
{coeficient interior}
dacă i ≠ 1 şi j ≠ 1
atunci
{deplasare pe linii}
77
an = a
cât timp nextr (an ) ≠ Λ şi raw(an ) < i execută
a = an şi an = nextr (an )
{deplasare pe coloane}
cât timp nextc(b ) ≠ an execută b = nextc(b )
{eliminare nod)
nextr (a ) = nextr (an ) , nextc(b ) = nextc(an )
FREECEL(an ) .

IV.2. Implementarea structurilor de liste

Pentru a putea realiza structuri complexe se poate realiza combinarea mai


multor liste, elemente din unele liste făcând trimiteri către elementele din alte liste.
Celulele utilizate pot avea mai multe legături şi eventual marcaje prin care să se
indice natura şi conţinutul celulei respective. O astfel de construcţie va purta
numele de structură de liste. O structură de liste este o mulţime de liste legate
pentru care digraful corespunzător ei este conex.
Definiţie. Dacă o celulă dintr-o listă L1 conţine o legătură către o celulă
din altă listă L2 (de obicei prima celulă a listei), spunem că L2 este o sublistă a
listei L1 .
Definiţie. Dacă într-o structură de liste digraful corespunzător ei conţine
cicluri, spunem că structura de liste este reentrantă. În particular, o listă care este
propria sublistă se numeşte listă reentrantă.
Definiţie. Dacă două liste L1 şi L2 au ca sublistă acceaşi listă L ,
spunem că L1 şi L2 distribuie sublista L .
O aplicaţie pentru structurile de liste este reprezentată de modalitatea de
reprezentare a unui graf orientat prin intermediul listelor. Pentru realizarea acestei
reprezentări vom considera că nodurile au structura (tip, date ) , unde tip indică
tipul datelor conţinute în nod şi poate fi 1, dacă nodul conţine informaţie utilă, şi 2,
dacă nodul conţine doar informaţii de legătură.
Dacă pentru un nod tip = 1 , atunci date este perechea (inf , alist ) , unde
inf este informaţia utilă (identificarea unui vârf al grafului orientat), iar alist este
o legătură către o listă liniară care conţine arcele care pleacă din vârful identificat
prin inf .
Dacă tip = 2 , atunci date este perechea (vlist , anext ) , unde vlist este o
legătură către o identificare de vârf (o celulă cu tip = 1 ) care este vârf destinaţie
pentru arcul corespunzător nodului, iar anext este o legătură către următorul arc
cu acelaşi nod sursă.

78
Celulele cu tip = 1 vor fi considerate celule de date, iar celulele cu
tip = 2 vor fi celule de legătură.
Astfel, pentru un graf orientat G = (V , E ) numărul de celule cu tip = 1 va
fi egal cu V , în timp ce numărul celulelor cu tip = 2 va fi egal cu E .
Pentru exemplificarea modului de realizare a reprezentării prin structuri de
liste a grafurilor orientate să considerăm graful

Pentru acest graf se obţine următoarea structură de liste

IV.3. Structuri arborescente

IV.3.1. Arbori

Cele mai importante structuri neliniare care apar în algoritmi sunt


structurile arborescente prin care se descriu diverse ramificaţii.
Definiţie. Un arbore este o mulţime finită A de unul sau mai multe noduri
din care se distinge un nod R numit rădăcină, toate celelalte noduri fiind
partiţionate în mulţimile disjuncte A1 , A2 , …, An , n ≥ 0 . Fiecare dintre aceste
mulţimi este, la rândul ei, un arbore numit subarbore al rădăcinii R .
Definiţie. Numărul de subarbori ai unui nod se numeşte gradul acelui
nod. Nodurile de grad 0 se numesc noduri terminale sau frunze. Nodurile cu
gradul diferit de 0 se numesc noduri de ramificare.
Definiţie. Dacă x este un nod al unui arbore, rădăcinile subarborelui lui
x se numesc fii lui x , nodul x fiind numit tatăl acestor rădăcini. Doi fii ai unui
nod sunt fraţi între ei.
79
Definiţie. O mulţine de arbori se numeşte pădure.
Definiţie. Într-un arbore numărul de noduri prin care trebuie să trecem
pentru a ajunge de la rădăcină la un nod x , inclusiv R şi x , se numeşte nivelul
nodului x . Nivelul maxim al nodurilor dintr-un arbore se numeşte înălţimea
arborelui.
Ţinând cont de ultima definiţie se poate observa că rădăcina R a unui
arbore are nivelul 1, rădăcina oricărui subarbore al lui R are nivelul 2 şi, în
general, rădăcina unui subarbore al unui nod x are nivelul cu 1 mai mare decât
nivelul nodului x .
Definiţie. Dacă ordinea relativă a subarborilor fiecărui nod în parte este
semnificativă pentru definirea arborelui, spunem că avem un arbore ordonat.
Definiţie. Dacă A este un arbore în care fiecare nod are cel mult doi
subarbori, unul stâng şi unul drept, şi dacă un nod are un singur subarbore se
precizează dacă este subarbore stâng sau drept, atunci A se numeşte arbore
binar.
Definiţie. Numim parcurgerea arborelui o examinare sistematică a
nodurilor astfel încât fiecare nod să fie considerat o singură dată.
Prin parcurgerea unui arbore se determină o ordine totală asupra nodurilor
din arbore. În acest mod pentru fiecare nod se poate spune care nod îl precede şi
care îl succede logic.
Definiţie. Un arbore (pădure) în care în fiecare nod are asociată o
identificare astfel încât prin concatenarea acestora pe fiecare drum de la rădăcină
la noduri se formează cuvinte distincte se numeşte arbore (pădure) calificat(ă).

IV.3.2. Arbori binari

Datorită modului de definire a arborilor binari, un nod pentru


implementarea lor este de forma (llink , inf , rlink ) , unde inf este informaţia
utilă, llink este legătura către rădăcina subarborelui stâng al nodului şi rlink este
legătura spre subarborele drept. Pentru accesul la arborele binar este suficientă
cunoaşterea adresei nodului rădăcină, printr-o variabilă R .
O operaţie foarte importantă asupra arborilor binari este cea de parcurgere,
prin aceasta obţinându-se informaţiile depuse în arbore într-o anumită ordine. În
funcţie de aplicaţia la care arborii binari sunt utilizaţi, celelalte operaţii care se
efectuează trebuie să prezerve această ordine.
Ca exemplu de memorare a unui arbore binar să considerăm arborele:

80
Reprezentarea acestui arbore binar este:

Modurile posibile de parcurgere sunt:


@ parcurgerea în preordine care impune utilizarea informaţiei din rădăcină şi
apoi parcurgerea în preordine, întâi a subarborelui stâng şi apoi a subarborelui
drept;
@ parcurgerea în inordine prin care se face o parcurgere în inordine a
subarborelui stâng, se utilizează informaţia existentă în nodul rădăcină şi în
final se parcurge în inordine subarborele drept;
@ parcurgerea în postordine în care se parcurge întâi în postordine subarborele
stâng, apoi se parcurge în postordine subarborele drept şi, în final, de utilizează
informaţia existentă în rădăcină
Realizarea practică a parcurgerii arborilor binari cu nodurile definite ca
mai sus se poate face atât prin algoritmi iterativi, cât şi prin algoritmi recursivi.
Pentru algoritmii iterativi există dezavantajul necesităţii de a folosi o strivă
suplimentară gestionată explicit în aplicaţie, în timp ce la rezolvările recursive
această stivă nu mai este necesară, chiar dacă principial, din modul de rezolvare a
recursivităţii în limbajele de programare, se utilizează tot o stivă, dar fără ca
aceasta să mai fie controlată explicit (se lucrează prin intermediul stivei program,
care, fiind de dimensiune fixă, definită ca element static, poate fi uşor depăşită, de
unde şi principalul dezavantaj al acestui mod de lucru).
Cum este şi firesc, cel mai simplu mod de realizare a parcurgerii este cel
prin definirea proceselor recursive:
- pentru parcurgerea în preordine

81
rutină preordine( v )
dacă v ≠ Λ
{utilizare inf (v ) }
preordine( llink (v ) )
preordine( rlink (v ) )

iar pentru parcurgerea arborelui se utilizează apelul


preordine( R )

- pentru parcurgerea în inordine

rutină inordine( v )
dacă v ≠ Λ
inordine( llink (v ) )
{utilizare inf (v ) }
inordine( rlink (v ) )

iar parcurgerea se face prin apelul


inordine( R )
- pentru parcurgerea în postordine

rutină postordine( v )
postordine( llink (v ) )
postordine( rlink (v ) )
{utilizare inf (v ) }

parcurgerea fiind obţinută în urma apelului


postordine( R )
Pentru procesele iterative se consideră o stivă S în care informaţia utilă este o
adresă a unui nod din arbore şi pentru care s-au definit operaţiile S = ∅
(iniţializare stivă), x ⇒ S (depunerea în stivă) şi x ⇐ S (extragerea din stivă).
Pentru parcurgere se utilizează următorii algoritmi:
- parcurgerea în preordine este realizată prin
S = ∅ dacă R ≠ Λ atunci R ⇒ S
cât timp S ≠ ∅ execută
x⇐S
{utilizează inf ( x ) }
dacă rlink ( x ) ≠ Λ atunci rlink ( x ) ⇒ S
dacă llinl ( x ) ≠ Λ atunci llink ( x ) ⇒ S

82
- parcurgerea în inordine este asigurată de algoritmul

S =∅
dacă R ≠ Λ atunci
x=R
cât timp llink ( x ) ≠ Λ execută x ⇒ S şi x = llink ( x )
x⇒S
cât timp S ≠ ∅ execută
x⇐S
{utilizează inf ( x ) }
dacă rlink ( x ) ≠ Λ atunci
x = rlink ( x )
cât timp llink ( x ) ≠ Λ execută
x ⇒ S şi x = llink ( x )
x⇒S

O parcurgere mai simplă se obţine în cazul în care se modifică formatul


stivei de manevră. Modificarea constă într-un câmp suplimentar de informaţie
utilă, ceea ce face ca formatul informaţiei din stiva de parcurgere să fie
(tip, adr ) , unde tip este o valoare bivalentă care prin 1 indică parcurgerea
nodului pe legătura llink şi 2 indică parcurgerea pe legătura rlink şi adr
este adresa nodului care urmează a fi supus prelucrării. Pentru această stivă
considerăm că sunt definite operaţiile S = Λ (iniţializare stivă), (t , x ) ⇒ S
(depunere) şi (t , x ) ⇐ S (extragere). Utilizând o astfel de stivă, procedeul de
parcurgere în inordine devine:
S =∅
dacă R ≠ Λ atunci (1, R ) ⇒ S
cât timp S ≠ ∅ execută
(t , x ) ⇐ S
dacă t = 1
atunci
(2, x ) ⇒ S
dacă llink ( x ) ≠ Λ
atunci (1, llink ( x )) ⇒ S
altfel
{utilizează inf ( x ) }
dacă rlink ( x ) ≠ Λ
atunci (1, rlink ( x )) ⇒ S
83
- parcurgerea în postordine este efectul următorului proces, în care vom
considera de la început o stivă cu informaţia utilă de forma (tip, adr ) , unde, de
această dată, tip poate avea valorile 1 pentru a indica continuarea cu
parcurgerea legăturii llink , 2 pentru continuarea cu legătura rlink şi 3 pentru
utilizarea informaţiei utile din nod.
S =∅
dacă R ≠ Λ atunci (1, R ) ⇒ S
cât timp S ≠ ∅ execută
(t , x ) ⇐ S
dacă t = 3 atunci
{utilizează inf ( x ) }
altfel dacă t = 2 atunci
(3, x ) ⇒ S
dacă rlink ( x ) ≠ Λ atunci (1, rlink ( x )) ⇒ S
altfel
(2, x ) ⇒ S
dacă llink ( x ) ≠ Λ atunci (1, llink ( x )) ⇒ S

Procesele de depunere şi eliminare a unui nod dintr-un arbore binar trebuie


să ţină cont de modalitatea de utilizare a arborelui astfel încât, după operaţie,
parcurgerea să producă o ordine corectă a elementelor.
Indiferent de utilizarea unei stive, controlată de utilizator sau a stivei
program, parcurgerile pe arborii binary, definiţi mai sus impun consumuri mari de
spaţii de memorie suplimentare. Acest impediment poate fi eliminat dacă
implementarea este concepută astfel încât în locul unei legături Λ să fie prezentă o
trimitere către nodul care urmează conform modului de parcurgere ales pentru
arbore. Structura care se obţine prin aceste legături suplimentare poartă numele de
arbore binar însăilat.
O problemă care intervine este aceea de a face distincţie între o legătură
normală dintr-un nod şi o legătură de însăilare. Pentru a putea realiza o astfel de
distincţie este necesară prezenţa unei informaţii suplimentare ataşate fiecărei
legături sau la nivelul întregului nod.
Pentru exemplificarea modurilor de implementare considerăm arborele
binar:

84
O modalitate de asigurare a distincţiei de utilizare a legăturilor se aplică
atunci când un singur câmp din cele două de legătură este folosit pentru însăilare.
Considerăm că un arbore are nodurile de forma (inf , llink , rlink , tiprl ) , unde
inf , llink şi rlink sunt definite normal pentru un arbore binary, iar tiprl are
valoarea 1 dacă rlink este o legătură normală şi 2 dacă este o legătură de
înlănţuire.
Pentru arborele binar de mai sus se obţine, în acest caz, implementarea:

Această implementare este cunoscută ca arbore binar simplu însăilat.


Pentru această implementare toate legăturile rlink , cu excepţia uneia, sunt diferite
de Λ . Legătura pentru care rlink este egal cu Λ indică terminarea operaţiei de
parcurgere a arborelui binar.
Similar cu prezentarea de mai sus se poate imagina o însăilare care să
folosească ambele informaţii de legătură şi aceasta va conduce la un arbore binar
dublu însăilat. Pentru acest mod de implementare distincţia se poate face
independent pe fiecare tip de legătură prin noduri de forma
(inf , tipll , llink , rlink , tiprl ) , semnificaţiile câmpurilor fiind conforme cu cele
prezentate mai sus.

85
Pentru simplificarea construcţiilor se mai pot utiliza moduri în care
codificarea utilizării legăturilor să se facă printr-un singur câmp. Obţinem, în acest
caz, noduri de forma (inf , tipl , llink , rlink ) , unde un exemplu de codificare pentru
tipl este
- 0 pentru ambele legături efective
- 1 pentru legătura llink de însăilare şi rlink normală
- 2 când llink este o legătură normală, iar rlink se foloseşte la însăilare
- 3 pentru cazul utilizării ambelor legături la însăilare

IV.3.2. Arbori oarecare

Arborii oarecare sunt structuri neliniare cu o multitudine de legături în


fiecare nod. Pentru implementarea unei astfel de structuri se pot aborda două
moduri de memorare, prin structuri de liste şi prin structuri de legătură fiu-frate.
Implementarea arborilor oarecare prin structuri de liste utilizează două
tipuri de noduri. Nodul de informaţie utilă de forma (1, inf , llfiu ) , unde 1 indică
tipul de nod, inf conţine informaţia utilă din nod, iar llfiu este o legătură către o
listă de legături către fiii nodului curent. Nodul din lista de legături la fii este de
forma ( 2, lnodi, lnext ) , unde 2 indică tipul de nod, lnodi este o legătură către un
nod de informaţie utilă, iar lnext este legătura către următorul nod din lista de
legături la fii. Câmpul prin care se indică tipul de nod va fi numit, în continuare,
tipn .
Pentru exemplificarea implementării prin structuri de liste să considerăm
următorul arbore oarecare:

86
Structura de liste rezultată pentru implementarea acestui arbore este:

Pentru reţinerea structurii de liste este necesară o variabilă R care să


indice rădăcina arborelui oarecare. Pentru specificarea unui arbore oarecare vid se
face atribuirea
R=Λ .
Completarea arborelui oarecare se face prin depunerea de noduri ca ultimi
fii ai unui nod existent, pentru care se indică nodul tată şi informaţia utilă pentru
nodul care se adaugă ( inft şi, respectiv, infn ). În procesul de depunere este
necesară căutarea nodului tată, deci o operaţie de parcurgere, ceea ce impune
utilizarea unei stive S pentru continuarea parcurgerilor pe legăturile neutilizate.
Depunerea se realizează prin algoritmul:

87
dacă R = Λ
atunci
dacă inft nedefinit
atunci
GETCEL ( w1)
tipn ( w1) = 1
inf ( w1) = infn
llfiu ( w1) = Λ
altfel eroare arbore vid
altfel
dacă inft nedefinit
atunci eroare arbore nevid
altfel
S = ∅ , R → S , err = true
cât timp S ≠ ∅ execută
v←S
dacă tipn ( v ) = 2
atunci
dacă lnext ( v ) ≠ Λ
atunci lnext ( v ) → S
altfel continuă
lnodi ( v ) → S
altfel
dacă inf ( v ) ≠ inft
atunci
dacă llfiu ( v ) ≠ Λ
atunci llfiu ( v ) → S
altfel continuă
altfel
{depunere nod}
GETCEL ( w1)
tipn ( w1) = 1
inf ( w1) = infn
llfiu ( w1) = Λ
GETCEL ( w2 )
88
tipn ( w2 ) = 2
lnodi ( w2 ) = w1
lnext ( w2 ) = Λ
dacă llfiu ( v ) = Λ
atunci llfiu ( v ) = w2
altfel
v = llfiu ( v )
cât timp lnext ( v ) ≠ Λ
execută
v = lnext ( v )
lnext ( v ) = w2
err = false , S = ∅
dacă err atunci eroare tată negăsit

Pentru parcurgerea unui arbore reprezentat sub forma de structură de liste


este necesară o stivă S în care să se reţină adresele nodurilor la care nu s-a utilizat
legătura stângă. Parcurgerea se poate realiza în inordine, adică utilizarea
informaţiei utile, urmată de trecerea la fii, sau în postordine, adică parcurgerea
fiilor şi apoi utilizarea informaţiei utile.
Parcurgerea în inordine pentru un arbore oarecare se realizează prin
algoritmul:
S =∅ , R→S
cât timp S ≠ ∅ execută
v←S
dacă tipn ( v ) = 2
atunci
dacă lnext ( v ) ≠ Λ
atunci lnext ( v ) → S
altfel continuă
lnodi → S
altfel
utilizează inf ( v )
dacă llfiu ( v ) ≠ Λ
atunci llfiu ( v ) → S
altfel continuă

89
Pentru parcurgerea în postordine stiva utilizată va avea nodurile de forma
( u, v ) , unde v este adresa unui nod din arborele oarecare, iar u ia valoarea 1,
dacă pentru nod urmează parcurgere, şi 2, dacă nodul este parcurs şi urmează
utilizarea informaţiei din nod. Această informaţie este ignorată pentru nodurile v
pentru care tipn ( v ) = 2 . Pentru parcurgere se foloseşte algoritmul:

S = ∅ , (1, R ) → S
cât timp S ≠ ∅ execută
( u, v ) ← S
dacă tipn ( v ) = 2
atunci
dacă lnext ( v ) ≠ Λ

( )
atunci 1,lnext ( v ) → S
altfel continuă
lnodi → S
altfel
dacă u = 2
atunci utilizează inf ( v )
altfel
( 2, v ) → S
dacă llfiu ( v ) ≠ Λ
atunci
(1,llfiu ( v ) ) → S
altfel continuă

Procesele de parcurgere descrise mai sus utilizează o stivă explicită, lucru


care poate fi evitat prin utilizarea unor parcurgeri folosind recursivitatea. Pentru
parcurgerea în inordine se defineşte un subprogram de parcurgere a listei de
legătură la fii, care are următoarea formă:

rutină parcurgere_listă_fii( nod )


parcurgere_interioară( nod )
dacă lnext ( nod ) = Λ
atunci continuă
altfel parcurgere_listă_fii( lnext ( nod ) )
revenire
90
Pentru parcurgerea în inordine se defineşte subprogramul

rutină parcurgere_interioară( nod )


(
utilizare inf lnodi ( nod ) )
(
dacă llfiu lnodi ( nod ) = Λ)
atunci continuă
(
altfel parcurgere_listă_fii( llfiu lnodi ( nod ) ) )
revenire

Cu aceste definiţii parcurgerea structurii în inordine utilizează algoritmul:

dacă R ≠ Λ
atunci
GETCEL ( w )
tipn ( w ) = 2
lnodi ( w ) = R
lnext ( w ) = Λ
parcurgere_interioară( w )
FREECEL ( w )

Parcurgerea în postordine se realizează, în mod similar proceselor descrise


mai sus, schimbând poziţia utilizării informaţiei din nod în procedurile recursive.
Ca în cazul arborilor binari, poate fi evitată utilizarea explicită sau
implicită a unei stive la parcurgere, dacă structura utilizată pentru memorarea
aborelui oarecare este una însăilată. Pentru a realiza o astfel de stuctură se
consideră că fiecare nod conţine un câmp suplimentar, de tip indicator, cu valoarea
1, dacă legătura este una normală, şi 2, dacă legătura este de însăilare. Astfel,
nodurile de memorare a structurii care conţin informaţie utilă vor avea forma
(1, inf , llfiu, tipl ) , iar cele din listele de fii, ( 2, lnodi, lnext, tipl ) . Ca anterior,
primul câmp cu valoarea 1 sau 2 şi prin care se specifică tipul de nod va fi notat
tipn .
O variantă redusă este cea în care se extinde semnificaţia câmpului tipn
care va lua valorile:
- 1 pentru câmp de informaţie utilă în care llfiu este o legătură la lista de fii,
- 2 pentru câmp din lista de fii în care lnext este o legătură la următorul fiu,
- 3 pentru câmp de informaţie utilă în care llfiu este o legătură de însăilare,
- 4 pentru câmp din lista de fii în care lnext este o legătură de însăilare.

91
Pentru arborele oarecare considerat ca exemplu, implementarea sub forma
însăilată este:

Generarea formei însăilate se realizează în mod similar formei simple. În


aceleaşi condiţii, depunerea unui nod în structură se obţine prin aplicare
algoritmului:

92
dacă R = Λ
atunci
dacă inft nedefinit
atunci
GETCEL ( w1)
tipn ( w1) = 1
inf ( w1) = infn
llfiu ( w1) = Λ
tipl ( w1) = 1
altfel eroare arbore vid
altfel
dacă inft nedefinit
atunci eroare arbore nevid
altfel
err = true , v = R , depus = false
cât timp not depus execută
dacă tipn ( v ) = 2
atunci v = lnodi ( v )
altfel
dacă inf ( v ) ≠ inft
atunci
dacă llfiu ( v ) ≠ Λ
atunci
dacă tipl ( v ) = 1
atunci v = llfiu ( v )
altfel
v = llfiu ( v )
cât timp tipl ( v ) = 2
execută
v = lnext ( v )
dacă lnext ( v ) = Λ
atunci depus = true
altfel v = lnext ( v )
altfel depus = true
93
altfel
{depunere nod}
GETCEL ( w1)
tipn ( w1) = 1
inf ( w1) = infn
tipl ( w1) = 2
GETCEL ( w2 )
tipn ( w2 ) = 2
lnodi ( w2 ) = w1
llfiu ( w1) = w2
depus = true
dacă llfiu ( v ) = Λ
atunci
llfiu ( v ) = w2
tipl ( w2 ) = 1
lnext ( w2 ) = Λ
altfel
v = llfiu ( v )
cât timp ( lnext ( v ) ≠ Λ
and tipl ( v ) ≠ 2 )
execută
v = lnext ( v )
dacă lnext ( v ) = Λ
atunci
lnext ( v ) = w2
tipl ( w2 ) = 1
lnext ( w2 ) = Λ

94
altfel
tipl ( w2 ) = 2
lnext ( w2 ) = lnext ( v )
lnext ( v ) = w2
tipl ( v ) = 1
err = false
dacă err atunci eroare tată negăsit

Pentru implementarea însăilată a arborilor oarecare, procesul de parcurgere


în inordine care, aşa cum am afirmat, nu mai necesită o stivă pentru parcurgere, se
realizează prin următorul algoritm:

dacă R ≠ Λ atunci
utilizează inf ( R )
dacă llfiu ( R ) ≠ Λ atunci
v = llfiu ( R )
cât timp v ≠ Λ execută
utilizează inf ( lnodi ( v ) )

( )
dacă tipl lnodi ( v ) = 1

(
atunci v = llfiu lnodi ( v ) )
altfel
dacă tipl ( v ) = 1
atunci v = lnext ( v )
altfel
cât timp tipl ( v ) = 2
execută
v = lnext ( v )
v = lnext ( v )

Parcurgerea în postordine se realizează, în mod similar, şi o lăsăm ca


exerciţiu.

95
O implementare alternativă a arborilor oarecare este similară implementării
arborilor binari. Implementarea are la bază noduri de forma ( inf , lpfiu , lnfrate ) ,
unde inf este informaţia utilă din nod, lpfiu este legătura la primul fiu al nodului
curent, iar lnfrate este legătura către următorul frate al nodului curent.
Specificarea nodului rădăcină se face printr-o variabilă suplimentară R . Pentru
arborele oarecare considerat ca exemplu, implementarea prin legături fiu/frate
conduce la următoarea reprezentare:

Procesele de creare a arborilor oarecare şi de parcurgere a lor pentru


implementarea fiu/frate sunt realizate pe aceleaşi principii ca în cazul
implementării prin structurile de liste şi considerăm că pot fi lăsate cititorului ca
exerciţii.
Şi în cazul implementării fiu/frate, pentru a evita utilizarea unei stive
pentru parcurgere se poate imagina o formă insăilată a structurii care este similară
formei insăilate prezentate pentru arborii binari.

96
V. CĂUTARE
Problema căutării se poate enunţa astfel: fiind dată o structură de date şi o
valoare K numită cheie de căutare, trebuie localizate înregistrările din structură
care au în câmpuri asociate cheii valoarea K .
Cea mai mare parte a algoritmilor de căutare sunt concepuţi pentru
execuţia pe calculator şi din acest punct de vedere se împart în algoritmi de
căutare internă, când întreaga structură de date se găseşte în memoria internă a
calculatorului, şi algoritmi de căutare externă, când structura sau o parte a ei se
află pe o memorie externă (fişier).
Dacă în structura de date nu există două înregistrări cu chei egale se spune
că structura este cu cheie unică, iar dacă este posibil ca mai multe înregistrări să
aibă chei egale structura este cu cheie multiplă. Pentru structurile cu cheie multiplă
se pune problema dacă trebuie determinată o singură înregistrare de cheie dată sau
toate înregistrările care au acea cheie.
Algoritmii de căutare pot să se bazeze pe comparaţii de chei sau pe
semnificaţia lor. Căutarea se termină cu succes dacă a fost găsită cel puţin o
înregistrare cu cheia K sau fără succes dacă toate înregistrările au cheia diferită
de K .

V.1. Căutarea secvenţială


Căutarea secvenţială este specifică structurilor liniare. Se consideră dată o
valoare K . Trebuie să se determine înregistrarea (cheie unică sau o apariţie pentru
cheie multiplă) sau toate înregistrările (cheie multiplă) care, în câmpul asociat
cheii, conţine o valoare egală cu K , dacă o astfel de înregistrare există (căutare cu
succes).
Algoritmul de căutare pentru o structură cu cheie unică realizează o
parcurgere a structurii, pentru fiecare înregistrare Ri comparând argumentul K cu
cheia K i din înregistrare. Căutarea se termină când se găseşte un i pentru care
K = K i (căutare cu succes) sau dacă nu mai sunt înregistrări de explorat (căutare
fără succes).
Considerând că numărul total de înregistrări din structură este n , numărul
de comparaţii la căutarea fără succes este egal cu n deoarece, în acest caz, au fost
explorate toate înregistrările structurii. În cazul căutării cu succes, în cazul cel mai
favorabil, prima înregistrare are cheia egală cu K şi numărul de comparaţii este
egal cu 1. Cazul cel mai nefavorabil pentru căutarea cu succes este cel în care
ultima înregistrare a structurii are valoarea cheii egală cu K astfel că se realizează
n comparaţii prin parcurgerea în totalitate a structurii. Considerând cheile
97
echiprobabile, numărul mediu de comparaţii la căutarea cu succes se obţine ca
n +1
medie aritmetică între numărul de comparaţii din cazurile extreme, deci .
2
Forma matematică generală a algoritmului de căutare secvenţială este:

Pas 1. R este prima înregistrare, i = 1


Pas 2. dacă cheie R = K atunci cheie găsită în poziţia i altfel continuă
Pas 3. dacă există înregistrări neparcurse atunci R este următoarea
înregistrare, i = i + 1 şi se trece la pasul 2 altfel căutare fără succes

Dacă structura liniară în care se face căutarea este de tip vector cu număr
n de înregistrări, algoritmul de căutare secvenţială se transpune în:

p = 0, i =1
cât timp i ≤ n execută
dacă K i = K
atunci p = i şi i = n + 1
altfel i = i + 1
dacă p = 0
atunci căutare fără succes
altfel cheie găsită în poziţia p

Dacă structura care conţine înregistrările este una înlănţuită (simplu


înlănţuită), având legătura la următoarea înregistrare indicată prin câmpul nextn şi
prima înregistrare indicată prin variabila primn , atunci procesul de căutare poate
fi descris sub forma:

v = primn , i = 1 , p = 0 ( posadr = Λ )
cât timp v ≠ Λ execută
dacă cheie ( v ) = K
atunci p = i ( posadr = v ) şi v = Λ
altfel i = i + 1 şi v = nextn ( v )
dacă p = 0 ( posadr = Λ )
atunci căutare fără succes
altfel
cheie găsită în poziţia p
(cheie găsită la adresa posadr )

Pentru a evita dubla condiţionare a terminării algoritmului putem adăuga


în finalul structurii liniare o înregistrare care să conţină în câmpul cheie valoarea
98
K , modul de terminare a căutării urmând a fi indicat de găsirea sau nu a cheii K
în ultima înregistrare. Cu acest artificiu forma matematică generală a algoritmului
este:

Pas 1. adaugă ca ultimă înregistrare o înregistrare cu cheia K , R este prima


înregistrare şi i = 1
Pas 2. cât timp cheie ( R ) ≠ K , R este următoarea înregistrare şi i = i + 1
Pas 3. dacă R este ultima înregistrare atunci căutare fără succes altfel cheia
este găsită în poziţia i

cu forma pariculară

n = n + 1 , Kn = K , i = 1
cât timp K i ≠ K execută i = i + 1
dacă i = n
atunci căutare fără succes
altfel cheie găsită în poziţia i

pentru vectori şi

dacă primn = Λ
atunci căutare fără succes
altfel
GETCEL ( w ) , cheie ( w ) = K , nextn ( w ) = Λ
v = primn
cât timp nextn ( v ) ≠ Λ execută v = nextn ( v )
nextn ( v ) = w
v = primn , i = 1 (----------)
cât timp cheie ( v ) ≠ K execută
v = nextn ( v ) , i = i + 1 (----------)
dacă nextn ( v ) = Λ
atunci căutare fără succes
altfel
cheie găsită în poziţia i
(cheie găsită la adresa v )

pentru căutarea într-o structură înlănţuită.


Dacă structura este cu cheie multiplă, o primă problemă este de a
determina apariţia sau nu a cel puţin unei apariţii a cheii cu valoarea dată K .
99
Pentru acest caz soluţiile pentru căutare sunt cele prezentate anterior pentru
structurile cu cheie unică.
A două problemă este de a determina, dacă există, toate înregistrările care
conţin cheia dată K . În acest caz este necesară parcurgerea integrală a structurii,
ceea ce face ca algoritmul de căutare să realizeze întotdeauna un număr de
comparaţii egal cu numărul de înregistrări din structură. Rezultatul căutării va fi un
vector p care conţine identificările înregistrărilor care au valoarea cheii egală cu
K şi putem trage concluzia că avem o căutare fără succes dacă dimensiunea
vectorului p este zero, în caz contrar căutarea fiind cu succes.
Algoritmul matematic general pentru căutarea tuturor apariţiilor cheii date
K este:

Pas 1. R este prima înregistrare, dp = 0 , i = 1


Pas 2. dacă cheie ( R ) = K atunci dp = dp + 1 şi pdp = i
Pas 3. dacă există înregistrări neexplorate atunci R este următoarea
înregistrare, i = i + 1 şi se trece la pasul 2 altfel se continuă
Pas 4. dacă dp = 0 atunci căutare fără succes altfel există dp înregistrări
cu cheia K aflate în poziţiile p1 , …, pdp

Dacă structură liniară în care se face căutarea este un vector v de


dimensiune n , forma algoritmului este:

dp = 0 , i = 1
cât timp i ≤ n execută
dacă cheie ( v ) = K
atunci dp = dp + 1 şi pdp = i
altfel continuă
i = i +1
dacă dp = 0
atunci căutare fără succes
altfel
există dp înregistrări cu cheia K
aflate în poziţiile p1 , …, pdp

Pentru căutarea într-o structură cu cheie multiplă de tip înlănţuit în care


accesul la primul element se face printr-o variabilă primn şi care conţine cel puţin

100
câmpurile cheie şi nextn (pentru trecerea la următoarea înregistrare), procesul de
căutare este descris prin:

v = primn , dp = 0 , i = 1 (---------)
cât timp v ≠ Λ execută
dacă cheie ( v ) = K
atunci dp = dp + 1 şi pdp = i ( padrdp = v )
altfel continuă
v = nextn ( v ) , i = i + 1 (----------)
dacă dp = 0
atunci căutare fără succes
altfel
există dp înregistrări cu cheia K aflate
în poziţiile p1 , …, pdp
(la adresele padr1 , …, padrdp )

V.2. Căutarea în structuri ordonate

V.2.1. Căutare în vectori ordonaţi

Pentru structurile în care se realizează un număr mare de căutări, aplicarea


căutării secvenţiale prezentate în paragraful anterior implică, cumulat, un număr
considerabil de comparaţii. Pentru reducerea timpului necesar căutării este
recomandabil ca înregistrările să fie date în ordinea crescătoare a valorilor pentru
câmpul cheie şi utilizarea unor algoritmi care să utilizeze această proprietate a
înregistrărilor. O condiţie esenţială pentru a putea aplica un astfel de algoritm este
accesul direct la orice element al structurii liniare, algoritmul putând fi aplicat, în
special, la memorarea structurii în vectori ordonaţi.
Algoritmul de căutare, utilizat pentru vectorii ordonaţi poartă numele de
algoritmul căutării binare (sau algoritmul căutării logaritmice).
Se consideră dat un vector v de dimensiune n pentru care este îndeplinită
condiţia cheie ( v1 ) < cheie ( v2 ) < ... < cheie ( vn ) (pentru structura cu cheie
unică), respectiv cheie ( v1 ) ≤ cheie ( v2 ) ≤ ... ≤ cheie ( vn ) (pentru structura cu
cheie multiplă).
Presupunem că la un moment dat s-a determinat că înregistrarea posibilă,
având cheia K , este în porţiunea din vector delimitată de indicii iinf şi isup ,
deci printre înregistrările viinf , …, visup , fără a fi însă la capete, deci îndeplinind

( ) (
condiţia cheie viinf < K < cheie visup . )
101
Folosind o metodă aplicată frecvent în analiza numerică (înjumătăţirea
intervalului), putem considera o valoare mediană a indicelui înregistrării cu care
⎡ iinf + isup ⎤
vom face următoarea comparaţie, adică i = ⎢ ⎥⎦ . Dacă pentru indicele i
⎣ 2
astfel determinat avem cheie ( vi ) = K , atunci căutarea este cu succes. Dacă
K < cheie ( vi ) , atunci pentru următoarea căutare putem reduce spaţiul de lucru la
cel delimitat de indicii iinf şi i , în caz contrar (este valabilă inegalitatea
K > cheie ( vi ) ) reducând spaţiul de căutare la cel delimitat de indicii i şi isup .
Procesul de căutare este fără succes dacă la un pas de căutare valorile iinf
şi isup sunt consecutive (deci iinf + 1 = isup ).
Pentru aplicarea algoritmului la întregul spaţiu al vectorului v (prima
aplicare a metodei descrise mai sus) este necesar să asigurăm validitatea condiţiei
cheie ( v1 ) < K < cheie ( vn ) , ceea ce impune tratarea separată a capetelor
structurii.
Dacă ţinem cont de faptul că, odată îndeplinită condiţia
cheie ( viinf ) < K < cheie ( visup ) , nu se mai realizează operaţii cu capetele
domeniului de lucru, atunci printr-un artificiu putem evita tratarea separată a
capetelor vectorului. Pentru aceasta vom considera fictiv că vectorul are n + 2
valori, considerând suplimentar elementele v0 (pentru care cheie ( v0 ) = −∞ ) şi
vn +1 (pentru care cheie ( vn +1 ) = +∞ .
Pentru determinarea performanţelor procesului de căutare într-un vector de
n componente formăm şirul (finit) de număr de elemente din vector care formează
⎡n⎤ ⎡d ⎤
spaţiul cu care se continuă comparările, d1 = n , d 2 = ⎢ ⎥ , … , d n = ⎢ n −1 ⎥ , …
⎣2⎦ ⎣ 2 ⎦
Acest şir este într-adevăr finit ca şir descrescător de numere naturale, ceea ce
demonstrează în plus că procesul de căutare este finit.
Din teoria numerelor ştim că pentru orice număr natural nenul k există
p natural, astfel încât 2 p ≤ k < 2 p +1 . Putem trage concluzia că numărul maxim
de elemente din şirul ( di ) este egal cu p dat mai sus şi astfel că numărul maxim
de paşi ai algoritmului este tot p . Cum p este de fapt [ log 2 n ] , putem scrie că
algoritmul de căutare are complexitatea O ( log 2 n ) .
Procesul de căutare prezentat mai sus poate fi descrie după cum urmează:

102
{iniţializare}
iinf = 0 , isup = n + 1 , p = 0
{ciclu căutare}
cât timp p = 0 şi isup − iinf > 1 execută
⎡ iinf + isup ⎤
i=⎢ ⎥⎦
⎣ 2
dacă vi = k
atunci p = i
altfel dacă vi < k
atunci isup = i
altfel iinf = i
dacă p = 0
atunci valoare absentă
atlfel vaoare în poziţia p

Aplicarea acestui proces la o structură cu cheie unică dă un răspuns exact


la întrebarea existenţei unei valori k în vectorul v , în caz afirmativ indicând şi
poziţia exactă a cheii căutate în vector.
Algoritmul se aplică şi pentru structurile cu cheie multiplă. Acum poziţia
p în cazul existenţei cheii k în vector va indica una din poziţiile cheii. Pentru a
determina toate poziţiile în care cheia k se găseşte în vector trebuie făcută o
parcurgere a vectorului la stânga şi la dreapta lui p .

V.2.2. Căutare în arbori binari

În realizarea aplicaţiilor se doreşte ca atât determinarea unui element, cât şi


introducerea elementelor noi să fie cât mai rapidă. Această cerinţă poate fi
îndeplinită prin intermediul reprezentării prin arbore de căutare.
Un arbore de căutare este un arbore binar în care, pentru fiecare nod p ,
orice cheie aflată în subarborele stâng al lui p are valoarea mai mică decât
cheie ( p ) şi orice cheie aflată în subarborele drept al lui p are valoarea mai mare
decât cheie ( p ) .

Pentru un arbore de căutare intervin trei probleme, cea de căutare efectivă


şi cele de întreţinere a structurii atât la introducerea unui nou element, cât şi la
eliminarea unui element. Nu considerăm şi construcţia iniţială a unui arbore de
căutare deoarece aceasta impune iniţializarea unui arbore vid,
R=λ

103
şi apoi aplicarea repetată a procedurii de introducere a unui nod nou. Am
considerat că R reprezintă variabila de acces la rădăcina arborelui de căutare.
Pentru simplificarea prezentării vom considera că un nod al arborelui binar
de căutare este de forma ( llink , cheie, inf , rlink ) , deci din valoarea cheii
( cheie ), o zonă de informaţie utilă ( inf ) şi din informaţiile de legătură ( llink ,
legătura la rădăcina subarborelui stâng, şi rlink , legătura la rădăcina subarborelui
drept). De asemenea, considerăm doar structurile cu cheie unică.
În procesul de depunere a unui nod nou este necesară parcurgerea structurii
pentru a detecta un nod astfel încât considerarea nodului nou ca fiu al acestuia să
menţină proprietăţile arborelui de căutare.
Procedeul de depunere poate fi descris prin:

GETCEL ( w )
cheie ( w ) = k , inf ( w ) = vinf , llink ( w ) = λ , rlink ( w ) = λ
dacă R = λ
atunci R = w
altfel
p=R
cât timp p ≠ λ execută
dacă cheie ( p ) = k
atunci
eroare nod existent
p=λ
altfel dacă cheie ( p ) < k
atunci
dacă llink ( p ) = λ
atunci llink ( p ) = w , p = λ
altfel p = llink ( p )
altfel
dacă rlink ( p ) = λ
atunci rlink ( p ) = w , p = λ
altfel p = rlink ( p )
Generarea arborelui de căutare în maniera descrisă mai sus face ca, la
parcurgerea în inordine a arborelui binar obţinut, nodurile să fie explorate în
ordinea crescătoare a cheilor din noduri. Din această cauză, generarea unui arbore
binar de căutare, urmată de o parcurgere în inordine a acestuia, constituie şi o
modalitate de ordonare, ordonarea prin arbori binari, una din cele mai eficiente
104
metode de ordonare din punct de vedere al timpului de execuţie (exprimat, în
general, ca număr de comparaţii efective între valorile cheilor).
Dacă avem un arbore de căutare şi dorim să determinăm informaţia
aferentă unei valori k a cheii, vom proceda după cum urmează:

p = R , gasit = false
cât timp (not gasit ) şi p ≠ λ execută
dacă cheie ( p ) = k
atunci
gasit = true , vinf = inf ( p )
altfel dacă cheie ( p ) < k
atunci p = llink ( p )
altfel p = rlink ( p )

Să considerăm acum dată valoarea k pentru cheie. Dacă dorim să


eliminăm nodul cu această cheie, atunci pentru a menţine proprietăţile arborelui de
căutare va fi necesară trecerea întregului subarbore stâng al nodului şters ca
subarbore stâng al celui mai stâng nod din subarborele drept al nodului eliminat.
Procesul de eliminare se realizează prin următorul procedeu:

q=λ, p=R
cât timp p ≠ λ şi cheie ( p ) ≠ k execută
q= p
dacă cheie ( p ) < k
atunci p = llink ( p )
altfel p = rlink ( p )
dacă p = λ
atunci eroare cheie absentă
altfel dacă p = R
atunci dacă llink ( p ) = λ
atunci
R = rlink ( p ) , q = λ
altfel
R = llink ( p ) , q = R , p = rlink ( p )
altfel dacă llink ( p ) = λ
atunci
105
dacă cheie ( q ) < k
atunci llink ( q ) = rlink ( p )
altfel rlink ( q ) = rlink ( p )
q=λ
altfel
dacă cheie ( q ) < k
atunci llink ( q ) = llink ( p )
altfel rlink ( q ) = llink ( p )
q = llink ( p ) , p = rlink ( p )
dacă q ≠ λ atunci
cât timp rlink ( q ) ≠ λ execută
q = rlink ( q )
rlink ( q ) = p

Performanţele cu care se realizează o căutare într-o structură de arbore de


căutare depinde, în mod esenţial, de numărul nivelurilor din arbore. Putem spune
că viteza de căutare este maximă atunci când arborele de căutare obţinut este un
arbore binar echilibrat.

106
VI. INTERCLASAREA

Interclasarea este procesul de obţinere a unei structuri din două sau mai
multe structuri cu aceleaşi proprietăţi, structura obţinută moştenind aceste
proprietăţi. În cele ce urmează vom considera că proprietatea îndeplinită este cea
de ordonare, structurile fiind de tip liniar.
Considerând date structurile s1 , s2 ,..., sk de tip liniar cu valorile (cheile)
ordonate crescător, interclasarea va fi o modalitate directă de a obţine structura
liniară S care să conţină toate elementele din structurile s1 , s2 ,..., sk , având
cheile ordonate crescător.
În continuare vom considera, în primul rând, cazul interclasării a două
structuri liniare s1 şi s2 şi apoi cazul unei interclasări generalizate pentru cazul
vectorilor. Pentru două structuri liniare vom considera separate cazurile structurilor
de tip vector şi listă liniară.
În final vom da o utilizare a procesului de interclasare pentru cazul
definirii operaţiilor asupra mulţimilor.

VI.1. Interclasarea a doi vectori

Fie daţi doi vectori v şi w de dimensiuni n şi m . Fiecare dintre vectorii


v şi w au valorile ordonate crescător. Dorim să obţinem un vector nou, r , de
dimensiune n + m în care să figureze toate elementele din vectorii v şi w şi
acestea să fie în ordine crescătoare.
În mod cert un vector r cu cerinţele de mai sus se poate obţine imediat
prin alipirea (concatenarea) vectorilor v şi w şi ordonarea vectorului obţinut.
Acest lucru nu este însă recomandat datorită complexităţii algoritmilor de ordonare
(după cum se va vedea în capitolul VII).
Procesul de obţinere directă a lui r impune parcurgerea simultană a
vectorilor v şi w . Din cele două valori considerate se depune în r cea mai mică,
urmând a avansa doar în vectorul din care s-a făcut depunerea. În acest mod
numărul de comparaţii va fi egal chiar cu n + m .
O problemă deosebită (extremă) apare în momentul epuizării unuia dintre
vectori şi când valorile rămase în celălalt trebuie transferate în r pentru terminarea
interclasării. Astfel, varianta de lucru poate fi dată prin:

107
pas 1. cât timp nu s-a epuizat nici unul dintre vectorii v şi w , se foloseşte
interclasarea efectivă cu valorile curente vi şi w j .
pas 2. dacă s-a epuizat vectorul v , se depune în r restul vectorului w ,
altfel se depune în r restul din vectorul v .

După cum se poate vedea, procesul impune zone de cod executabil


atât pentru interclasarea efectivă, cât şi pentru fiecare dintre procesele de
transfer în vectorul r (din v sau w ).
Codul suplimentar poate fi evitat după cum urmează. În exteriorul
vectorilor considerăm valorile extrase din aceştia, cv şi cw , în interclasarea
efectivă folosindu-le în locul valorilor vi şi w j .
Indicarea terminării unui vector se realizează prin definirea unei
valori extreme, în cazul nostru, pentru ordonare crescătoare, valoarea va fi
numită high value, hv , şi trebuie să fie o majorantă strictă a valorilor
comune din vectorii v şi w .
Astfel, dacă cv = hv , atunci s-au epuizat valorile din v , iar când
cw = hv s-a terminat utilizarea valorilor din w . De aici rezultă că operaţia
de interclasare va fi terminată când cv = cw = hv , ceea ce este echivalent cu
cv + cw = 2hv (ţinând cont de modul de definire al valorii hv ).
Pentru hv se poate indica o valoare prestabilită sau, folosind proprietatea
de ordonare, putem defini o valoare locală de forma hv = vn + vm + λ cu λ > 0 .
Folosind cele prezentate, interclasarea poate fi descrisă prin:

{definire hv }
cv = v1 , cw = w , i = 1 , j = 1 , k = 0
cât timp cv + cw ≠ 2hv execută
k = k +1
dacă cv < cw
atunci
rk = cv , i = i + 1
dacă i > n
atunci cv = hv
altfel cv = vi
altfel
rk = cw , j = j + 1
dacă j > m
atunci cw = hv
altfel cw = w j

108
VI.2. Interclasarea a două liste liniare

Considerăm două liste liniare l1 şi l2 cu aceeaşi structură şi în care


informaţia (cheia) este ordonată crescător. Dorim să obţinem o singură listă liniară,
formată din toate nodurile din l1 şi l2 cu cheia ordonată crescător.
În mod cert, dacă dorim ca noua listă să fie într-un spaţiu de memorie
diferit de cel ocupat de l1 şi l2 sau dacă dorim să menţinem cel puţin una dintre
listele l1 şi l2 , atunci se poate utiliza un procedeu analog celui prezentat mai sus în
care depunerea în vectorul r se înlocuieşte cu o scriere la finalul unei liste liniare.
Dacă listele implicate nu trebuie păstrate, interclasarea se poate realiza
direct pe listele iniţiale, fără transfer de informaţie, folosind doar câmpurile de
legătură.
Să considerăm cazul listelor liniare simplu înlănţuite cu nodurile de forma
( cheie, link ) . Listele sunt indicate prin variabilele A şi B . Lista care se obţine
va fi indicată prin C . Procesul de interclasare este dat prin:

C =λ, q=λ
cât timp A ≠ λ şi B ≠ λ execută
dacă cheie ( A ) < cheie ( B )
atunci
dacă C = λ
atunci C = A
altfel link ( q ) = A
q = A , A = link ( A)
altfel
dacă C = λ
atunci C = B
altfel link ( q ) = B
q = B , B = link ( B )
dacă A ≠ λ atunci
dacă C = λ
atunci C = A
altfel link ( q ) = A
A=λ

109
dacă B ≠ λ atunci
dacă C = λ
atunci C = B
altfel link ( q ) = B
B=λ

VI.3. Interclasarea multiplă

Considerăm un caz general de interclasare a m vectori. Pentru


uşurarea expunerii considerăm că fiecare vector vi ocupă linia i a unei
matrice A şi are dimensiunea d i .
` Pentru realizarea interclasării vom generaliza procesul de interclasare a doi
vectori cu high value. De această dată pentru valorile extrase din vectori vom
utiliza un vector c , iar pentru indicii acestor valori vom considera un vector j de
numere întregi.
Interclasarea multiplă este dată prin următorul algoritm:

{construcţie hv }
k =0
pentru i de la 1 la m execută
ji = 1 , ci = Ai ,1
cât timp Suma ( c, m ) ≠ mihv execută
min ( c, m, p, l , v )
pentru i de la 1 la l execută
zk +i = v
k = k +l
pentru i de la 1 la l execută
j p = j pi + 1
i

dacă j pi ≤ d pi
atunci c pi = Api , j p
i

altfel c pi = hv

În algoritmul descris mai sus intervine funcţia Suma ( c, m ) prin care se


realizează suma tuturor elementelor unui vector c de dimensiune m . De
asemenea, intervine procedura min ( c, m, p, l , v ) , unde c este un vector de m
componente pentru care se determină valoarea minimă care se memorează în v . În
110
plus se detemină toate poziţiile din c în care valoarea minimă este atinsă. Numărul
acestor poziţii este l , iar poziţiile efective sunt memorate în vectorul p având
componente întregi.

VI.4. Operaţii de tip mulţime

În finalul acestui capitol considerăm că o construcţie de tipul mulţime este


memorată într-un vector în care elementele sunt ordonate strict crescător.
Prezentăm, în continuare, modul de implementare a interclasării a doi vectori încât
să realizăm operaţiile specifice de calcul cu mulţimi. Acest lucru este imediat,
ţinând cont de definirea acestor operaţii.
Fie A şi B doi vectori reprezentând elementele din două mulţimi.
Presupunem că valorile din fiecare vector sunt ordonate strict crescător.
Dacă C = A ∪ B , atunci pentru obţinerea lui C putem utiliza algoritmul
de interclasare modificat astfel încât la valori cv şi cw egale să intervină o
singură depunere. Secvenţa de prelucrare repetabilă devine:

k = k +1
dacă cv < cw
atunci
zk = cv , i = i + 1
dacă i > n
atunci cv = hv
altfel cv = vi
altfel
zk = cw , j = j + 1
dacă j > m
atunci cw = hv
altfel cw = w j
dacă cv = zk atunci
i = i +1
dacă i > n
atunci cv = hv
altfel cv = vi

Pentru celelalte operaţii de mulţime, ∩ , Δ , \, modificările algoritmului de


interclasare sunt similare şi le vom lăsa ca exerciţiu.

111
VII. SORTAREA

Sortarea este procesul prin care valorile dintr-o structură liniară de date
sunt ordonate conform unei relaţii de ordine specificată.
În cazul informaţiilor complexe pentru elementele unei structuri este
posibil ca doar o parte a informaţiei să fie cea pe baza căreia se realizează
ordonarea. Această informaţie poartă numele de cheie de sortare.
Dacă datele care formează cheia de sortare nu sunt elementare, ci sunt
formate din mai multe valori elementare, atunci spunem că sortarea este cu cheie
multiplă. În caz contrar spunem că sortarea este cu cheie simplă.
Pentru realizarea operaţiei de sortare informaticienii şi-au imaginat un
număr foarte mare de algoritmi, construiţi specific anumitor structuri pentru
informaţia manipulată, modul de memorare a structurii, dimensiunea spaţiului de
memorie disponibil. S-a căutat ca, întotdeauna, în condiţiile date specific să se
obţină un proces de prelucrare cu număr cât mai mic de comparaţii şi transferuri de
date, deci care să se execute cât mai rapid.
Principala preocupare din acest capitol este de a prezenta o serie de
algoritmi de ordonare, în special pentru contextul sortării cu cheie simplă. Pentru
simplificarea prezentărilor vom presupune, în continuare, că elementele structurilor
supuse sortării sunt formate din valoarea cheii.
În considerarea algoritmilor de sortare un rol foarte mare revine locului de
memorare a structurilor. Reţinerea datelor în memoria internă, care conduce la
sortarea internă, este influenţată în apreciere doar de operaţiile de comparare
realizate deoarece transferurile de date sunt foarte rapide (nu produc consumuri
semnificative de timp de execuţie).
În cazul datelor care nu sunt memorate intern, sortarea externă, operaţiile
cele mai costisitoare pentru timpul de execuţie sunt cele de transfer între memoria
internă şi cea externă. Din această cauză în alegerea unui algoritm de ordonare vor
fi prioritari algoritmii în care se vor minimiza operaţiile de transfer de date.
Un alt aspect important care este luat în calcul la alegerea unui algoritm de
sortare este legat de dimensiunea spaţiului de memorie folosit pentru obţinerea
rezultatului. Din acest punct de vedere există algoritmi în care sortarea se face în
spaţiul iniţial de valori pentru vectori (rezultatul ocupând acelaşi spaţiu) şi
algoritmi care utilizează spaţiu de memorie suplimentar (rezultatul se obţine fie în
acelaşi spaţiu, fie într-un spaţiu de memorare distinct).
Principial, alegerea algoritmului de sortare se face printr-un compromis
realizat între timpul de execuţie şi memoria utilizată suplimentar.

112
VII.1. Sortare prin numărarea comparaţiilor

Metoda constă în a număra pentru fiecare element xi , i = 1, 2,..., n , câte


elemente mai mici sau egale cu el există în vectorul dat x . Astfel, pentru aplicarea
algoritmului spaţiul suplimentar necesar trebuie să acopere memorarea a n valori
întregi, vectorul în care se memorează numerele obţinute.
Vectorul rezultat în urma numărării reprezintă inversa permutării care
trebuie aplicată vectorului x pentru a obţine valorile în ordinea dorită. Astfel, dacă
y este vectorul distinct în care se obţine rezultatul şi p este vectorul rezultat al
( )
numărării, atunci y p ( i ) = x ( i ) .
Procesul de sortare internă este descris prin:

pentru i de la 1 la n execută
p (i ) = 1
pentru i de la 2 la n execută
pentru j de la 1 la i − 1 execută
dacă x ( i ) ≤ x ( j )
atunci p ( j ) = p ( j ) + 1
altfel p ( i ) = p ( i ) + 1
pentru i de la 1 la n execută
y ( p (i )) = x (i )

După cum se poate observa, transferul de informaţii intervine doar la


ultimul ciclu şi impune exact n operaţii.
Pentru fiecare i fixat prin ciclul după j se realizează i − 1 comparaţii.
Cum i este fixat în n − 1 moduri, numărul total de comparaţii este indiferent de
distribuţia valorilor în vectorul x . Astfel, algoritmul prezentat are complexitatea la
⎛ n2 ⎞
comparaţii Oc ⎜ ⎟ .
⎝ 2⎠
Procesul de ordonare externă bazat pe această metodă, dacă presupunem că
p este la rândul lui memorat extern (într-un fişier cu acces direct), se realizează
prin:

113
pentru i de la 1 la n execută
scrie( pi ,1)
pentru i de la 2 la n execută
citeşte( xi , y )
pentru j de la 1 la i − 1 execută
citeşte( x j , z )
dacă y ≤ z
atunci
citeşte( p j , q ), scrie( p j , q + 1 )
altfel
citeşte( pi , q ), scrie( pi , q + 1 )
pentru i de la 1 la n execută
citeşte( pi , q ), citeşte( xi , y ), scrie( yq , y )

Numărul de comparaţii în cazul formei externe se determină ca în cazul


⎛ n2 ⎞
sortării interne şi conduce la aceeaşi complexitate de comparaţii, Oc ⎜ ⎟.
⎝ 2⎠
Se observă că pentru fiecare i şi j fixate, o comparaţie este dublată de 3
operaţii I/O (transfer). De asemenea, la fixarea unei valori i pentru ciclul de
comparare se realizează încă o operaţie de transfer. Astfel, numărul de operaţii de
transfer pentru ciclul de comparaţii este:
n ( n − 1) 3 2 1
n − 1 + 3i = n − n −1
2 2 2
În procesul de iniţializare a vectorului de numărare se folosesc n
operaţii I/O, iar pentru obţinerea vectorului rezultat y sunt necesare 3n operaţii.
Astfel, indiferent de distribuţia elementelor supuse sortării, numărul total de
operaţii de transfer este
3 2 7
n + n −1
2 2
⎛3 ⎞
ceea ce conduce la o complexitate de transfer O ⎜ n 2 ⎟ .
⎝2 ⎠

114
VII.2. Sortarea prin inserţie

În mod evident, o mulţime cu un singur element este o mulţime ordonată.


Dacă presupunem că mulţimea { x1 , x2 ,..., xi −1} este o mulţime ordonată cu i − 1
valori şi xi este o valoare nouă, atunci se poate determina k ( 1 ≤ k ≤ i − 2 ) astfel
încât xk ≤ xi < xk +1 sau xi −1 < xi , când se alege k = i − 1 . Dacă inserăm xi în
poziţia k + 1 şi rescriem indicii consecutiv se obţine mulţimea { x1 , x2 ,..., xi } ,
ordonată, cu i valori. Acest mod de ordonare reprezintă metoda de sortare prin
inserţie.
O primă formă a sortării prin inserţie foloseşte decalările de valori pe
parcursul realizării comparaţiilor pentru determinarea poziţiei de inserare. Această
formă a algoritmului este cunoscută sub numele de sortare prin inserţie directă şi
este realizată prin:

pentru i de la 2 la n execută
a = xi , k = i − 1
cât timp k > 0 şi xk > a execută
xk +1 = xk , k = k − 1
xk +1 = a

Cazul cel mai favorabil pentru algoritm este cel în care x este deja un
vector ordonat crescător. Astfel, în ciclul interior condiţia xk > a nu este
îndeplinită niciodată, iar prelucrarea se reduce la:

pentru i de la 2 la n execută
a = xi , k = i − 1 {comparaţia xk > a ), xk +1 = a

Obţinem că se realizează doar n − 1 comparaţii (dar şi trei operaţii de


acces la elementele vectorului x ).
Cazul cel mai nefavorabil este cel în care x este un vector ordonat
descrescător. Astfel, în ciclul interior condiţia xk > a este tot timpul adevărată
pentru fiecare k = 1, 2,..., i − 1 . Rezultă că pentru fiecare i fixat se realizează câte
i − 1 operaţii de comparare şi trei operaţii de acces la vectorul x pentru fiecare
comparaţie.
Cum i are n − 1 valori posibile, numărul de comparaţii va fi
n ( n − 1) 3n ( n + 1)
1 + 2 + ... + n − 1 = (cu −1 accesuri la componentele
2 2
vectorului).
115
Considerând că formele de intrare pentru vectorul x sunt uniform
distribuite, numărul mediu de comparaţii realizate pentru ordonarea unui vector cu
n elemente este
n ( n − 1)
n −1 + ( n − 1)( n + 2 )
2 =
2 4
⎛ n2 ⎞
ceea ce conduce la o complexitate de comparare Oc ⎜ ⎟ .
⎝ 4⎠
Numărul mediu de accese la componentele vectorului x va fie egal cu
3n 2 3n 3n 2 9n
+ − 1 + 3n − 3 = + −4
2 2 2 2
⎛ 3n 2 ⎞
producând complexitatea Ot ⎜ ⎟.
⎝ 2 ⎠
O concluzie la obţinerea unui număr relativ mare de accese la
componentele vectorului x este că algoritmul nu se pretează la o aplicare în cazul
sortării externe.
O variantă a metodei de sortare prin inserţie este de a realiza o decalare
globală în momentul determinării locului în care valoarea xi trebuie inserată în
segmentul { x1 , x2 ,..., xi −1} . Pentru determinarea poziţiei k în care se face
inserarea se poate utiliza un algoritm de căutare (într-un vector ordonat).
Dacă această căutare se face prin algoritmul căutării binare, algoritmul de
ordonare va purta numele de sortare prin inserţie binară. Acest algoritm are
forma:

pentru i de la 2 la n execută
a = xi
caut( x , i − 1 , a , p )
pentru j de la i − 1 la p pas −1 execută
x j +1 = x j
xp = a

unde caut( x , i − 1 , a , p ) este o procedură de căutare binară în care p este fie


indicele unui element cu valoarea a , fie valoarea isup din algoritmul prezentat în
capitolul V, dacă valoarea a nu este găsită în vectorul x1 , x2 ,..., xi −1 .
Din punctul de vedere al numărului de transferuri de date (accese la
elementele lui x ) valoarea obţinută nu se modifică faţă de sortarea prin inserţie
directă.
116
Considerând comparaţiile realizate pentru fiecare i fixat, prin utilizarea
căutării binare se folosesc un număr de comparaţii egal cu log 2 i . Astfel, numărul
de comparaţii va fi
log 2 2 + log 2 3 + ... + log 2 n < n log 2 n
deci putem considera că algoritmul sortării prin inserţie binară are complexitatea
(de comparaţie) O ( n log 2 n ) .

VII.3. Sortarea prin interschimbare

Principiul acestei metode de sortare este de comparare a două elemente


consecutive şi de schimb între ele în cazul în care nu corespund ordinii dorite.
Pentru efectuarea comparaţiilor, structura liniară este parcursă integral şi,
aplicând principiul enunţat, va rezulta că, la finalul parcurgerii cel mai mare
element care nu este la locul său, va fi deplasat în poziţia sa finală. Pentru a asigura
ordonarea este necesar ca la o parcurgere integrală să nu se realizeze nici o
interschimbare.
În considerarea unui algoritm performant trebuie să observăm că după o
parcugere toate elementele aflate în vector, începând cu poziţia ultimei
interschimbări, inclusive, se găsesc deja în poziţia lor finală. Din această cauză, la
următoarea parcurgere a vectorului, putem considera doar zona în care ordonarea
nu este garantată, reducând astfel numărul de comparaţii.
Algoritmul de ordonare care foloseşte ultima observaţie este (pentru
ordonarea crescătoare) de forma:

d =n
cât timp d > 1 execută
p=0
pentru i de la 1 la d − 1 execută
dacă vi > vi +1 atunci
vi ↔ vi +1 {interschimbarea}
p=i
d=p

La implementarea algoritmului, operaţia vi ↔ vi +1 este una de factură


ceva mai complexă. Pentru o structură de vector, operaţia este dată de o secvenţă

aux = vi
vi = vi +1
vi +1 = aux

117
În cazul cel mai favorabil (vector deja ordonat crescător) se face o singură
aplicare a ciclului dat de condiţia d > 1 . Numărul de paşi ai ciclului de tip for este
n − 1 şi dă şi numărul de comparaţii realizate.
În cazul cel mai nefavorabil (vectorul este ordonat dar descrescător), o
aplicare a ciclului de tip for face, conform principiului metodei, ca elementul aflat
în v1 să fie trecut în poziţia sa finală vd , după aceea d fiind scăzut cu o unitate.
Numărul de comparaţii în ciclul for este d − 1 şi astfel, numărul total de comparaţii
va fi
n ( n − 1)
( n − 1) + ( n − 2 ) + ... + 1 =
2
Ţinând cont de cele de mai sus, numărul mediu de comparaţii necesare
pentru ordonarea unui vector de n componente este media aritmetică a valorilor
⎛ n2 ⎞
obţinute în cazurile date, ceea ce conduce la o complexitate exprimată ca O ⎜ ⎟.
⎝ 4⎠

VII.4. Sortarea prin interclasare

Această metodă de ordonare foloseşte ca intermediar procesele de


interclasare a structurilor ordonate. Se presupune că din vectorul dat se formează
doi sau mai mulţi vectori ordonaţi şi prin interclasarea acestora se obţine vectorul
ordonat final.
Cea mai bună descriere a unui proces de sortare prin interclasare este de
formă recursivă. Subprogramul de lucru este de forma:

Sortinterclasare
dacă lf − li ≤ 0
atunnci
continuă (vectorul vid şi cel cu un singur
element este deja ordonat}
altfel
⎡ li + lf ⎤
k=⎢
⎣ 2 ⎥⎦
Sortinterclasare ( v, li, k )
Sortinterclasare ( v, k + 1, lf )
Interclasare ( v, li, k , lf , z )
Muta ( z.v, li, lf )

118
Interclasare descrie procedeul de interclasare a doi vectori memoraţi în
acelaşi vector v , primul aflat între indicii li şi k , iar al doilea între indicii k + 1 şi
lf , rezultatul fiind depus în vectorul z între poziţiile li şi lf .
Muta descrie un proces de mutare a valorilor dintr-un vector z într-un
vector v , fiind transferate doar valorile din domeniul de indici li ÷ lf .
Ordonarea se obţine prin aplicarea apelului

Sortinterclasare ( v,1, n )

Algoritmul recursiv prezentat mai sus nu este unul foarte util,


implementarea sa necesitând un volum foarte mare de memorie pentru stiva
program.
Variantele utile pentru sortarea prin interclasare formează formal procese
cu două faze:
a. Obţinerea de monotonii (subşiruri ordonate ale şirului dat).
b. Interclasarea monotoniilor obţinute în prima fază.
În faza de interclasare a monotoniilor se poate aplica fie algoritmul de
interclasare a doi vectori, fie varianta de interclasare multiplă.
Cea mai simplă variantă pentru obţinerea monotoniilor este de a parcurge
integral şirul de valori şi de a reţine fiecare indice pentru care două valori
consecutive nu sunt în ordinea dorită.
Variantele uzuale de obţinere a monotoniilor stabilesc o limită a
dimensiunii monotoniilor iniţiale k şi considerarea subşirurilor de lungime k din
şirul dat pe care le ordonează printr-o altă metodă de sortare.

În mod cert, procesele de ordonare, cuprinse în acest capitol, reprezintă


doar o mică parte din procedurile înregistrate până acum. Una dintre cele mai
importante colecţii de algoritmi, legaţi de fenomenul de ordonare, este volumul
Sortare şi căutare din Tratat de programarea calculatoarelor, avându-l ca autor
pe D.E. Knuth.

119
BIBLIOGRAFIE

1. O. Bâscă, M. Jaică, Structuri de date (note de curs), Editura


Universităţii Piteşti, 2000
2. S. Bârză, Algoritmi şi programare, Vol I, Programare statică,
Editura Universităţii din Bucureşti 2000
3. O. Bâscă, Sisteme de operare şi teleprelucrarea datelor, Tipografia
Universităţii din Bucureşti, 1976
4. O. Bâscă, I. Popescu, Sisteme de operare, Vol. I, Tipografia
Universităţii din Bucureşti, 1987
5. A.T. Berztiss, Data Structures. Theory and Practice, Academic
Press, 1975
6. D.E. Knuth, Tratat de programarea calculatoarelor, Vol. I
Algoritmi fundamentali şi Vol III Sortare şi căutare, Editura
Tehnică, Bucureşti, 1974-1976 (Editura Teora, Bucureşti, 1999-
2000)

120

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