Documente Academic
Documente Profesional
Documente Cultură
I. Morogan, Luciana
044.422.63
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
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
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.
∞
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ă).
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
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
16
I.4. Reprezentarea datelor nenumerice
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.
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
21
II.3. Implementarea dinamică a tablourilor unidimensionale
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
( )
mα
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 ).
mα
23
II.3.2. Vectori dinamici cu implementare variabilă
24
II.4. Implementarea statică a tablourilor multidimensionale
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
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.
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
= (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ă.
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.
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
38
II.8.3. Matrice bandă
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 .
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.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
44
III.2.4. Coada cu priorităţi
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ă.
dacă V = m
atunci overflow
altfel V = V + 1 şi A(V ) = x
dacă V = 0
atunci underflow
altfel z = A(V ) şi V = V − 1
46
III.3.2. Coada secvenţială
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 ,
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 ,
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.
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 ) .
dacă V = modulo m (B + 1)
atunci overflow
altfel B = modulo m (B + 1) şi A(B ) = x .
dacă B = V
atunci underflow
altfel V = modulo m (V + 1) şi z = A(V ) .
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
dacă P = m
atunci overflow
altfel P = P + 1 şi A(P ) = x
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
dacă P = 0
atunci underflow
altfel z = A(P ) şi P = P − 1
dacă L = modulo m (R + 1)
atunci overflow
altfel R = modulo m (R + 1) şi A(R ) = x
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 )
dacă L = R
atunci underflow
altfel z = A(R ) şi R = modulo m (R − 1) .
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
dacă E = 0
atunci underflow
altfel z = V (E ) şi E = E − 1 .
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 .
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 .
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 .
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ă V 1 + 1 = V 2
atunci overflow
altfel V 2 = V 2 − 1 şi A(V 2 ) = x
dacă V 1 = 0
atunci underflow
altfel z = A(V 1) şi V 1 = V 1 − 1 ;
dacă V 2 = m + 1
atunci underflow
altfel z = A(V 2 ) şi V 2 = V 2 + 1 .
dacă DISP = Λ
atunci overflow
altfel k = DISP şi DISP = l (DISP ) .
GETCEL(k )
dacă k = Λ
atunci overflow
altfel v(k ) = x , l (k ) = V şi V = k .
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
{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 ) .
GETCEL(a )
dacă a = Λ
atunci overflow
altfel inf (a ) = x , prev(a ) = V şi V = a .
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ă
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.
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 .
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 )
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 .
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.
Î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
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
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ă 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 ) = Λ ;
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.
GETCEL(a )
dacă a = Λ
atunci overflow
altfel
inf (a ) = x , llink (a ) = Λ , rlink (a ) = B şi
B=a
dacă V = Λ
atunci V = B .
dacă V = Λ
atunci underflow
altfel
a = V , z = inf (a ) ,
V = llink (a ) şi FREECEL(a )
dacă V = Λ
atunci B = Λ
altfel rlink (V ) = Λ .
69
III.4.6. Implementarea cozilor circulare cu dublă înlănţuire
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 .
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
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.
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.
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 .
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.
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 ) = Λ .
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 ) .
a=R
pentru k = 2,..., j execută a = nextc(a )
cât timp raw(a ) < i execută a = nextr (a )
inf (a ) = x .
{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 .
{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 ) .
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
IV.3.1. Arbori
80
Reprezentarea acestui arbore binar este:
81
rutină preordine( v )
dacă v ≠ Λ
{utilizare inf (v ) }
preordine( llink (v ) )
preordine( rlink (v ) )
rutină inordine( v )
dacă v ≠ Λ
inordine( llink (v ) )
{utilizare inf (v ) }
inordine( rlink (v ) )
rutină postordine( v )
postordine( llink (v ) )
postordine( rlink (v ) )
{utilizare inf (v ) }
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
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:
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
86
Structura de liste rezultată pentru implementarea acestui arbore este:
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
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ă
dacă R ≠ Λ
atunci
GETCEL ( w )
tipn ( w ) = 2
lnodi ( w ) = R
lnext ( w ) = Λ
parcurgere_interioară( w )
FREECEL ( w )
91
Pentru arborele oarecare considerat ca exemplu, implementarea sub forma
însăilată este:
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
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 )
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:
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 .
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
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 )
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 )
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
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 )
( ) (
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
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 )
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
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.
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 .
{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
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=λ
{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
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
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
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 )
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 )
114
VII.2. Sortarea prin inserţie
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
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
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
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⎠
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 )
119
BIBLIOGRAFIE
120