94650969 19 ID Program Area Calculatoarelor

96
Academia de Studii Economice din Bucureşti Facultatea de Cibernetică, Statistică şi Informatică Economică Catedra de Informatică Economică Cătălina-Lucia Cocianu Cristian Uscatu Programarea calculatoarelor Material didactic pentru ID Acest material are la bază lucrarea Programarea calculatoarelor. Algoritmi în programare autori I. Gh. Roşca, B. Ghilic-Micu, C. Cocianu, M. Stoica, C. Uscatu, M. Mircea publicată la Editura ASE Bucureşti, 2007 Editura ASE Bucureşti 2010

description

calculatoare

Transcript of 94650969 19 ID Program Area Calculatoarelor

Page 1: 94650969 19 ID Program Area Calculatoarelor

Academia de Studii Economice din Bucureşti

Facultatea de Cibernetică, Statistică şi Informatică Economică Catedra de Informatică Economică

Cătălina-Lucia Cocianu Cristian Uscatu

Programarea calculatoarelor

Material didactic pentru ID

Acest material are la bază lucrarea Programarea calculatoarelor. Algoritmi în programare

autori I. Gh. Roşca, B. Ghilic-Micu, C. Cocianu, M. Stoica, C. Uscatu, M. Mircea publicată la Editura ASE Bucureşti, 2007

Editura ASE Bucureşti

2010

Page 2: 94650969 19 ID Program Area Calculatoarelor

Programarea calculatoarelor 2

Copyright © 2011, Cătălina Cocianu, Cristian Uscatu Toate drepturile asupra acestei ediţii sunt rezervate autorului Editura ASE Piaţa Romană nr. 6, sector 1, Bucureşti, România cod 010374 www.ase.ro www.editura.ase.ro [email protected]

Referenţi:

Prof. univ. dr. Ion Gh. ROŞCA Prof. univ. dr. Gabriel ZAMFIR

ISBN 978-606-505-464-6

Page 3: 94650969 19 ID Program Area Calculatoarelor

* Material didactic pentru ID * 3

Titlul cursului: Programarea calculatoarelor Introducere: Cursul Programarea calculatoarelor se adresează studenţilor facultăţii de Cibernetică, Statistică şi Informatică Economică din cadrul Academiei de Studii Economice din Bucureşti. Conform planului de învăţămînt, cursul se desfăşoară în semestrul 2 al anului 2 de studiu.

Cursul Programarea calculatoarelor este destinat dezvoltării cunoştinţelor studenţilor în cîteva aspecte fundamentale ale programării calculatoarelor: lucrul cu structuri dinamice de date, elemente de teoria grafurilor cu aplicaţii, noţiuni

de programare orientată obiect. Limbajul utilizat pentru demonstrarea conceptelor şi aplicaţiile practice este C standard (ANSI C). Obiectivele principale ale cursului vizează însuşirea de către studenţi a cunoştinţelor teoretice şi a abilităţilor practice de lucru referitoare la următoarelor elemente din cadrul programării calculatoarelor:

structuri dinamice de date liniare, modul de lucru cu acestea şi aplicabilitatea acestora;

elemente de teoria grafurilor; structuri dinamice de date arborescente, modul de lucru cu acestea, utilitatea şi

aplicabilitatea lor; noţiuni de programare orientată obiect.

Cursul Programarea calculatoarelor este structurat în patru unităţi de învăţare, corespunzătoare elementelor principale studiate prezentate mai sus.

În cadrul procesului de instruire pot fi utilizate ca resurse suplimentare materialele puse la dispoziţie de biblioteca facultăţii, atît în cadrul sălii de lectură cît şi prin serviciul de împrumut.

De asemenea, laboratoarele Catedrei de Informatică Economică pot fi utilizate pentru studiu individual pe toată durata programului de lucru, atunci cînd nu se desfăşoară ore (programarea orelor în laboratoare poate fi consultată la secretariatul Catedrei). Calculatoarele din laboratoare oferă medii de programare adecvate pentru dezvoltarea abilităţilor de lucru în limbajul C şi posibilitatea de accesare a bibliotecii virtuale a instituţiei.

Evaluarea cunoştinţelor se va realiza prin intermediul lucrărilor susţinute pe parcursul semestrului astfel:

o probă practică, constînd în rezolvarea a două probleme de programare din cadrul tematicii cursului;

o lucrare scrisă. Ambele teste se susţin în timpul orelor aferente temelor de control din calendarul disciplinei.

Cele două teste contribuie la formarea notei finale astfel:

proba practică constituie 40% din nota finală; lucrarea scrisă constituie 50% din nota finală; din oficiu se acordă 10%.

Page 4: 94650969 19 ID Program Area Calculatoarelor

Programarea calculatoarelor 4

CUPRINS 1. Grafuri ................................................................................................................................... 5 1.1 Definiţii şi reprezentări ale grafurilor .............................................................................. 5 1.2 Parcurgerea grafurilor în lăţime....................................................................................... 8 1.3 Metoda de parcurgere df (Depth First) .......................................................................... 10 1.4. Drumuri în grafuri. Conexitate ...................................................................................... 13 1.5. Drumuri de cost minim ................................................................................................... 17 1.6. Răspunsuri la testele de autoevaluare. Comentarii ...................................................... 20 2. Structuri dinamice de date. Liste ...................................................................................... 26 2.1. Reprezentarea listelor ..................................................................................................... 26 2.2. Operaţii primitive asupra listelor liniare ...................................................................... 27 2.3. Liste circulare .................................................................................................................. 32 2.4. Stive şi cozi ....................................................................................................................... 35 2.5 Răspunsuri la testele de autoevaluare. Comentarii ....................................................... 37 3. Structuri arborescente ....................................................................................................... 43 3.1. Definiţii şi caracterizări ale grafurilor arbori............................................................... 43 3.2. Reprezentări şi parcurgeri ale arborilor orientaţi ....................................................... 45 3.3. Arbori parţiali; algoritmul kruskal ............................................................................... 48 3.4. Arbori binari. Arbori binari de sortare......................................................................... 50 3.5. Arbori de structură ......................................................................................................... 54 3.6. Răspunsuri la testele de autoevaluare. Comentarii ...................................................... 59 4. Elemente de programare orientată obiect........................................................................ 64 4.1. Modelul de date orientat obiect ...................................................................................... 64 4.2. Definirea claselor ............................................................................................................. 70 4.3. Constructori ..................................................................................................................... 73 4.4. Destructori........................................................................................................................ 76 4.5. Funcţii prieten.................................................................................................................. 77 4.6. Derivarea claselor ............................................................................................................ 78 4.7. Răspunsuri şi comentarii la testele de autoevaluare .................................................... 93 Bibliografie .............................................................................................................................. 96

Page 5: 94650969 19 ID Program Area Calculatoarelor

* Material didactic pentru ID * 5

1. Grafuri

Obiective Principalele obiective vizează însuşirea şi aprofundarea de către studenţi a noţiunilor fundamentale cu care se operează în studiul grafurilor: reprezentări şi parcurgeri ale grafurilor, conectivitate, drumuri în grafuri. Cuprins 1.1 Definiţii şi reprezentări ale grafurilor ........................................................................................... 5 1.2 Parcurgerea grafurilor în lăţime.................................................................................................... 8 1.3 Metoda de parcurgere df (Depth First) ....................................................................................... 10 1.4. Drumuri în grafuri. Conexitate ................................................................................................... 13 1.5. Drumuri de cost minim ................................................................................................................ 17 1.6. Răspunsuri la testele de autoevaluare. Comentarii ................................................................... 20

Timpul mediu de învăţare pentru studiul individual: 8 ore.

1.1 Definiţii şi reprezentări ale grafurilor

Definiţia 1.1.1. Se numeşte graf sau graf neorientat o structură G=(V,E), unde V este o mulţime nevidă iar E este o submulţime posibil vidă a mulţimii perechilor neordonate cu componente distincte din V.

Elementele mulţimii V se numesc vîrfuri, iar obiectele mulţimii E se numesc muchii. Dacă e∈E, uvnot. v)(u,e = , vîrfurile u şi v se numesc extremităţi ale lui e, muchia e fiind determinată de

vîrfurile u şi v. Dacă e=uv∈E se spune că vîrfurile u, v sînt incidente cu muchia e. Definiţia 1.1.2. Fie G=(V,E) graf. Vîrfurile u, v sînt adiacente în G dacă uv∈E. Definiţia 1.1.3. Graful G=(V,E) este graf finit, dacă V este o mulţime finită. Definiţia 1.1.4. Fie Gi =(V i,Ei), i=1,2 grafuri. G2 este un subgraf al grafului G1 dacă 12 VV ⊆ şi

12 EE ⊆ . G2 este este un graf parţial al lui G1 dacă V2=V1 şi G2 este subgraf al lui G1. Definiţia 1.1.5. Un digraf este o structură D=(V,E), unde V este o mulţime nevidă de vîrfuri, iar

E este o mulţime posibil vidă de perechi ordonate cu componente elemente distincte din V. Elementele mulţimii E sînt numite arce sau muchii ordonate. Un graf direcţionat este o structură D=(V,E), unde V este o mulţime nevidă de vîrfuri, iar E este o mulţime posibil vidă de perechi ordonate cu componente elemente din V, nu neapărat distincte. Evident, orice digraf este un graf direcţionat.

Definiţia 1.1.6. Se numeşte graf ponderat o structură (V,E,W), unde G=(V,E) este graf şi W este o funcţie definită prin ( )∞→ ,0E:W . Funcţia W este numită pondere şi ea asociază fiecărei muchii a grafului un cost/cîştig al parcurgerii ei.

Definiţia 1.1.7. Fie G=(V,E) un graf, u,v∈V. Secvenţa de vîrfuri Γ:u0,u1,..,un este un u-v drum dacă u0=u, un=v, uiui+1∈E pentru toţi i, ni0 ≤≤ .

Definiţia 1.1.8. Fie G=(V,E) un graf. Elementul v∈V se numeşte vîrf izolat dacă, pentru orice e∈E, u nu este incident cu e.

Cea mai simplă reprezentare a unui graf este cea intuitivă, grafică; fiecare vîrf este figurat printr-un punct, respectiv muchiile sînt reprezentate prin segmentele de dreaptă, orientate (în cazul digrafurilor) sau nu şi etichetate (în cazul grafurilor ponderate) sau nu, avînd ca extremităţi punctele corespunzătoare vîrfurilor care o determină.

Exemple 1.1.1. Fie G=(V,E) graf, cu V={1,2,3,4,5,6}, E={(1,2),(1,3),(2,5),(3,5),(5,6)}. O posibilă

reprezentare grafică este,

Page 6: 94650969 19 ID Program Area Calculatoarelor

Programarea calculatoarelor 6

1

2

3

4

56

1.1.2. Fie D=(V,E) digraf, V={1,…,5}, E={(1,2), (1,3), (1,5), (2,5), (3,5), (4,1), (5,4)}. Digraful poate fi reprezentat grafic astfel,

1

2

3 5

4

1.1.3. Fie G=(V,E,W) graf ponderat, V={1,2,3,4}, E={(1,2), (1,3), (1,4), (2,3), (2,4)},

W((1,2))=5, W((1,3))=1, W((1,4))=7, W((2,3))=4, W((2,4))=2. O posibilă reprezentare grafică este: 1

32

4

5 1

27

4

În scopul reprezentării grafurilor în memoria calculatorului sînt utilizate în general următoarele

structuri de date. Reprezentarea matriceală Grafurile, digrafurile şi grafurile direcţionate pot fi reprezentate prin matricea de adiacenţă.

Dacă G=(V,E ) este graf, digraf sau graf direcţionat cu nV = , atunci matricea de adiacenţă

A∈Mnxn({0,1}) are componentele ( )

⎩⎨⎧ ∈

=altfel,0

Ev,vdacă,1a ji

ij , unde vi, vj reprezintă cel de-al i-lea,

respectiv cel de-al j-lea nod din V. În cazul unui graf neorientat, matricea de adiacenţă este simetrică.

Exemplu 1.1.4. Graful din exemplul 1.1.1 şi digraful din exemplul 1.1.2sunt reprezentate prin,

⎟⎟⎟⎟⎟⎟⎟⎟

⎜⎜⎜⎜⎜⎜⎜⎜

=

010000100110000000010001010001000110

A (1.1.1),

⎟⎟⎟⎟⎟⎟

⎜⎜⎜⎜⎜⎜

=

1100000001100001000010110

A (1.1.2)

În cazul grafurilor ponderate, reprezentarea poate fi realizată prin matricea ponderilor. Dacă G=(V,E,W) este graf ponderat, nV = , W ∈Mnxn((0,∞ )) are componentele,

( ) ( )⎩⎨⎧ ∈

=altfel,

Ev,vdacă,)v,v(Ww jiji

j,i α, unde vi, vj reprezintă cel de-al i-lea, respectiv cel de-al j-lea

nod din V, 0=α , dacă ponderea are semnificaţia de cîştig, respectiv ∞=α în cazul în care se doreşte reprezentarea costurilor ca ponderi ale grafului.

Page 7: 94650969 19 ID Program Area Calculatoarelor

* Material didactic pentru ID * 7

Exemplu 1.1.5. Presupunînd că ponderile reprezintă costuri, matricea de reprezentare a grafului din

exemplul 1.1.3. este

⎟⎟⎟⎟⎟

⎜⎜⎜⎜⎜

∞∞∞∞

∞∞

=

2741

245715

W .

Reprezentarea tabelară Reţinînd muchiile prin intermediul extremităţilor şi eventual valoarea ponderii ei, se obţine

reprezentarea tabelară, mai economică din punctul de vedere al spaţiului de memorie necesar. Dacă graful conţine vîrfuri izolate atunci este necesară păstrarea acestora într-un vector suplimentar VS. Mulţimea muchiilor este reţinută într-o matrice A cu E linii şi c coloane, unde c=2 dacă graful nu este ponderat, altfel c=3. În primele două coloane se scriu perechile de vîrfuri ce determină muchiile, în cazul grafurilor ponderate cea de-a treia coloană conţine valoarea ponderii muchiei respective.

Exemple

1.1.6. Graful din exemplul 1.1.1 poate fi reprezentat astfel, VS=(4), T

A ⎟⎟⎠

⎞⎜⎜⎝

⎛=

6553253211

1.1.7. Digraful din exemplul 1.1.2 este reprezentat prin T

A ⎟⎟⎠

⎞⎜⎜⎝

⎛=

41555325432111

.

1.1.8. Graful ponderat din exemplul 1.1.3. este reprezentat prin

⎟⎟⎟⎟⎟⎟

⎜⎜⎜⎜⎜⎜

=

242741432131521

A .

Reprezentarea prin intermediul listelor Această reprezentare permite utilizarea economică a spaţiului de memorare şi, în anumite cazuri,

implementări mai eficiente pentru anumite clase de algoritmi. Vîrfurile grafului sînt memorate într-o listă, fiecare nod al listei N conţinînd o referinţă spre lista vecinilor vîrfului memorat ca informaţie în N.

Dacă graful nu este ponderat, el poate fi reprezentat prin structura listă de liste, şi anume: nodurile grafului se trec într-o listă L_nod, fiecare celulă avînd structura,

Informaţie legătură vecini legătură nod următor Unde cîmpul informaţie conţine identificatorul nodului, legătură vecini reprezintă referinţa spre

începutul listei vecinilor şi legătură nod următor conţine adresa următoarei celule din lista L_nod. Un graf ponderat poate fi reprezentat în mod similar, cu diferenţa că, fiecare celulă din lista

vecinilor conţine şi ponderea muchiei respective. Teste de autoevaluare

1.1. Fie graful G=(V,E) graf, cu V={1,2,3,4,5,6}, E={(1,2),(1,3),(2,5),(2,6),(3,5),(3,6),(5,6)}. Care este matricea de adiacenţă a grafului?

1.2. Fie D=(V,E) digraf, V={1,…,5}, E={(1,2), (1,3), (2,5), (3,5), (4,1), (5,1), (5,3), (5,4)}. Care este matricea de adiacenţă a digrafului?

1.3. Fie D=(V,E) digraf, V={1,…,5}, E={(1,2), (1,3), (2,5), (3,5), (4,1), (5,1), (5,3), (5,4)}. Care este reprezentarea tabelară a digrafului?

1.4. Fie G=(V,E,W) graf ponderat, V={1,2,3,4}, E={(1,2), (1,4), (2,3), (2,4), (3,4)}, W((1,2))=5, W((1,4))=7, W((2,3))=4, W((2,4))=2, W((3,4))=1. Specificaţi matricea ponderilor.

Page 8: 94650969 19 ID Program Area Calculatoarelor

Programarea calculatoarelor 8

1.2 Parcurgerea grafurilor în lăţime

Modalitatea de vizitare a tuturor vîrfurilor grafului în care fiecare vîrf al grafului este vizitat o singură dată se numeşte parcurgere sau traversare. În acest material sînt prezentate metodele de parcurgere BF (în lăţime) şi DF (în adîncime). Metodele de parcurgere sunt aplicate grafurilor neorientate respectiv grafurilor direcţionate şi presupun selectarea unui vîrf iniţial v0 şi identificarea acelor vîrfuri ale grafului v cu proprietatea că există cel puţin un drum de la vîrful iniţial către v. Grafurile cu proprietatea că oricare două vârfuri sunt conectate printr-un drum se numesc grafuri conexe şi sunt prezentate în § 1.4. Dacă graful este conex, atunci prin aplicarea metodelor de parcurgere vor fi identificate toate vârfurile grafului. Cele două modalităţi de parcurgere sunt prezentate în continuare în cazul grafurilor neorientate, extinderea la digrafuri şi grafuri direcţionate fiind imediată.

Traversarea BF presupune parcurgerea în lăţime a grafului, în sensul că, vîrfurile grafului sînt prelucrate în ordinea crescătoare a distanţelor la vîrful iniţial. Distanţa de la u la v, notată ( )v,uδ , este numărul de muchii ale unui cel mai scurt u-v drum.

La momentul iniţial vîrf curent este v0. Deoarece vîrful curent la fiecare moment trebuie să fie unul dintre vîrfurile aflate la distanţă minimă de v0 se poate proceda în modul următor: iniţial lui v0 i se asociază valoarea 0, [ ] 0vd 0 = şi fiecărui vîrf 0vv ≠ i se asociază valoarea ∞ , [ ] ∞=vd . Dacă valoarea asociată vîrfului curent este m, atunci fiecăruia dintre vecinii acestuia de valoare ∞ li se asociază valoarea m+1. Se observă că, dacă după ce toate vîrfurile de valoare m au fost considerate şi nici unui vîrf nu i-a fost recalculată valoarea, atunci toate vîrfurile conectate cu v0 au fost vizitate, deci calculul se încheie.

Exemple 1.2.1. Fie graful,

şi v0=1.Valorile calculate prin aplicarea metodei prezentate sînt,

vîrf d

1 2 3 4 5 6 7

0 0 ∞ ∞

∞ ∞

1 0 1 1 1 ∞

1

2 0 1 1 1 2 2 1 0 1 1 1 2 2 1

Fie G=(V,E) un graf, nV = . O alternativă de implementare a metodei BF este construită prin

utilizarea următoarelor structuri de date, • A matricea de adiacenţă a grafului; • o structură de tip coadă, C, în care sînt introduse vîrfurile ce urmează a fi vizitate şi procesate

(în sensul cercetării vecinilor lor); • un vector c cu n componente, unde,

⎩⎨⎧

=,0,1

ic

Componentele vectorului c sînt iniţializate cu valoarea 0.

dacă i a fost adăugat în coadăaltfel

1

2

5

6

7

3

4

Page 9: 94650969 19 ID Program Area Calculatoarelor

* Material didactic pentru ID * 9

Parcurgerea BF poate fi descrisă astfel, • coada C este iniţializată cu vîrful v0; • cît timp ≠C Ø, este extras şi vizitat un vîrf i din coadă, apoi sînt introduşi în coadă vecinii lui

i care nu au fost deja introduşi (acele vîrfuri k cu proprietatea că c[k]=0 şi a[i][k]=1). Vîrfurile i ce au fost introduse în coadă sînt marcate prin c[i]=1.

Exemplu 1.2.2. Pentru graful din exemplul 1.2.1., aplicarea metodei de traversare BF determină

următoarea evoluţie,

c t 1 2 3 4 5 6 7

t=1 1 0 0 0 0 0 0 t=2 1 1 1 1 0 0 1 t=3 1 1 1 1 1 0 1 t=4 1 1 1 1 1 1 1 t=5 1 1 1 1 1 1 1 t=6 1 1 1 1 1 1 1 t=7 1 1 1 1 1 1 1 t=8 1 1 1 1 1 1 1

c t

t=1 1 t=2 2 3 4 7 t=3 3 4 7 5 t=4 4 7 5 6 t=5 7 5 6 t=6 5 6 t=7 6 t=8

Observaţie Deoarece graful din exemplul 1.2.1. este conex, traversarea BF realizează vizitarea

tuturor vîrfurilor grafului. Aplicarea metodei BF unui graf neconex nu determină vizitarea tuturor vîrfurilor. Cu alte cuvinte, metoda BF aplicată unui graf determină vizitarea tuturor vîrfurilor care sînt conectate cu vîrful iniţial selectat.

Teste de autoevaluare. Probleme 1.2.1. Fie graful,

1

2 3

4

5 7

6

8

9 10

11

şi v0=1. Prezentaţi tabelul valorilor distanţelor rezultat prin aplicarea metodei BF. 1.2.2. Scrieţi o sursă C pentru implementarea metodei BF. 1.2.3. Fie graful de la testul 1.2.1. Care este ordinea de parcurgere BF pentru v0=3?

Page 10: 94650969 19 ID Program Area Calculatoarelor

Programarea calculatoarelor 10

1.2.4. Fie graful 1

2

3

4

5 6

Care este ordinea de parcurgere BF pentru v0=2? 1.2.5. Fie graful, Prezentaţi evoluţia determinată de aplicarea metodei de traversare BF pentru vârful iniţial v0=3.

19

82 4

3

65

7

1.3 Metoda de parcurgere DF (Depth First)

Ideea metodei DF revine la parcurgerea în adîncime a grafurilor. Considerînd v0 vîrf iniţial şi M mulţimea vîrfurilor vizitate de procedură, pentru vizitarea vecinilor este considerat unul din vîrfurile din M cu proprietatea că lungimea drumului calculat de metodă pînă la vîrful iniţial v0 este maximă.

Implementarea acestei metode poate fi realizată în mai multe moduri, pentru menţinerea mulţimii vîrfurilor grafului disponibilizate pînă la momentul curent fiind utilizată o structură de de date de tip stivă S. La momentul iniţial se introduce în stivă v0. La fiecare pas, se preia cu ştergere ca vîrf curent vîrful stivei S şi se introduc în stivă vecinii încă nevizitaţi ai vîrfului curent. Un vîrf se marchează ca vizitat în momentul introducerii lui în S. Calculul continuă pînă cînd este efectuat un acces de preluare din stivă şi se constată că S este vidă. Pentru gestiunea vîrfurilor vizitate, se utilizează un vector c cu n componente, unde n reprezintă numărul vîrfurilor grafului şi, la fiecare moment, componentele sînt:

⎩⎨⎧

=altfel,0

vizitatfostaidacă,1ci

Componentele vectorului c vor fi iniţializate cu valoarea 0. Exemple 1.3.1. Pentru graful,

1

2

5

6

7

3

4

Page 11: 94650969 19 ID Program Area Calculatoarelor

* Material didactic pentru ID * 11

şi v0=1, prin aplicarea metodei descrise, rezultă următoarea evoluţie.

c t 1 2 3 4 5 6 7

t=1 1 0 0 0 0 0 0 t=2 1 1 1 1 0 0 1 t=3 1 1 1 1 0 1 1 t=4 1 1 1 1 0 1 1 t=5 1 1 1 1 1 1 1 t=6 1 1 1 1 1 1 1 t=7 1 1 1 1 1 1 1 t=8 1 1 1 1 1 1 1

S t

t=1 1 t=2 7 4 3 2 t=3 6 4 3 2 t=4 4 3 2 t=5 5 3 2 t=6 3 2 t=7 2

Ordinea în care sînt vizitate vîrfurilor corespunzător acestei variante de parcurgere DF este: 1, 2,

3, 4, 7, 6, 5. O variantă de implementare a metodei DF rezultă prin gestionarea stivei S în modul următor.

Iniţial vîrful v0 este unicul component al lui S. La fiecare etapă se preia, fără ştergere, ca vîrf curent vîrful stivei. Se introduce în stivă unul dintre vecinii vîrfului curent încă nevizitat. Vizitarea unui vîrf revine la introducerea lui în S. Dacă vîrful curent nu are vecini încă nevizitaţi, atunci el este eliminat din stivă şi este efectuat un nou acces de preluare a noului vîrf al stivei ca vîrf curent. Calculul se încheie în momentul în care este efectuat un acces de preluare a vîrfului stivei ca vîrf curent şi se constată că S este vidă. Evident, nici în cazul acestei variante nu vor fi vizitate vîrfurile care nu sînt conectate cu vîrful iniţial ales.

Exemplu 1.3.2. Pentru graful,

1

2

5

6

7

3

4

Page 12: 94650969 19 ID Program Area Calculatoarelor

Programarea calculatoarelor 12

şi v0=1, prin aplicarea metodei descrise, rezultă următoarea evoluţie.

c t 1 2 3 4 5 6 7

t=1 1 0 0 0 0 0 0 t=2 1 1 0 0 0 0 0 t=3 1 1 0 1 0 0 0 t=4 1 1 1 1 0 0 0 t=5 1 1 1 1 0 1 0 t=6 1 1 1 1 0 1 1 t=7 1 1 1 1 0 1 1 t=8 1 1 1 1 0 1 1 t=9 1 1 1 1 0 1 1 t=10 1 1 1 1 1 1 1 t=11 1 1 1 1 1 1 1 t=12 1 1 1 1 1 1 1 t=13 1 1 1 1 1 1 1 t=14 1 1 1 1 1 1 1 S T

t=1 1 t=2 2 1 t=3 4 2 1 t=4 3 4 2 1 t=5 6 3 4 2 1 t=6 7 6 3 4 2 1 t=7 6 3 4 2 1 t=8 3 4 2 1 t=9 4 2 1 t=10 5 4 2 1 t=11 4 2 1 t=12 2 1 t=13 1 t=14

Ordinea în care sînt vizitate vîrfurile corespunzător acestei variante este: 1, 2, 4, 3, 6, 7, 5.

Teste de autoevaluare. Probleme 1.3.1. Fie graful,

1

2 3

4

5 7

6

8

9 10

11

şi v0=1. Care este ordinea de vizitare a vârfurilor prin aplicarea metodei DF în prima variantă prezentată? 1.3.2. Scrieţi o sursă C pentru implementarea metodei DF în cea de-a doua variantă.

Page 13: 94650969 19 ID Program Area Calculatoarelor

* Material didactic pentru ID * 13

1.4. Drumuri în grafuri. Conexitate

Una dintre cele mai importante proprietăţi ale grafurilor o constituie posibilitatea de accesare, prin intermediul unei secvenţe de muchii (arce), dintr-un vîrf dat a oricărui alt vîrf al grafului, proprietate cunoscută sub numele de conexitate sau conexiune. Aşa după cum a rezultat în §1.2. şi 1.3, dacă G=(V,E) este un graf conex, atunci pentru orice vîrf iniţial v0 considerat metodele BF şi DF permit vizitarea tuturor vîrfurilor din V.

Definiţia 1.4.1. Fie G=(V,E) un graf, u,v∈V. Secvenţa de vîrfuri Γ: u0, u1,..,un este un u-v drum dacă u0=u, un=v, uiui+1∈E pentru toţi i, ni0 ≤≤ . Lungimea drumului, notată l(Γ) este egală cu n. Convenţional, se numeşte drum trivial, un drum Γ cu l(Γ)=0.

Definiţia 1.4.2. Fie Γ: u0, u1,..,un un drum în graful G=(V,E). Γ este un drum închis dacă u0=un; în caz contrar, Γ se numeşte drum deschis. Drumul Γ este elementar dacă oricare două vîrfuri din Γ sînt distincte, cu excepţia, eventual, a extremităţilor. Drumul Γ este proces dacă, pentru orice

1nji0 −≤≠≤ uiui+1≠ ujuj+1. Evident, orice drum elementar este un proces. Exemplu 1.4.1. Pentru graful,

v1

v2

v3

v4

v5

Γ1: v1, v2, v3, v2, v5, v3, v4 este un v1- v4 drum care nu este proces; Γ2: v1, v2, v5, v1, v3, v4 este un v1- v4 proces care nu este drum elementar; Γ3: v1, v3, v4 este un v1- v4 drum elementar.

Definiţia 1.4.3. Fie Γ: u0, u1,..,un un drum în graful G=(V,E). Γ’: v0, v1,..,vm este un subdrum al lui Γ dacă Γ’ este un drum şi pentru orice j, mj0 ≤≤ , există i, ni0 ≤≤ astfel încît ui=vj.

Observaţie Orice drum cu lungime cel puţin 1 conţine cel puţin un drum elementar cu aceleaşi

extremităţi. Într-adevăr, dacă Γ: u0, u1,..,un nu este elementar, atunci există nji0 ≤<≤ şi i≠ 0 sau j ≠ n

astfel încît ui=uj. Atunci drumul

⎪⎩

⎪⎨

≠≠=

=

+

+

nj,0idacă,u...uu...uu0jdacă,u...uu

0idacă,u...uu

:

n1ji10

i10

n1jj'Γ

este de asemenea un u0-un drum. Aplicînd în continuare eliminarea duplicatelor vîrfurilor în modul descris, rezultă în final un u0-um drum elementar.

Page 14: 94650969 19 ID Program Area Calculatoarelor

Programarea calculatoarelor 14

Exemplu 1.4.2. În graful,

v1

v2

v3

v4 v5

v6 v7

v8

v9

v10

dacă Γ: v1, v2, v4, v5, v3, v1, v2, v5, v6, v7, v8, v9, v5, v9, v8, v10, atunci Γ1: v1, v2, v5, v9, v8, v10, Γ2: v1, v2, v4, v5, v9, v8, v10 sînt v1-v10 subdrumuri elementare. Matricea existenţei drumurilor; algoritmul Roy-Warshall

Lema 1.4.1. Fie G=(V,E) un graf, nV = . Dacă A este matricea de adiacenţă asociată grafului,

atunci, pentru orice p≥1, )p(ija este numărul vi-vj drumurilor distincte de lungime p din graful G, unde

( ))p(ij

p aA = . Definiţia 1.4.4. Fie Mn({0,1)} mulţimea matricelor de dimensiuni nxn, componentele fiind

elemente din mulţimea {0,1}. Pe Mn({0,1)}se definesc operaţiile binare, notate ⊕ şi ⊗ , astfel: pentru orice A=(aij), B=(bij) din Mn({0,1)}, A⊕ B=(cij), A⊗ B=(dij), unde

nj,i1 ≤≤ , cij=max{aij, bij} dij=max{min{aik, bkj}, nk1 ≤≤ }.

Dacă A=(aij) ∈ Mn({0,1)}, se notează ( ){ }1k;aA)k(

ijk

≥= secvenţa de matrice definită prin: ( )

2k,AAA,AA)1k(k1

≥∀⊗==−

. Dacă A este matricea de adiacenţă a unui graf G=(V,E), atunci pentru fiecare k, 1nk1 −≤≤ ,

⎩⎨⎧

=altfel,0

klungimedejlailadedrumexistădacă,1a

)k(ij

Matricea )1n()2()1(

AAAM−

⊕⊕⊕= K se numeşte matricea existenţei drumurilor în graful G. Semnificaţia componentelor matricei M este:

⎩⎨⎧ −

=≤≤∀altfel,1

Gîndrumvvexistănudacă,0m,nj,i1 ji

ij

Exemplu 1.4.3. Pentru graful,

1

2

3

4

⎟⎟⎟⎟⎟

⎜⎜⎜⎜⎜

=

⎟⎟⎟⎟⎟

⎜⎜⎜⎜⎜

=

⎟⎟⎟⎟⎟

⎜⎜⎜⎜⎜

=

⎟⎟⎟⎟⎟

⎜⎜⎜⎜⎜

=

1111111111111111

,

1111111111011111

,

1111111111101101

,

0101100100011110

32MAAA

Page 15: 94650969 19 ID Program Area Calculatoarelor

* Material didactic pentru ID * 15

Observaţie Calculul matricei existenţei drumurilor permite verificarea dacă un graf dat este conex. Graful este conex dacă şi numai dacă toate componentele matricei M sînt egale cu 1.

Algoritmul Roy-Warshall calculează matricea existenţei drumurilor într-un graf G cu n vîrfuri. void Roy_Warshall (unsigned char a[10][10],unsigned n,unsigned char m[10][10]) {int i,j,k; for (i=0;i<n;i++) for (j=0;j<n;j++) m[i][j]=a[i][j]; for (j=0;j<n;j++) for (i=0;i<n;i++) if(m[i][j]) for (k=0;k<n;k++) if (m[i][k]<m[k][j]) m[i][k]=m[k][j];}

Datele de intrare sînt: n, numărul de noduri şi A, matricea de adiacenţă corespunzătoare grafului. Matricea M calculată de algoritm constituie ieşirea şi este matricea existenţei drumurilor în graful G.

Componente conexe ale unui graf Definiţia 1.4.5. Fie G=(V,E) graf netrivial. Vîrfurile u,v ∈V sînt conectate dacă există un u-v

drum în G. Definiţia 1.4.6. Dacă G este un graf, atunci o componentă conexă a lui G este un subgraf conex

al lui G, maximal în raport cu proprietatea de conexitate. Exemplu 1.4.4. Componentele conexe ale grafului

1

2

3

5

4

6

sînt: C1={1,2,3}, C2={4,5}, C3={6}.

Observaţii Un graf este conex dacă şi numai dacă numărul componentelor sale conexe este 1. Mulţimile de vîrfuri corespunzătoare oricăror două componente conexe distincte sînt disjuncte.

Rezultă că mulţimile de vîrfuri corespunzătoare componentelor conexe ale unui graf formează o partiţie a mulţimii vîrfurilor grafului.

Problema determinării componentelor conexe corespunzătoare unui graf poate fi rezolvată în

modul următor. Iniţial, este selectat drept vîrf curent un vîrf al grafului pentru care este calculată componenta conexă care îl conţine. Dacă există vîrfuri care nu aparţin componentei conexe determinate, este ales drept vîrf curent unul dintre aceste vîrfuri. În continuare este aplicată aceeaşi metodă, pînă cînd au fost găsite toate componentele conexe ale grafului.

Determinarea componentei conexe care conţine un vîrf v0 dat poate fi realizată pe baza următorului algoritm. Pentru G=(V,E), nV = , n 1≥ şi v0∈V, paşii algoritmului sînt: Pas1: V0={v0}; E0=Φ ; i=0; Pas 2: repetă Pas 3 pînă cînd Vi=Vi-1 şi Ei=Ei-1 Pas 3: i=i+1;

{ }{ };ecuincidentu,Vu,Ee/eEE

;Euv,Vu,Vv/vVV

1i1ii

1i1ii

−−

−−

∈∃∈∪=∈∈∃∈∪=

Page 16: 94650969 19 ID Program Area Calculatoarelor

Programarea calculatoarelor 16

Ieşirea este Gi=(Vi,Ei), componenta conexă din care face parte v0. Exemplu 1.4.5. Pentru graful,

1 2

7 3

4 5 8 69

Aplicarea algoritmului descris pentru v0=1, determină următoarea evoluţie: I Vi Ei

i=0 {1} Ø i=1 {1,2,4} {(1,2),(1,4)} i=2 {1,2,4,7,8,5} {(1,2),(1,4),(2,7),(2,8),(7,8),(4,5),(4,7),(5,8)}

Teste de autoevaluare. 1.4.1. Fie graful,

1

2 3

4

5 7

6

8

9 10

11

Câte component conexe are graful?

1.4.2. Pentru graful de mai sus, determinaţi componenta conexă care conţine vârful 5, aplicând

algoritmul de mai sus. 1.4.3.Fie graful,

1

2

3

4

5 6

Care este matricea existenţei drumurilor?

Page 17: 94650969 19 ID Program Area Calculatoarelor

* Material didactic pentru ID * 17

1.5. Drumuri de cost minim

Definiţia 1.5.1. Fie G=(V,E,w) un graf ponderat. Costul drumului Γ: u1,u2,..,un, notat L(Γ), este definit prin:

( ) ( )∑−

=+=

1n

1i1ii u,uwL Γ .

Pentru orice u şi v vîrfuri conectate în G, u≠ v, w-distanţa între u şi v, notată D(u,v), este definită prin,

( ) ( ){ }uvD,Lminv,uD ∈= ΓΓ , unde Duv desemnează mulţimea tuturor u-v drumurilor

elementare din G. Dacă uvD∈Γ este astfel încît D(u,v)=L(Γ), drumul Γ se numeşte drum de cost minim.

Observaţie Cu toate că este utilizat termenul de w-distanţă, în general D nu este o distanţă în

sensul matematic al cuvîntului.În particular, dacă funcţia pondere asociază valoarea 1 fiecărei muchii a grafului, atunci pentru fiecare pereche de vîrfuri distincte ale grafului, costul D(u,v) este lungimea unui cel mai scurt drum între cele două vîrfuri. În acest caz D este o distanţă pe mulţimea vîrfurilor.

Algoritmul Dijkstra Următorul algoritm a fost propus de către E. W. Dijkstra pentru determinarea w-distanţelor

D(u0,v) şi a cîte unui u0-v drum de cost minim pentru fiecare vîrf v≠u0 într-un graf ponderat, unde u0 este prestabilit. Fie G=(V,E,w) un graf conex ponderat, u0∈V, S⊂V, u0∈S. Se notează S\VS = şi

( ) ( ){ }Sx;x,uDminS,uD 00 ∈= . Fie v∈ S astfel încît D(u0,v)=D(u0, S ), Γ : u0, u1,…,upv un u0-v

drum de cost minim. Evident, ∀0≤i≤p ui∈S şi Γ ’: u0, u1,…,up un u0- up drum de cost minim. De asemenea,

( ) ( ){ }Euv,Sv,Su);uv(wu,uDminS,uD 00 ∈∈∈+= .

Dacă x∈S, y∈ S astfel încît ( ) ( ) )xy(wx,uDS,uD 00 += , rezultă

( ) ( ) )xy(wx,uDy,uD 00 += . Pentru determinarea a cîte unui cel mai ieftin u0-v drum, algoritmul consideră o etichetare

dinamică a vîrfurilor grafului.Eticheta vîrfului v este (L(v),u), unde L(v) este lungimea unui cel mai ieftin u0-v drum determinat pînă la momentul respectiv şi u este predecesorul lui v pe un astfel de drum.

Pentru (V,E,w) graf conex ponderat, nV = şi u0∈V, calculul implicat de algoritmul Dijkstra

poate fi descris astfel: Pas 1: i=0; S0={u0}; L(u0)=0, L(v)=∞ pentru toţi v∈V, v≠u0. Dacă n=1 atunci stop Pas 2: Pentru toţi v∈ iS , dacă L(v)>L(ui)+w(uiv), atunci L(v)=L(ui)+w(uiv) şi etiche-tează v cu (L(v),ui). Pas 3: Se determină d=min{L(v), v∈ iS } şi se alege ui+1∈ iS astfel încît L(ui+1)=d. Pas 4: Si+1=Si∪ {ui+1} Pas 5: i=i+1. Dacă i=n-1, atunci stop. Altfel, reia Pas 2.

Observaţie Dacă (V,E,w) graf ponderat neconex, atunci, pentru u0∈V, algoritmul lui Dijkstra permite determinarea w-distanţelor D(u0,v) şi a cîte unui u0-v drum de cost minim pentru toate vîrfurile v din componenta conexă căreia îi aparţine u0.

Page 18: 94650969 19 ID Program Area Calculatoarelor

Programarea calculatoarelor 18

Exemplu 1.5.1. Fie graful ponderat,

1

2 3

4 5

1 5

9

2 5

16

Considerînd u0=1, etapele în aplicarea algoritmului Dijkstra sînt: P1: i=0; S0={1}; L(1)=0, L(i)=∞ pentru toţi 5,2i = .

P2: 0S ={2,3,4,5}, u0=1 L(2)= ∞>L(1)+5=5 ⇒ L(2)=5, etichetează 2 cu 1 L(3)= ∞>L(1)+1=1 ⇒ L(3)=1, etichetează 3 cu 1 L(4)= ∞>L(1)+9=9 ⇒ L(4)=9, etichetează 4 cu 1 L(5)= ∞ , w(1,5)= ∞ , deci L(5) nu se modifică P3: selectează u1=3, L(3)=1, cea mai mică dintre w-distanţele calculate la P2 P4: S1={1,3} P5: i=i+1=1 ≠ 4, reia P2 P2: 1S ={2,4,5}, u1=3 Nu se modifică nici o etichetă şi nici o w-distanţă (w(3,i)= ∞ , pentru toţi i din 1S P3: selectează u2=2, L(2)=5, cea mai mică dintre w-distanţele calculate la P2 P4: S2={1,3,2} P5: i=i+1=2 ≠ 4, reia P2 P2: 2S ={4,5}, u2=2 L(4)= 9>L(2)+2=7 ⇒ L(4)=7, etichetează 4 cu 2 L(5)= ∞>L(2)+16=21, etichetează 5 cu 2 P3: selectează u3=4, L(4)=7, cea mai mică dintre w-distanţele calculate la P2 P4: S3={1,3,2,4} P5: i=i+1=3 ≠ 4, reia P2 P2: 3S ={5}, u3=4 L(5)= 21>L(4)+5=12, etichetează 5 cu 4 P3: selectează u4=5, L(5)=12, cea mai mică dintre w-distanţele calculate la P2 P4: S3={1,3,2,4,5} P5: i=i+1=4, stop.

Algoritmul calculează următoarele rezultate: Vîrful v pînă la care este calculată w-distanţa 1 2 3 4 5 D(1,v), eticheta lui v 0, 1 5, 1 1, 1 7, 2 12, 4

Drumurile de cost minim de la vîrful 1 la fiecare dintre vîrfurile grafului se stabilesc pe baza sistemului de etichete astfel: drumul de la 1 la un vîrf v este dat de: v1, eticheta lui v, v2 eticheta lui v1 şamd, pînă se ajunge la eticheta 1. Astfel, v0 -drumurile de cost minim sînt:

pînă la 2: 2,1; pînă la 3: 3,1; pînă la 4: 4,2,1; pînă la 5: 5,4,2,1.

Page 19: 94650969 19 ID Program Area Calculatoarelor

* Material didactic pentru ID * 19

În anumite aplicaţii este necesară exclusiv determinarea w-distanţelor D(v0,v), pentru toţi v∈V. În acest caz algoritmul Roy-Floyd permite o rezolvare a acestei probleme mai simplu de implementat decît algoritmul Dijkstra.

Algoritmul Roy-Floyd Pentru (V,E,w) graf ponderat, nV = şi W matricea ponderilor, sistemul de w-distanţe D(v0,v),

v∈V, poate fi calculat pe baza următoarei funcţii (similară algoritmului Roy-Warshall), void Roy_Floyd (float w[10][10],unsigned n,float d[10][10],float MAX) {int i,j,k; for (i=0;i<n;i++) for (j=0;j<n;j++) d[i][j]=w[i][j]; for (j=0;j<n;j++) for (i=0;i<n;i++) if(d[i][j]<MAX) for (k=0;k<n;k++) if (d[i][k]>d[i][j]+d[j][k]) d[i][k]=d[i][j]+d[j][k]; }

Matricea D calculată de algoritm este matricea w-distanţelor D(u,v) în graful ponderat conex

(V,E,w); pentru orice nj,i1 ≤≤

⎩⎨⎧

∞=

altfel,

conectatesuntv,v),v,v(Dd jiji

ij

Într-adevăr, procedura realizează calculul dinamic al w-distanţei între oricare două vîrfuri i şi k, astfel: dacă există un drum i-k drum ce trece prin j ( nj1 ≤≤ ), cu costul corespunzător (dij+djk) inferior costului curent (dik), atunci noul drum de la i la k via j este de cost mai mic decît costul drumului vechi, deci w-distanţa între i şi k trebuie reactualizată la dij+djk.

Teste de autoevaluare. 1.5.1. Fie graful ponderat,

Considerînd u0=1, descrieţi rezultatele aplicării algoritmului Dijkstra acestui graf. 1.5.2. Scrieţi o sursă C pentru implementarea algoritmului Dijkstra 1.5.3. Fie graful,

Considerînd u0=4, descrieţi rezultatele aplicării algoritmului Dijkstra acestui graf.

1

23

45

12

9

121

1

1

23

45

15

9

25

16

Page 20: 94650969 19 ID Program Area Calculatoarelor

Programarea calculatoarelor 20

1.5.4. Fie graful ponderat, 1

2

3 4

5

4

4

5

2

3 7

1

Care este matricea w-distanţelor în graf?

1.6. Răspunsuri la testele de autoevaluare. Comentarii

1.1.1.

⎟⎟⎟⎟⎟⎟⎟⎟

⎜⎜⎜⎜⎜⎜⎜⎜

=

010110100110000000

110001110001000110

A

1.1.2.

⎟⎟⎟⎟⎟⎟

⎜⎜⎜⎜⎜⎜

=

01101000011000010000

00110

A

1.1.3.Numărul de vârfuri este 5. T

A ⎟⎟⎠

⎞⎜⎜⎝

⎛=

43115525554321

1.1.4.

⎟⎟⎟⎟⎟⎟

⎜⎜⎜⎜⎜⎜

∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞

=127

1424575

W

1.2.1. Valorile rezultate prin aplicarea metodei sînt:

vîrf d

1 2 3 4 5

6

7 8

9

10 11

0 0 ∞ ∞ ∞ ∞ ∞ ∞ ∞ ∞ ∞ ∞ 1 0 1 1 ∞ 1 ∞ 1 ∞ ∞ ∞ ∞ 2 0 1 1 2 1 2 1 ∞ ∞ ∞ ∞ 0 1 1 2 1 2 1 ∞ ∞ ∞ ∞

Page 21: 94650969 19 ID Program Area Calculatoarelor

* Material didactic pentru ID * 21

Se observă că valorile lui d calculate în final reprezintă numărul de muchii corespunzător unui cel mai scurt drum care conectează vîrful iniţial cu vîrful respectiv, pentru vîrfurile neconectate cu v0 valoarea d[v0] rezultată la terminarea calculului este ∞ .

1.2.2 O sursă C pentru implementarea metodei BF este următoarea. #include <stdio.h> #include <conio.h> #include <alloc.h> typedef struct nn { int inf; struct nn *leg; } nod,* pnod; int insereaza_coada(pnod *head,pnod *tail,int info) { pnod nou; if(nou=(pnod)malloc(sizeof(nod))){ nou->inf=info; nou->leg=NULL; if(*head==NULL) *head=nou; else (*tail)->leg=nou; *tail=nou; return 1; } else return 0; } int extrage_coada(pnod *head,pnod *tail, int *info) { if(*head){ pnod aux=*head; *info=(*head)->inf; (*head)=(*head)->leg; free(aux); if(*head==NULL)*head=*tail=NULL; return 1; } else return 0;} void breadth_first(int v0,int a[10][10],int n) { pnod head=NULL; pnod tail=NULL; int c[10]; for(int i=0;i<n;c[i++]=0); int r=insereaza_coada(&head,&tail,v0); c[v0]=1; while(head){ r=extrage_coada(&head,&tail,&i); printf("\n%i",i+1); for(int k=0;k<n;k++) if((a[i][k]==1)&&(c[k]==0)){ r=insereaza_coada(&head,&tail,k); c[k]=1; } } }

void main() { int n,v0,a[10][10]; clrscr(); printf("Numarul de varfuri:"); scanf("%i",&n); printf("\nMatricea de adiacenta\n"); for(int i=0;i<n;i++) for(int j=0;j<i;j++){ scanf("%i",&v0); a[j][i]=a[i][j]=v0; } for(i=0;i<n;i++)a[i][i]=0; printf("\nVarful initial "); scanf("%i",&v0); printf("\nParcurgerea BF a grafului este"); breadth_first(v0,a,n); }

Page 22: 94650969 19 ID Program Area Calculatoarelor

Programarea calculatoarelor 22

1.2.3. Ordinea în care sînt vizitate vîrfurilor corespunzător BF este: 3, 1, 4, 6, 2, 5, 7. 1.2.4. Ordinea în care sînt vizitate vîrfurilor corespunzător BF este: 2, 1, 5, 3,6. 1.2.5. Aplicarea metodei de traversare BF determină următoarea evoluţie,

c t 1 2 3 4 5 6 7

8 9

t=1 0 0 1 0 0 0 0 0 0 t=2 0 1 1 1 0 1 0 0 0 t=3 1 1 1 1 1 1 0 0 0 t=4 1 1 1 1 1 1 0 0 0 t=5 1 1 1 1 1 1 0 0 0 t=6 1 1 1 1 1 1 0 0 0 t=7 1 1 1 1 1 1 1 0 0 t=8 1 1 1 1 1 1 1 0 0 C t

t=1 3 t=2 2 4 6 t=3 4 6 1 5 t=4 6 1 5 t=5 1 5 t=6 5 t=7 7 t=8

1.3.1. Ordinea în care sînt vizitate vîrfurilor corespunzător DF în prima varianta prezentată este:

1, 2, 3, 4, 6, 7, 5. 1.3.2 Următoarea sursă C implementează varianta a doua a parcurgerii DF.

#include <stdio.h> #include <conio.h> #include <alloc.h> typedef struct nn{ int inf; struct nn *leg; }nod,* pnod; int insereaza_stiva(pnod *head,int info) { pnod nou; if(nou=(pnod)malloc(sizeof(nod))){ nou->inf=info; nou->leg=*head; *head=nou; return 1; } else return 0; } int extrage_stiva(pnod *head,int *info) { if(head){ pnod aux=*head; *info=(*head)->inf; (*head)=(*head)->leg; free(aux); return 1; } else return 0;} void depth_first(int v0,int a[10][10],int n) {

Page 23: 94650969 19 ID Program Area Calculatoarelor

* Material didactic pentru ID * 23

pnod head=NULL; int c[10]; for(int i=0;i<n;c[i++]=0); int r=insereaza_stiva(&head,v0); c[v0]=1; printf("\n%i",v0+1); while(head){ r=extrage_stiva(&head,&i); for(int k=0;k<n;k++) if((a[i][k]==1)&&(c[k]==0)){ r=insereaza_stiva(&head,k); c[k]=1;printf("\n%i",k+1); } } } void main() { int n,v0,a[10][10]; clrscr(); printf("Numarul de varfuri:");scanf("%i",&n); printf("\nMatricea de adiacenta\n"); for(int i=0;i<n;i++) for(int j=0;j<i;j++){ scanf("%i",&v0); a[j][i]=a[i][j]=v0; } for(i=0;i<n;i++)a[i][i]=0; printf("\nVarful initial ");scanf("%i",&v0); printf("\nParcurgerea DF a grafului este"); depth_first(v0,a,n); }

1.4.1. Graful are 2 componente conexe. 1.4.2. Aplicarea algoritmului descris pentru v0=5, determină următoarea evoluţie:

I Vi Ei

i=0 {5} Ø i=1 {1,4,5} {(1,5),(4,5)} i=2 {1,2,3,4,5,7} {(1,5),(4,5),(1,2),(1,3),(2,4),(3,4),(1,7),(4,7)} i=3 {1,2,3,4,5,6,7} {(1,5),(4,5),(1,2),(1,3),(2,4),(3,4),(1,7),(4,7),(3,6),(3,7)}

1.4.3. Matricea existenţei drumurilor este

⎟⎟⎟⎟⎟⎟⎟⎟

⎜⎜⎜⎜⎜⎜⎜⎜

=

110111110111000000110111110111110111

A

Page 24: 94650969 19 ID Program Area Calculatoarelor

Programarea calculatoarelor 24

1.5.1. Algoritmul calculează următoarele rezultate:

Vîrful v pînă la care este calculată w-distanţa 1 2 3 4 5 D(1,v), eticheta lui v 0, 1 2, 1 1, 1 4, 5 3, 2

Drumurile de cost minim de la vîrful 1 la fiecare dintre vîrfurile grafului se stabilesc pe baza sistemului de etichete astfel: drumul de la 1 la un vîrf v este dat de: v1, eticheta lui v, v2 eticheta lui v1 şamd, pînă se ajunge la eticheta 1. Astfel, v0 -drumurile de cost minim sînt:

pînă la 2: 2,1; pînă la 3: 3,1; pînă la 4: 4,5,2,1; pînă la 5: 5,2,1. 1.5.2. Următoarea sursă C implementează algoritmul Dijkstra.

#include<stdio.h> #include<conio.h> #include<alloc.h> typedef struct{ int predv; float L; } eticheta; void creaza(int *s,int *sb,int nv,int u0) { s[0]=u0; for(int j=0,i=0;i<nv;i++) if(i-u0)sb[j++]=i; } void modifica(int *s,int *sb,int ui, int *ns, int *nb) { s[*ns]=ui; (*ns)++; for(int i=0;i<*nb;i++) if(sb[i]==ui){ for(int j=i+1;j<*nb;j++) sb[j-1]=sb[j]; (*nb)--; return; } } eticheta *Dijkstra(float w[][50],int nv,int u0) { eticheta *r=(eticheta *)malloc(nv*sizeof(eticheta)); for(int i=0;i<nv;i++)r[i].L=1000; r[u0].L=0; r[u0].predv=u0; int s[50],sb[50],ns=1,nb=nv-1; creaza(s,sb,nv,u0); for(i=0;i<nv-1;i++){ float dmin=1000; for(int j=0;j<nb;j++) for(int k=0;k<ns;k++) if(r[sb[j]].L>r[s[k]].L+w[sb[j]][s[k]]){ r[sb[j]].L=r[s[k]].L+w[sb[j]][s[k]]; r[sb[j]].predv=s[k]; } int ui; for(j=0;j<nb;j++) if(r[sb[j]].L<dmin){ dmin=r[sb[j]].L; ui=sb[j]; } modifica(s,sb,ui,&ns,&nb); } return r; } void main() {

Page 25: 94650969 19 ID Program Area Calculatoarelor

* Material didactic pentru ID * 25

int n,i,j; clrscr(); printf("Numarul de varfuri"); scanf("%i",&n); printf("Matricea ponderilor:\n"); float w[50][50]; for(i=0;i<n;i++) for(j=0;j<n;j++) scanf("%f",&w[i][j]); int u0; printf("\nVarful initial:"); scanf("%i",&u0);u0--; eticheta *rez=Dijkstra(w,n,u0); for(i=0;i<n;i++){ printf("Distanta de la vf. %i la vf. %i este %7.2f\n",u0+1,i+1,rez[i].L); printf("Un drum de cost minim este:"); printf("%i, ",i+1); j=rez[i].predv; while(j-u0){ printf("%i, ", j+1); j=rez[j].predv; } printf("%i\n\n",u0+1); } free(rez); getch(); }

1.5.3. Algoritmul calculează următoarele rezultate:

Vîrful v pînă la care este calculată w-distanţa

1 2 3 4 5

D(4,v), eticheta lui v 7, 2 2, 4 8, 1 0, 4 5, 4 Astfel, v0 -drumurile de cost minim sînt: pînă la 1: 1,2,4; pînă la 2: 2,4; pînă la 3: 3,1,2,4; pînă la 5: 5,4

1.5.4. Matricea este:

⎟⎟⎟⎟⎟⎟

⎜⎜⎜⎜⎜⎜

∞∞∞∞∞∞

∞∞∞∞

=

1447

145253

3623

D

Bibliografie 1. I. Gh. Roşca, B. Ghilic-Micu, C. Cocianu, M. Stoica, C. Uscatu, M. Mircea − Programarea calculatoarelor. Algoritmi în programare, Ed. ASE Bucureşti, 2007 2. I. Gh. Roşca, B. Ghilic-Micu, C. Cocianu, M. Stoica, C. Uscatu − Programarea calculatoarelor. Ştiinţa învăţării unui limbaj de programare. Teorie şi aplicaţii, Ed. ASE Bucureşti, 2003 3. Liviu Negrescu − Limbajele C şi C++ pentru începători, vol. I, II, Ed. Microinformatica, Cluj-Napoca, 1994

Page 26: 94650969 19 ID Program Area Calculatoarelor

Programarea calculatoarelor 26

2. Structuri dinamice de date. Liste

Obiective Principalele obiective vizează însuşirea şi aprofundarea de către studenţi a noţiunilor fundamentale legate de definirea, necesitatea şi utilizarea structurii de listă cu legături, precum şi a operaţiilor primitive asupra acestora. Cuprins 2.1. Reprezentarea listelor ..................................................................................................... 26 2.2. Operaţii primitive asupra listelor liniare ...................................................................... 27 2.3. Liste circulare .................................................................................................................. 32 2.4. Stive şi cozi ....................................................................................................................... 35 2.5 Răspunsuri la testele de autoevaluare. Comentarii ....................................................... 37

Timpul mediu de învăţare pentru studiul individual: 7 ore

2.1. Reprezentarea listelor

Organizarea de tip listă corespunde unei structurări lineare a datelor, în sensul că la nivelul

fiecărei componente există suficientă informaţie pentru identificarea următoarei componente a colecţiei. Datele unei mulţimi structurate prin intermediul listelor sunt referite de obicei prin termenii de noduri, celule, componente etc.

Reprezentarea unei liste poate fi realizată static, prin intermediul structurii de date vector, în acest caz ordinea componentelor fiind dată de ordinea pe domeniul de valori corespunzător indexării şi, în consecinţă, următoarea componentă este implicit specificată. Memorarea unei mulţimi de date {d1, d2,…, dn} prin intermediul unei structuri statice poate fi realizată în limbajul C utilizând un masiv unidimensional.

Principalele dezavantaje ale utilizării reprezentării statice rezidă din volumul de calcule necesare efectuării operaţiilor de inserţie/eliminare de noduri şi din necesitatea păstrării unei zone de memorie alocată, indiferent de lungimea efectivă a listei.

Aceste dezavantaje pot fi eliminate prin opţiunea de utilizare a structurilor dinamice. Componentele unei liste dinamice sunt eterogene, fiecare nod conţinând o parte de informaţie şi câmpuri de legătură care permit identificarea celulelor vecine. Câmpurile de legătură sunt reprezentate de date de tip referinţă (adresă).

În cazul listelor cu un singur câmp de legătură (simplu înlănţuite), valoarea câmpului indică adresa nodului următor, în timp ce, în cazul listelor cu dublă legătură (dublu înlănţuite), valorile memorate în câmpurile de legătură sunt adresele componentelor care preced şi, respectiv, urmează celulei. În ambele situaţii, câmpul de legătură pentru indicarea celulei următoare corespunzător ultimei componente a listei are valoarea NULL în cazul listelor “deschise” (lineare) şi respectiv indică adresa primei componente din listă în cazul listelor “închise” (circulare).

Declararea tipurilor de date C pentru definirea structurilor de liste dinamice simplu şi respectiv

dublu înlănţuite este,

a) Listă simplu înlănţuită typedef struct nod{ tip_informatie inf; struct nod *leg; } list, *lista;

Page 27: 94650969 19 ID Program Area Calculatoarelor

* Material didactic pentru ID * 27

b) Listă dublu înlănţuită typedef struct nod{ tip_informatie inf; struct nod *ls, *ld; } list, *lista; unde tip_informatie este numele tipului de date C utilizat pentru memorarea fiecărei date din mulţimea{d1, d2,…, dn}.

În cele ce urmează vom considera că tip_informatie este tipul C int. Teste de autoevaluare 2.1.1 Definiţi structura de listă dinamică simplu înlănţuită, unde tipul de date informaţie este

float. 2.1.2 Definiţi structura de listă statică pentru o mulţime cu n elemente date caracter.

2.2. Operaţii primitive asupra listelor liniare

Accesul la informaţia stocată într-o variabilă de tip listă revine la efectuarea uneia sau mai multe dintre operaţiile primitive: regăsirea (dacă există) nodului care corespunde unei chei date (condiţie impusă asupra valorii câmpului de informaţie), inserarea unei noi componente în listă, eliminarea componentei (componentelor) cu proprietatea că valorile câmpurilor de informaţie satisfac o anumită cerinţă şi înlocuirea câmpului de informaţie corespunzător unei componente printr-o informaţie dată.

Accesarea componentelor unei liste reprezentată printr-o structură statică poate fi realizată atât secvenţial, cât şi direct, utilizând valorile indicelui considerat pentru indexare, în timp ce accesarea componentelor unei liste dinamice se realizează de regulă numai secvenţial, începând cu prima componentă şi continuând cu următoarele, pe baza valorilor câmpurilor de legătură.

Convenţional, numim cap al listei dinamice pointerul a cărui valoare este adresa primei componente a listei. În continuare ne vom referi exclusiv la liste dinamice, studiul listelor reprezentate prin intermediul vectorilor fiind propus cititorului.

1. Parcurgerea datelor memorate într-o listă Funcţia C parc implementează parcurgerea unei liste dinamice în varianta simplu înlănţuită şi în

cazul listelor dublu înlănţuite. Se presupune că declarările de tip pentru definirea structurilor de liste menţionate anterior sunt globale, relativ la procedurile descrise în continuare.

a) Lista reprezentată prin structură dinamică simplu înlănţuită

void parc(lista cap) { if(cap){ printf("%i ",cap->inf); parc(cap->leg); } }

b) Lista reprezentată prin structură dinamică dublu înlănţuită void parc(lista cap) { if(cap){ printf("%i ",cap->inf); parc(cap->ls); } }

Page 28: 94650969 19 ID Program Area Calculatoarelor

Programarea calculatoarelor 28

2. Regăsirea unei date d într-o colecţie memorată într-o listă Funcţia C cauta calculează adresa nodului în care este găsit elementul căutat. Dacă valoarea

căutată nu se regăseşte printre elementele listei, funcţia returnează valoarea NULL.

a) Lista reprezentată prin structură dinamică simplu înlănţuită lista cauta(lista cap,int info) { if(cap==NULL)return NULL; else if(cap->inf==info) return cap; else return cauta(cap->leg,info); }

b) Lista reprezentată prin structură dinamică dublu înlănţuită lista cauta(lista cap,int info) { if(cap==NULL)return NULL; else if(cap->inf==info) return cap; else return cauta(cap->ls,info); }

3. Inserarea unei date, d, într-o listă Includerea unei noi componente într-o listă poate fi realizată, în funcţie de cerinţele problemei

particulare, la începutul listei, după ultima componentă din listă, înaintea/după o componentă cu proprietatea că valoarea câmpului de informaţie îndeplineşte o anumită condiţie.

Deoarece prin inserarea unei componente se poate ajunge la depăşirea spaţiului disponibil de memorie, este necesară verificarea în prealabil dacă este posibilă inserarea sau nu (dacă se poate aloca spaţiu de memorie pentru componenta de inserat). În continuare ne vom referi la liste dinamice simplu înlănţuite, operaţiile de inserare în cazul listelor dinamice dublu înlănţuite putând fi realizate similar cazului listelor simplu înlănţuite, cu specificarea ambelor câmpuri de adresă ale nodurilor.

Pentru exemplificarea operaţiei de inserare sunt prezentate funcţiile de inserare la începutul listei, inserare după ultimul element al listei şi inserarea unei celule după un nod cu informaţie dată.

Inserarea la începutul listei Funcţia C inserare_la_inceput calculează valoarea 1 dacă adăugarea unui nou element este

posibilă (spaţiul de memorie este suficient pentru o nouă alocare), altfel returnează 0. În cazul în care inserarea este posibilă, prin apelul funcţii este realizată adăugarea unui nou nod la începutul listei. int inserare_la_inceput(lista *cap,int info) { lista nou; if(nou=(lista)malloc(sizeof(list))){ nou->inf=info; nou->leg=*cap; *cap=nou; return 1; } return 0; }

Inserarea după ultima componentă a unei liste Funcţia C inserare_la_sfarsit returnează 1 dacă şi numai dacă este posibilă o inserare, altfel

calculează 0. Pentru inserarea unui nou nod în listă dupa ultima celulă este necesar calculul ultimului nod al listei, notat p.

Page 29: 94650969 19 ID Program Area Calculatoarelor

* Material didactic pentru ID * 29

int inserare_la_sfarsit(lista *cap,int info) { lista nou; if(nou=(lista)malloc(sizeof(list))){ nou->leg=NULL;nou->inf=info; if(cap==NULL)*cap=nou; else {for(lista p=*cap;p->leg;p=p->leg); p->leg=nou; } return 1; } return 0; }

Inserarea unuei informaţii după o celulă cu informaţie cunoscută Inserarea unui nou nod într-o listă identificată prin variabila cap după o celulă p cu informaţie

cunoscută, infod, poate fi realizată astfel. Este apelată funcţia de căutare cauta, care calculează nodul p cu proprietatea că informaţia memorată în p este infodat. Dacă p este adresa vidă sau dacă spaţiul de memorie disponibil nu este suficient, inserarea nu poate fi realizată. În caz contrar, este inserat un nou nod între celulele p şi p->leg. int inserare_dupa_informatie(lista cap,int info,int infod) { lista nou,p; if(nou=(lista)malloc(sizeof(list))) if(p=cauta(cap,infod)){ nou->inf=info; nou->leg=p->leg; p->leg=nou; return 1; } return 0; }

4. Eliminarea unei date, d, dintr-o listă. Modificarea conţinutului unei liste prin eliminarea uneia sau mai multor componente poate fi

descrisă secvenţial, astfel încât este suficient să dispunem de o procedură care realizează eliminarea unei singure componente.

Criteriile de eliminare pot fi formulate diferit, cele mai uzuale fiind: prima componentă, ultima componentă, prima componentă care îndeplineşte o anumită condiţie, respectiv componenta care precede/urmează primei componente care îndeplineşte o condiţie dată.

În aceste cazuri este necesară verificarea existenţei în lista considerată a componentei ce trebuie eliminată. Această verificare asigură şi testarea faptului că lista prelucrată este vidă sau nu.

Informaţia i aşată nodului care este eliminat din listă reprezintă dată de ieşire pentru orice modul de eliminare, în cazul în care i nu este cunoscută înaintea eliminării (de exemplu, atunci când este solicitată eliminarea unei celule care conţine o informaţie dată) .

Eliminarea unui nod p poate fi realizată logic sau fizic. Eliminarea logică a celulei p este efectuată excluzând p din lista dinamică prin setarea legăturii nodului care precede p pe adresa succesorului lui p, dacă p nu este adresa primului element al listei, cap, respectiv prin atribuirea adresei primului element al listei cu cap->leg, în caz contrar.

Eliminarea cu ştergere fizică unui nod p presupune redenumirea acelui nod în scopul eliberării memoriei ocupate de p şi efectuarea operaţiilor descrise în cadrul procesului de eliminare logică.

Page 30: 94650969 19 ID Program Area Calculatoarelor

Programarea calculatoarelor 30

În continuare sunt prezentate următoarele tipuri de eliminări, cu ştergere fizică. Eliminarea primei componente a unei liste Funcţia C elimina_de_la_inceput calculează 1 dacă lista nu este vidă, deci eliminarea primului

nod este posibilă, altfel calculează 0. Dacă lista conţine măcar un nod, este eliminată prima celulă. int elimina_de_la_inceput(lista *cap,int *info) { if(*cap){ lista aux=*cap; *info=aux->inf; *cap=(*cap)->leg; free(aux); return 1; } return 0; }

Eliminarea ultimei componente a unei liste Similar operaţiei de inserare a unui nod după ultima celulă a unei liste, eliminarea ultimului nod

presupune determinarea acelei celule p cu proprietatea că p->leg este NULL. Funcţia C elimina_ultim returnează 1 dacă lista nu este vidă, caz în care este eliminat cu ştergere ultimul nod al listei. Dacă lista este vidă, funcţia calculează valoarea 0.

int elimina_ultim(lista *cap,int *info) { if (*cap){ if((*cap)->leg){ for(lista p=*cap;p->leg->leg;p=p->leg); *info=p->leg->inf; free(p->leg); p->leg=NULL; } else{ *info=(*cap)->inf; free(*cap); *cap=NULL; } return 1; } return 0; }

Eliminarea primei celule a unei liste care are informaţia egală cu o informaţie dată Pentru realizarea acestei operaţii putem proceda astfel. Sunt calculate aux şi p, unde aux este

nodul care precede celulei cu informaţie dată în lista din care este efectuată eliminarea (funcţia C cautaprecedent) şi p=aux->leg.. Dacă p este NULL, atunci eliminarea este imposibilă. Dacă aux este NULL, atunci eliminarea revine la extragerea cu ştergere a primului nod din listă, altfel este eliminată celula p, succesoare a lui aux în listă. Funcţia C elimina_informatie implementează operaţia de eliminare a unui nod cu informaţie dată, info, din lista identificată prin parametrul cap. lista cautaprecedent(lista cap,int info, lista *aux) { lista p; if(cap==NULL)return NULL; else{

Page 31: 94650969 19 ID Program Area Calculatoarelor

* Material didactic pentru ID * 31

for(p=NULL,*aux=cap;(*aux)&& ((*aux)->inf-info);p=*aux,*aux=(*aux)->leg);

if((*aux)==NULL)return NULL; return p; } } int elimina_informatie(lista *cap,int info) { lista aux,p; p=cautaprecedent(*cap,info,&aux); if(aux==*cap){ *cap=(*cap)->leg; free(aux); return 1; } else if(p) { p->leg=aux->leg; free(aux); return 1; } return 0; }

Eliminarea nodului care succede primei componente al cărei câmp de informaţie este cunoscut

Funcţia C elimina_dupa_informatie calculează valoarea 1 dacă eliminarea este posibilă, altfel calculează valoarea 0. În situaţia în care informaţia infodat a fost găsită în câmpul corespunzător nodului nodul p (p nu este NULL) şi p are succesor în listă, este realizată eliminarea nodului p->leg.

int elimină_dupa_informatie(lista cap,int *info, int infodat) { lista aux,p; p=cauta(cap,infodat); if((p)&&(p->leg)){ aux=p->leg; p->leg=aux->leg; *info=aux->inf; free(aux); return 1; } return 0; }

Teste de autoevaluare. Probleme Pentru problemele propuse în continuare recomandăm utilizarea macrodefiniţiei:

#define NEW (TNOD*)malloc(sizeof(TNOD)); unde typedef struct TNOD{float x; struct TNOD *next};

Page 32: 94650969 19 ID Program Area Calculatoarelor

Programarea calculatoarelor 32

2.2.1. Scrieţi programul pentru crearea unei liste simplu înlănţuite cu preluarea datelor de la tastatură. Sfîrşitul introducerii datelor este marcat standard. După creare, se va afişa conţinutul listei apoi se va elibera memoria ocupată.

2.2.2 Scrieţi funcţia pentru inserarea unui nod într-o listă simplu înlănţuită după un nod identificat prin valoarea unui cîmp. Dacă nodul căutat nu există, inserarea se face la sfîrşitul listei.

2.2.3 Scrieţi funcţia pentru inserarea unui nod într-o listă simplu înlănţuită înaintea unui nod identificat prin valoarea unui cîmp. Dacă nodul căutat nu există, se face inserare la începutul listei.

2.2.4. Scrieţi funcţia pentru ştergerea unei liste simplu înlănţuite. 2.2.5. Scrieţi funcţia pentru ştergerea nodului aflat înaintea nodului identificat prin valoarea unui

cîmp. Pentru următoarele probleme cu liste dublu înlănţuite se va folosi o listă avînd ca informaţie

utilă în nod un număr real: typedef struct TNOD{float x; struct TNOD *next,*pred;};

2.2.6. Scrieţi programul pentru crearea unei liste dublu înlănţuite cu preluarea datelor de la tastatură. Sfîrşitul introducerii datelor este marcat standard. După creare, se va afişa conţinutul listei apoi se va elibera memoria ocupată.

2.2.7. Scrieţi funcţia pentru inserarea unui nod la începutul unei liste dublu înlănţuite. 2.2.8 Scrieţi funcţia pentru inserarea unui nod într-o listă dublu înlănţuită, după un nod

identificat prin valoarea unui cîmp. 2.2.9. Scrieţi funcţia pentru ştergerea unui nod identificat prin valoarea unui cîmp dintr-o listă

dublu înlănţuită. 2.2.10 Scrieţi funcţia pentru ştergerea ultimului nod dintr-o listă dublu înlănţuită.

2.3. Liste circulare

În anumite situaţii este preferabilă renunţarea la structura de tip linear a listelor şi utilizarea unei

legături de la ultima componentă către capul listei, rezultând structura de listă circulară. Principalul avantaj al utilizării acestui tip de structură rezidă din posibilitatea de accesare dintr-

un element al listei al oricărui alt element, astfel. Dacă nodul căutat este situat după nodul curent, este iniţiat un proces de căutare similar listelor lineare. În caz contrar, nodul poate fi accesat prin parcurgerea listei de la primul sau element, care, în procesul de căutare, este atins după parcurgerea în întregime a listei, începând de la nodul curent.

În continuare sunt prezentate funcţiile C pentru realizarea unor operaţii de bază în lucrul cu liste circulare. #include<stdio.h> #include<conio.h> #include<alloc.h> typedef struct nod{ int inf; struct nod *leg; } list, *lista; int inserare_la_inceput(lista *,int); int stergere_la_inceput(lista *,int *); int inserare_la_sfarsit(lista *,int); int stergere_la_sfarsit(lista *,int *); void parc(lista); lista cauta(lista,int);

Page 33: 94650969 19 ID Program Area Calculatoarelor

* Material didactic pentru ID * 33

void main() { clrscr(); int n,info;lista cap=NULL; printf("Numarul de noduri:");scanf("%i",&n); printf("Introduceti informatiile\n"); for(int i=0;i<n;i++){ scanf("%i",&info); if(inserare_la_inceput(&cap,info)); else {printf("\n Spatiu insuficient \n"); return;} } printf("\nLista rezultata\n"); parc(cap); printf("\n\nLista dupa extragerea primului element:\n"); if(stergere_la_inceput(&cap,&info)) parc(cap); else printf("\nEroare: lista vida"); printf("\n\nInformatia nodului de introdus la sfarsit:"); scanf("%i",&info); if(inserare_la_sfarsit(&cap,info)){ printf("Lista rezultata\n"); parc(cap); } else printf("\n Spatiu insuficient \n"); printf("\n\nLista dupa extragerea ultimului element:"); if(stergere_la_sfarsit(&cap,&info)){ printf("\nInformatia extrasa %i\nLista rezultata:",info); parc(cap); } else printf("\nEroare:Lista vida"); getch(); } void parc(lista cap) { lista p=cap; if(cap){ printf("%i ",cap->inf); for(p=p->leg;p-cap;p=p->leg) printf("%i ",p->inf); } else printf("\nLista vida"); } lista cauta(lista cap,int info) { if(cap==NULL)return NULL; if(cap->inf==info) return cap; for(lista p=cap->leg;p!=cap;p=p->leg) if(p->inf==info) return p; return NULL;} int inserare_la_inceput(lista *cap,int info) {

Page 34: 94650969 19 ID Program Area Calculatoarelor

Programarea calculatoarelor 34

lista nou,ultim; if(nou=(lista)malloc(sizeof(list))){ nou->inf=info;nou->leg=*cap; if(*cap){ for(ultim=*cap;ultim->leg!=(*cap);ultim=ultim->leg); ultim->leg=nou; } else nou->leg=nou; *cap=nou;return 1; } return 0; } int stergere_la_inceput(lista *cap,int *info) { if(*cap){ lista aux=*cap;*info=aux->inf; for(lista ultim=*cap;ultim->leg!=(*cap);ultim=ultim->leg); if(ultim==(*cap)) *cap=NULL; else{ *cap=(*cap)->leg;ultim->leg=*cap;} free(aux); return 1; } return 0; } int inserare_la_sfarsit(lista *cap,int info) { lista nou,ultim; if(nou=(lista)malloc(sizeof(list))){ nou->leg=*cap;nou->inf=info; if(*cap==NULL){ *cap=nou;(*cap)->leg=*cap;} else{ for(ultim=*cap;ultim->leg!=(*cap); ultim=ultim->leg); ultim->leg=nou; } return 1; } return 0; } int stergere_la_sfarsit(lista *cap,int *info) { if (*cap){ if((*cap)->leg!=(*cap)){ for(lista pultim=*cap;pultim->leg->leg!=(*cap);pultim=pultim->leg); *info=pultim->leg->inf;free(pultim->leg); pultim->leg=(*cap);} else{ *info=(*cap)->inf;free(*cap);*cap=NULL;} return 1; } return 0; }

Page 35: 94650969 19 ID Program Area Calculatoarelor

* Material didactic pentru ID * 35

Teste de autoevaluare. Probleme 2.3.1 Scrieţi funcţia pentru inserarea unui nod într-o listă circulară simplu înlănţuită după un nod identificat prin valoarea unui cîmp. 2.3.2 Scrieţi funcţia pentru inserarea unui nod într-o listă circulară simplu înlănţuită înaintea unui nod identificat prin valoarea unui cîmp. 2.3.3. Scrieţi funcţia pentru ştergerea unei liste circulare simplu înlănţuite. 2.3.4 Scrieţi funcţia pentru ştergerea nodului aflat după un nod identificat prin valoarea unui cîmp. Dacă nodul căutat este ultimul, atunci se şterge primul nod al listei. 2.3.5. Scrieţi funcţia pentru ştergerea nodului aflat înaintea nodului identificat prin valoarea unui cîmp. Dacă nodul căutat este primul, se va şterge ultimul nod al listei.

2.4. Stive şi cozi

Aşa cum a rezultat din secţiunile precedente, operaţiile de inserare şi eliminare sunt permise la oricare dintre componentele colecţiei. O serie de aplicaţii pot fi modelate utilizând liste lineare în care introducerea şi respectiv eliminarea informaţiilor este permisă numai la capete. În acest scop au fost introduse tipurile de listă stivă şi coadă prin impunerea unui tip de organizare a aplicării operaţiilor de inserare şi eliminare.

Stiva Se numeşte stivă o listă organizată astfel încât operaţiile de inserare şi eliminare sunt permise

numai la prima componentă. Acest mod de organizare corespunde unei gestiuni LIFO (Last In First Out) a informaţiei stocate.

Operaţiile de bază efectuate asupra unei stive pot fi realizate similar cazului listelor dinamice lineare, ţinând cont că inserarea/extragerea unui element sunt posibile numai în prima poziţie (vezi modulul de inserare la începutul unei liste şi respectiv funcţia de extragere a primului nod dintr-o listă, prezentate în §2.2).

Coada Se numeşte coadă o listă organizată astfel încât operaţia de inserare este permisă la ultima

componentă, iar operaţia de eliminare este permisă numai la prima componentă. Acest mod de organizare corespunde unei gestiuni FIFO (First In First Out) a informaţiei stocate.

Implementarea unei liste coadă poate fi efectuată atât printr-o structură statică (masiv unidimensional), cât şi printr-o structură dinamică de tip listă. Pentru optimizarea operaţiilor de inserare/extragere, în cazul implementării cozilor prin structuri dinamice lineare, este necesară utilizarea a două informaţii: adresa primei componente şi adresa ultimei componente. Aceste informaţii pot fi menţinute explicit prin utilizarea a doi pointeri sau prin utilizarea unui pointer şi a unei structuri de listă circulară.

O variantă alternativă de implementare a unei liste de tip coadă dinamic este obţinută prin considerarea unei liste circulare, cu memorarea adresei ultimului element.

În continuare sunt prezentate operaţiile de inserare şi extragere a unei informaţii dintr-o listă liniară de tip coadă.

#include<stdio.h> #include<conio.h> #include<alloc.h>

typedef struct nod{ int inf; struct nod *leg; } list, *lista;

int inserare(lista *,lista *,int); int extragere(lista *,lista *,int *); void parc(lista);

Page 36: 94650969 19 ID Program Area Calculatoarelor

Programarea calculatoarelor 36

void main() { clrscr(); int n,info; lista cap=NULL,ultim=NULL; printf("Numarul de noduri:");scanf("%i",&n); printf("Introduceti informatiile\n"); for(int i=0;i<n;i++){ scanf("%i",&info); if(inserare(&cap,&ultim,info)); else {printf("\n Spatiu insuficient \n");return;} } printf("\nCoada rezultata\n");parc(cap); printf("\n\nCoada dupa o extragere:\n"); if(extragere(&cap,&ultim,&info)) parc(cap); else printf("\nEroare: Coada vida"); getch(); }

void parc(lista cap) { if(cap){ printf("%i ",cap->inf);parc(cap->leg);} }

int extragere(lista *cap,lista *ultim,int *info) { if(*cap){ lista aux=*cap;*info=aux->inf; if((*ultim)==(*cap)) *cap=*ultim=NULL; else *cap=(*cap)->leg; free(aux);return 1;} return 0; }

int inserare(lista *cap,lista *ultim,int info) { lista nou; if(nou=(lista)malloc(sizeof(list))){ nou->inf=info;nou->leg=NULL; if(*cap==NULL) *cap=*ultim=nou; else{ (*ultim)->leg=nou;(*ultim)=nou;} return 1; } return 0; }

Teste de autoevaluare. Probleme În exerciţiile propuse se va folosi o stivă de elemente cu tip real. Pentru implementarea ei se va

folosi o listă simplu înlănţuită. Capul acestei liste, împreună cu capacitatea maximă a stivei şi numărul de poziţii ocupate constituie cîmpuri ale structurii STIVA:

typedef struct TNOD{float x; struct TNOD* next;}; typedef struct STIVA{TNOD* vf; capacitate c,max;};

Page 37: 94650969 19 ID Program Area Calculatoarelor

* Material didactic pentru ID * 37

2.4.1.Scrieţi funcţia care verifică dacă o stivă este vidă. 2.4.2. Scrieţi funcţia care verifică dacă o stivă este plină. 2.4.3. Scrieţi funcţia pentru adăugarea unui element de tip real într-o stivă. 2.4.4. Scrieţi funcţia pentru extragerea unui element dintr-o stivă. 2.4.5. Să se scrie funcţia pentru golirea unei stive.

2.5 Răspunsuri la testele de autoevaluare. Comentarii 2.1.1 typedef struct nod{ float inf; struct nod *leg; } list, *lista; 2.1.2 În cazul static, lista este reprezentată prin intermediul unui vector. Presupunem în continuare că n<100. char lista l[100]; int n; 2.2.1 #include<stdio.h> #include<alloc.h> typedef struct TNOD{float x; struct TNOD* next;}; void main() {TNOD *cap,*p,*q; int n,i; float a; //creare lista printf("primul nod="); scanf("%f",&a); cap=(TNOD*)malloc(sizeof(TNOD)); cap->x=a; cap->next=NULL; p=cap; printf("nod (CTRL-Z)="); scanf("%f",&a); while(!feof(stdin)) {q=(TNOD*)malloc(sizeof(TNOD)); q->x=a; q->next=NULL; p->next=q; p=q; printf("nod (CTRL-Z)="); scanf("%f",&a);} //afisare continut printf("\n"); p=cap; while(p) {printf("\t%5.2f",p->x); p=p->next;} //stergere lista while(cap) {p=cap; cap=cap->next; free(p);} }

2.2.2 Funcţia are ca parametri capul listei în care se inserează, valoarea care se inserează şi valoarea după care se inserează. Prin numele funcţiei se întoarce noul cap al listei. TNOD* ins2(TNOD* cap, float a,float x) {TNOD *p,*q; p=NEW; p->x=a; if(!cap) {cap=p;p->next=NULL;} else{q=cap; while((q->next)&&(q->x!=x))

Page 38: 94650969 19 ID Program Area Calculatoarelor

Programarea calculatoarelor 38

q=q->next; p->next=q->next; q->next=p;} return cap;}

2.2.3 Funcţia are ca parametri capul listei în care se inserează, valoarea care se inserează şi valoarea înaintea cărei se inserează. Prin numele funcţiei se întoarce noul cap al listei. TNOD* ins3(TNOD* cap, float a,float x) {TNOD *p,*q; p=NEW; p->x=a; if(!cap) {cap=p;p->next=NULL;} else{q=cap; while((q->next)&&(q->next->x!=x)) q=q->next; if(!q->next){p->next=cap;cap=p;} else {p->next=q->next; q->next=p;} } return cap;}

2.2.4 Funcţia are ca parametru capul listei care trebuie ştearsă şi întoarce, prin numele ei, noul

cap al listei (valoarea NULL). TNOD* sterg(TNOD* cap) {TNOD* p; while(cap) {p=cap; cap=cap->next; free(p);} return NULL;}

2.2.5. Funcţia are ca parametri capul listei, valoarea din nodul căutat (înaintea căruia se face ştergerea) şi adresa unde se înscrie parametrul de eroare (0 dacă se face ştergerea, 1 dacă nodul căutat este primul, 2 dacă nodul căutat nu a fost găsit). Funcţia întoarce noul cap al listei. TNOD* sterg5(TNOD* cap,float a, int *er) {TNOD *p, *q; if(!cap) *er=2; else{p=cap; if(cap->x==a)*er=1; else if(cap->next) if(cap->next->x==a) {*er=0; p=cap; cap=cap->next; free(p);} else if(cap->next->next) {p=cap; while((p->next->next)&&(p->next->next->x!=a)) p=p->next; if(p->next->next) {q=p->next; p->next=p->next->next; free(q); * er=0;} else *er=2; } } return cap;}

2.2.6. #include<stdio.h> #include<alloc.h> #define NEW (TNOD*)malloc(sizeof(TNOD));

Page 39: 94650969 19 ID Program Area Calculatoarelor

* Material didactic pentru ID * 39

typedef struct TNOD{float x; struct TNOD *next,*pred;}; void main() {TNOD *cap, *p, *q; float x; //creare lista printf("\nprimul nod: ");scanf("%f",&x); cap=NEW; cap->pred=NULL; cap->next=NULL; cap->x=x; p=cap; printf("\nnod: ");scanf("%f",&x); while(!feof(stdin)) {q=NEW; q->next=NULL; q->x=x; q->pred=p; p->next=q; p=q; printf("\nnod: ");scanf("%f",&x);} //afisare lista printf("\n"); p=cap; while(p) {printf("\t%5.2f",p->x); p=p->next;} //stergere lista while(cap) {p=cap; cap=cap->next; if(cap)cap->pred=NULL; free(p);} printf("\ngata");}

2.2.7. Funcţia are ca parametri capul listei şi valoarea care trebuie inserată. Prin numele funcţiei

se întoarce noul cap al listei. TNOD* ins1(TNOD* cap, float a) {TNOD *p; p=NEW; p->x=a; p->pred=NULL; p->next=cap; if(cap)cap->pred=p; return p;}

2.2.8. Funcţia are ca parametri capul listei, valoarea care se inserează şi valoarea după care se inserează. Dacă valoarea căutată nu este găsită, se face inserare la sfîrşitul listei. TNOD* ins2(TNOD* cap, float a, float x) {TNOD *p,*q; p=NEW; p->x=a; if(!cap){cap=p;p->next=NULL;p->pred=NULL;} else{q=cap; while((q->next)&&(q->x!=x)) q=q->next; p->next=q->next; p->pred=q; q->next=p; if(p->next)p->next->pred=p;} return cap;}

2.2.9 Funcţia are ca parametri capul listei şi valoarea de identificare a nodului care trebuie şters. Prin numele funcţiei se întoarce noul cap al listei. TNOD* sterg2(TNOD* cap, float a) {TNOD *p, *q;

Page 40: 94650969 19 ID Program Area Calculatoarelor

Programarea calculatoarelor 40

if(cap) {p=cap; while((p->next)&&(p->x!=a)) p=p->next; if(p->x==a) {if(p->next)p->next->pred=p->pred; if(p->pred)p->pred->next=p->next; else cap=p->next; free(p);} } return cap;}

2.2.10 Funcţia are ca parametru capul listei şi întoarce, prin numele ei, noul cap al listei. TNOD* sterg3(TNOD* cap) {TNOD *p,*q; if(cap) {p=cap; while(p->next) p=p->next; if(!p->pred){free(cap);cap=NULL;} else {q=p->pred;free(p);q->next=NULL;} } return cap;}

2.3.1. Funcţia are ca parametri capul listei, valoarea care trebuie inserată şi valoarea după care se inserează. Dacă valoarea căutată nu este găsită, atunci inserarea se face la sfîrşit. TNOD* ins2(TNOD* cap, float a,float x) {TNOD *p,*q; p=NEW; p->x=a; if(!cap) {cap=p;p->next=cap;} else{q=cap; while((q->next!=cap)&&(q->x!=x)) q=q->next; p->next=q->next; q->next=p;} return cap;}

2.3.2. Funcţia are ca parametri capul listei, valoarea care se adaugă şi valoarea înaintea căreia se adaugă. Prin numele funcţiei se întoarce noul cap al listei. TNOD* ins3(TNOD* cap, float a,float x) {TNOD *p,*q; p=NEW; p->x=a; if(!cap) {cap=p;p->next=cap;} else{q=cap; while((q->next!=cap)&&(q->next->x!=x)) q=q->next; if(q->next==cap)cap=p; p->next=q->next; q->next=p;} return cap;}

2.3.3. Funcţia are ca parametru capul listei şi întoarce, prin numele ei, noul cap al listei (NULL).

TNOD* sterg(TNOD* cap) {TNOD *p,*q; if(cap) {p=cap; do {q=cap; cap=cap->next; free(q);}

Page 41: 94650969 19 ID Program Area Calculatoarelor

* Material didactic pentru ID * 41

while(cap!=p);} return NULL;}

2.3.4 Funcţia are ca parametri capul listei, valoarea căutată şi adresa unde va scrie parametrul de eroare (1 dacă valoarea căutată nu este găsită sau lista are numai un nod, 0 dacă se face ştergerea). TNOD* sterg4(TNOD* cap,float a,int *er) {TNOD *p, *q; if(!cap) *er=1; else if(cap==cap->next)*er=1; else{p=cap; if(cap->x!=a) {p=cap->next; while((p!=cap)&&(p->x!=a)) p=p->next;} if(p->x!=a)*er=1; else {if(p->next==cap)cap=sterg1(cap); else{q=p->next; p->next=q->next; free(q);} *er=0;} } return cap;}

2.3.5 Funcţia are ca parametri capul listei, valoarea căutată şi adresa unde se va înscrie parametrul de eroare (0 dacă ştergerea s-a efectuat, 1 dacă nodul căutat nu este găsit sau lista are un singur nod, 2 dacă lista este vidă). TNOD* sterg5(TNOD* cap,float a, int *er) {TNOD *p, *q; if(!cap) *er=2; else if(cap==cap->next)*er=1; else if(cap->x==a){*er=0; cap=sterg3(cap);} else if(cap->next->x==a){cap=sterg1(cap);*er=0;} else{p=cap->next; while((p->next->next!=cap)&&(p->next->next->x!=a)) p=p->next; if(p->next->next->x==a) {q=p->next; p->next=p->next->next; free(q); *er=0;} else *er=1;} return cap;}

2.4.1. Funcţia are ca parametru stiva şi întoarce valoarea 1 dacă stiva este vidă sau 0 în caz contrar. int e_vida(STIVA s) {return s.vf==NULL;}

2.4.2. Funcţia are ca parametru stiva şi întoarce valoarea 1 dacă stiva este plină sau 0 în caz contrar.

int e_plina(STIVA s) {return s.c==s.max;}

2.4.3. Funcţia are ca parametri adresa stivei şi valoarea care trebuie adăugată în stivă. Prin numele funcţiei se întoarce valoarea 1 dacă stiva era plină (şi nu se poate face adăugare) sau 0 dacă adăugarea a decurs normal.

Page 42: 94650969 19 ID Program Area Calculatoarelor

Programarea calculatoarelor 42

int push(STIVA *s, float a) {TNOD *p; int er; if(!e_plina(*s)) {p=NEW; p->x=a; p->next=s->vf; s->vf=p; (s->c)++; er=0;} else er=1; return er;}

2.4.4 Funcţia are ca parametri adresa stivei şi adresa unde se va depune valoarea extrasă din stivă. Prin numele funcţiei se întoarce valoarea 1, dacă stiva este vidă, sau 0 în caz contrar. int pop(STIVA *s, float *a) {int er; TNOD *p; if(e_vida(*s))er=1; else{p=s->vf; s->vf=s->vf->next; *a=p->x; free(p); er=0; (s->c)--;} return er;}

2.4.5. Funcţia are ca parametru adresa stivei şi nu întoarce nimic prin numele ei. Valorile aflate în stivă se pierd. void golire(STIVA *s) {TNOD *p; while(s->vf) {p=s->vf; s->vf=s->vf->next; free(p);} s->c=0;} Bibliografie 1. I. Gh. Roşca, B. Ghilic-Micu, C. Cocianu, M. Stoica, C. Uscatu, M. Mircea - Programarea

calculatoarelor. Algoritmi în programare, Ed. ASE Bucureşti, 2007 2. I. Gh. Roşca, B. Ghilic-Micu, C. Cocianu, M. Stoica, C. Uscatu - Programarea

calculatoarelor. Ştiinţa învăţării unui limbaj de programare. Teorie şi aplicaţii, Ed. ASE Bucureşti, 2003

3. Liviu Negrescu - Limbajele C şi C++ pentru începători, vol. I, II, Ed. Microinformatica, Cluj-Napoca, 1994

Page 43: 94650969 19 ID Program Area Calculatoarelor

* Material didactic pentru ID * 43

3. Structuri arborescente Obiective Principalele obiective vizează însuşirea şi aprofundarea de către studenţi a noţiunilor

fundamentale cu care se operează în studiul grafurilor arbore şi a arborilor binari de sortare, respectiv a arborilor binari de structură: definiţii, caracterizări, reprezentări şi parcurgeri ale grafurilor arbore, arbori parţiali de cost minim, arbori direcţionaţi, construcţia şi utilitatea arborilor de sortare, construcţia, modul de operare şi utilitatea arborilor de structură asociaţi expresiilor. Cuprins 3.1. Definiţii şi caracterizări ale grafurilor arbori ...............................................................43 3.2. Reprezentări şi parcurgeri ale arborilor orientaţi........................................................45 3.3. Arbori parţiali; algoritmul kruskal................................................................................48 3.4. Arbori binari. Arbori binari de sortare.........................................................................50 3.5. Arbori de structură..........................................................................................................54 3.6. Răspunsuri la testele de autoevaluare. Comentarii ......................................................59 Timpul mediu de învăţare pentru studiul individual: 8 ore.

3.1. Definiţii şi caracterizări ale grafurilor arbori

Structurile cele mai simple şi care apar cel mai frecvent în aplicaţii sunt cele arborescente (arbori). Grafurile arbori constituie o subclasă a grafurilor conexe.

Definiţia 3.1.1 Graful G este arbore dacă G este aciclic şi conex. Definiţia 3.1.2. Fie G=(V,E) graf arbore. Subgraful H=(V1,E1) al lui G este subarbore al lui G

dacă H este graf arbore. Exemple 3.1.1. Graful

1

2 3 4

este arbore, deoarece, orice (i,j) E∈ , i≠j, există un i-j drum şi graful nu conţine cicluri.

3.1.2. Graful 1 3

4 2 7

5

6

nu este arbore, deoarece drumul Γ :1,4,6,2,1 este un ciclu.

Verificarea proprietăţii unui graf de a fi arbore poate fi realizată prin intermediul unor algoritmi care să verifice calităţile de conexitate şi respectiv aciclicitate. De asemenea, verificarea proprietăţii unui graf de a fi arbore poate fi realizată astfel.

Page 44: 94650969 19 ID Program Area Calculatoarelor

Programarea calculatoarelor 44

Proprietatea 3.1.1. Un graf G=(V,E), cu mE,nV == este graf arbore dacă şi numai dacă G este aciclic şi n=m+1.

Exemple 3.1.3. Graful din 9.1.1 este arbore, pentru că este aciclic şi n=7, m=6. 3.1.4. Graful din 9.1.2. nu este arbore pentru că este ciclic. Proprietatea 3.1.2 Un graf G=(V,E), cu mE,nV == este graf arbore dacă şi numai dacă G

este conex şi n=m+1. Exemple 3.1.5. Graful din 3.1.1. este arbore deoarece este conex şi n=m+1. 3.1.6. Graful conex din exemplul 3.1.2. nu este arbore pentru că n=6 şi m=8. Observaţie Fie G=(V,E) un graf. Următoarele afirmaţii sunt echivalente, G este graf arbore; G este graf conex minimal: oricare ar fi e∈E, prin eliminarea muchiei e din E, graful rezultat nu

este conex; G este graf aciclic maximal: prin adăugarea unei noi muchii în graf rezultă cel puţin un ciclu. Definiţia 3.1.3. Se numeşte graf asimetric un digraf D=(V,E) cu proprietatea că pentru orice Ev,u ∈ dacă uv∈E, atunci vu∉E. Digraful D este simetric dacă ,Ev,u ∈∀ uv∈E, dacă şi numai

dacă vu∈E. Definiţia 3.1.4. Fie D=(V,E) digraf netrivial. Graful G=(V,E’), unde E’={uv/ uv∈E sau vu∈E} se

numeşte graf suport al digrafului D. Definiţia 3.1.5. Un arbore direcţionat este un graf orientat asimetric şi astfel încât graful suport

corespunzător lui este graf arbore. Definiţia 3.1.6. Arborele direcţionat T=(V,E) este arbore cu rădăcină dacă există r∈V astfel

încât, pentru orice u∈V, u≠ r, există r-u drum în T. Vârful r se numeşte rădăcina arborelui direcţionat T.

Definiţia 3.1.7. Fie T=(V,E) arbore direcţionat. Arborele T1=(V1,E1) este subarbore al lui T dacă V1⊆V, E1⊆E şi T1 este arbore direcţionat.

Observaţie Graful suport al unui arbore direcţionat este aciclic, deci, pentru orice u∈V, u≠ r, r-

u drumul din T este unic. De asemenea, un arbore direcţionat are cel mult o rădăcină. Rezultă că, pentru orice u∈V, u≠ r, distanţa de la rădăcină la vârful u este egală cu numărul de muchii ale r-u drumului în T.

Exemple 3.1.7. Arborele direcţionat

1 3 4 2 5 6 7 8 9 10

este arbore cu rădăcină 1.

Page 45: 94650969 19 ID Program Area Calculatoarelor

* Material didactic pentru ID * 45

3.1.8. Arborele

1 4 2 5 6 8 10

este un subarbore cu rădăcină 1 al arborelui din 3.1.7.

Teste de autoevaluare 3.1.1. Graful

1 3 7 4 2

•5 9 6 8

este graf arbore? Justificaţi răspunsul.

3.1.2. Utilizând proprietatea 3.1.1, verificaţi dacă garful de la întrebarea 3.1.1 este arbore. 3.1.3. Utilizând proprietatea 3.1.1, verificaţi dacă garful de la întrebarea 3.1.1 este arbore.

3.2. Reprezentări şi parcurgeri ale arborilor orientaţi

Definiţia 3.2.1 Un arbore orientat este un arbore direcţionat cu rădăcină. Definiţia 3.2.2. Fie T=(V,E) arbore orientat cu rădăcină r. Un vârf v V∈ este situat pe nivelul i al

arborelui T, dacă distanţa de la vârf la rădăcină este egală cu i. Rădăcina arborelui este considerată de nivel 0.

Deoarece orice arbore orientat este, în particular, digraf, reprezentarea arborilor orientaţi poate fi realizată prin utilizarea oricăreia dintre modalităţile prezentate în unitatea de instruire 1. Datorită caracteristicilor arborilor orientaţi pot fi însă obţinute reprezentări mai eficiente din punct de vedere al spaţiului de memorie solicitat.

Una dintre modalităţi este reprezentarea de tip FIU-FRATE, care constă în numerotarea convenţională a vârfurilor grafului şi memorarea, pentru fiecare vârf i al arborelui, a următoarelor informaţii,

• FIU(i): numărul ataşat primului descendent al vârfului i; • FRATE(i): numărul ataşat vârfului descendent al tatălui vârfului i şi care urmează

imediat lui i; • INF(i): informaţia ataşată vârfului i (de obicei valoarea i).

Page 46: 94650969 19 ID Program Area Calculatoarelor

Programarea calculatoarelor 46

Pentru reprezentarea arborelui sunt reţinute rădăcina şi numărul nodurilor. Absenţa “fiului”, respectiv a “fratelui” unui vârf este marcată printr-o valoare din afara mulţimii de numere ataşate vârfurilor (de obicei valoarea 0).

Exemplu 3.2.1. Arborele orientat

1

2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

este reprezentat astfel, N=16, R=1 (rădăcina), FIU=(2,5,0,8,0,9,0,14,0,0,0,0,0,0,0,0) FRATE=(0,3,4,0,6,7,0,0,10,11,12,13,0,15,16,0)

O alternativă a reprezentării FIU-FRATE poate fi obţinută prin utilizarea structurile de date dinamice, astfel. Presupunând că fiecare vârf al arborelui are cel mult n descendenţi, fiecărui vârf îi este ataşată structura,

vector de legături către descendenţii vârfului identificator

vârf adresă fiu 1 ………… adresă fiu n

Parcurgerea unui arbore orientat revine la aplicarea sistematică a unei reguli de vizitare a vârfurilor arborelui. Cele mai utilizate reguli de parcurgere a arborilor orientaţi sunt A-preordine, A-postordine şi parcurgerea pe nivele.

Parcurgerea în A-preordine Modalitatea de vizitare a vârfurilor în parcurgerea în A-preordine poate fi descrisă astfel. Iniţial,

rădăcina arborelui este selectată drept vârf curent. Este vizitat vârful curent şi sunt identificaţi descendenţii lui. Se aplică aceeaşi regulă de vizitare pentru arborii având ca rădăcini descendenţii vârfului curent, arborii fiind vizitaţi în ordinea precizată prin numerele ataşate vârfurilor rădăcină corespunzătoare.

În reprezentarea FIU-FRATE, implementarea parcurgerii în A-preordine este realizată prin următoarea funcţie recursivă, cu parametru de intrare rădăcina arborelui curent.

void A_preordine (nod R) { if (R){ vizit (R); A_preordine(FIU[R]); A_preordine(FRATE[R]); } }

Page 47: 94650969 19 ID Program Area Calculatoarelor

* Material didactic pentru ID * 47

Parcurgerea A-postordine Regula de parcurgerea în A-postordine este asemănătoare traversării A-preordine, singura

diferenţă fiind aceea că, în acest tip de traversare, rădăcina fiecărui arbore este vizitată după ce au fost vizitate toate celelalte vârfuri ale arborelui.

Exemplu 3.2.2. Pentru arbori reprezentaţi prin structuri dinamice de date, implementarea parcurgerii în A-

postordine poate fi obţinută pe baza următoarei funcţii recursive. Parametrul de intrare al funcţiei A_postordine reprezintă rădăcina arborelui curent în momentul apelului.

void A_postordine (nod R) { if (R) { for(i=0;i<n;i++) A_postordine(R->leg[i]); vizit (R); } }

Observaţie Parcurgerile în A-preordine şi A-postordine sunt variante de parcurgeri în adâncime (variante ale metodei DF). Ambele metode consideră prioritare vârfurile aflate la distanţă maximă faţă de rădăcina arborelui iniţial.

Parcurgerea pe niveluri Parcurgerea unui arbore orientat pe nivele constă în vizitarea vârfurilor sale în ordinea

crescătoare a distanţelor faţă de rădăcină. Ca şi în cazul metodei BF, implementarea parcurgerii pe nivele este bazată pe utilizarea unei structuri de coadă C. La momentul iniţial rădăcina arborelui este inserată în C. Atâta timp cât timp coada este nevidă, este preluat cu ştergere un vârf din C, este vizitat şi sunt introduşi în coadă descendenţii săi. Calculul este încheiat când C=Ø.

Exemplu 3.2.3. Pentru arborele de la exemplul 3.2.1, evoluţia algoritmului este,

C T

t=1 1 t=2 2 3 4 t=3 3 4 5 6 7 t=4 4 5 6 7 t=5 5 6 7 8 t=6 6 7 8 t=7 7 8 9 10 11 12 13 t=8 8 9 10 11 12 13 t=9 9 10 11 12 13 14 15 16

t=10 10 11 12 13 14 15 16 t=11 11 12 13 14 15 16 t=12 12 13 14 15 16 t=13 13 14 15 16 t=14 14 15 16 t=15 15 16 t=16 15 t=17

deci vârfurile sunt vizitate în ordinea, 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16.

Page 48: 94650969 19 ID Program Area Calculatoarelor

Programarea calculatoarelor 48

Observaţie Metoda BF pentru parcurgerea grafurilor este o generalizare a tehnicii de parcurgere pe nivele a arborilor orientaţi.

O alternativă de implementare a parcurgerii pe nivele poate fi descrisă prin intermediul funcţiilor recursive fraţi şi parc. Coada C este o variabilă globală şi este iniţializată cu rădăcina arborelui. Parcurgerea este obţinută prin apelul parc(C).

void fraţi(v) { if (v){ push(C,v); fraţi(FRATE[v]); } } void parc() { if (C){ pop(C,v);VIZIT(v); fraţi(FIU[v]); parc(); } }

Teste de autoevaluare. Probleme 3.2.1. Scrieţi o sursă C pentru construcţia unui arbore orientat, reprezentat prin intermediul unei

structuri dinamice arborescente. Numărul maxim de descendenţi ai unui nod este 4. 3.2.2. Care este ordinea de vizitare a nodurilor arborelui din exemplul 3.2.1 în parcurgerea A-

preordine? 3.2.3. Care este ordinea de vizitare a nodurilor arborelui din exemplul 3.2.1 în parcurgerea A-

postordine? 3.2.4. Care este ordinea de vizitare a nodurilor arborelui din exemplul 3.2.1 în parcurgerea pe

niveluri? 3.2.5. Scrieţi o funcţie C pentru implementarea parcurgerii pe niveluri în cazul reprezentării

FIU-FRATE a arborelui de traversat.

3.3. Arbori parţiali; algoritmul Kruskal

Definiţia 3.3.1. Fie G un graf. Subgraful parţial H este un arbore parţial al lui G dacă H este graf arbore.

Definiţia 3.3.2. Fie G=(V,E,w) un graf ponderat conex.Dacă T=(V,E0) este un arbore parţial al grafului G’=(V,E), ponderea arborelui T, notată W(T), este definită prin W(T)=∑

∈ 0Ee)e(w .

Exemplu 3.3.1. Pentru graful ponderat

1 4 2 3 8 2 6 5 2 8 1 9 12 4 3

Page 49: 94650969 19 ID Program Area Calculatoarelor

* Material didactic pentru ID * 49

T este un arbore parţial de pondere 32. T: 1

4 2 3 2 6 5 8 9 4 3

Definiţia 3.3.4. Arborele parţial T0∈T(G) este arbore parţial minim pentru G dacă W(T0)=min{W(T); T∈T(G)}, unde T(G) este mulţimea arborilor parţiali corespunzători grafului G.

Observaţie Dacă G este graf finit, atunci T(G) este mulţime finită, deci orice graf finit ponderat

şi conex are cel puţin un arbore parţial minim. În continuare este prezentat algoritmul Kruskal, pentru determinarea unui arbore parţial minim

al unui graf ponderat conex G=(V,E,w). Pas 1: i=1; E0=∅ Pas 2: Determină R={e/e∈E \ Ei-1 astfel încât graful (V,Ei-1∪ {e}) este aciclic}

Dacă R=∅, atunci stop; altfel, selectează ei∈R cu w(ei)=min{w(e), e∈R}; Ei=Ei-1∪ {ei} Pas 3: i=i+1 şi reia pasul 2. Arborele parţial de cost minim al grafului G este (V,Ei-1).

Pentru implementarea algoritmului Kruskal, graful conex ponderat este reprezentat sub formă

tabelară, muchiile fiind ordonate crescător după ponderi. Muchiile selectate de algoritm pot fi menţinute de asemenea într-o structură tabelară, sau doar marcate ca fiind incluse în mulţimea de muchii din arborele parţial minim a cărui construcţie este dorită.În varianta prezentată în continuare muchiile selectate sunt afişate.

Verificarea condiţiei ca muchia selectată să nu formeze nici un ciclu cu muchiile selectate la etapele precedente este realizată prin utilizarea un vector TATA, definit astfel. Pentru fiecare vârf i (vârfurile grafului fiind numerotate de la 1 la n, unde n este numărul de noduri ale grafului), componenta TATA [i] este predecesorul său în arborele care conţine vârful i construit până la momentul curent dacă i nu este rădăcina acelui arbore, respectiv TATA[i] este egal cu –numărul de vârfuri ale arborelui de rădăcină i, în caz contrar. Componentele vectorului TATA sunt iniţializate cu valoarea -1.

Calculul care realizează adăugarea unei noi muchii poate fi descris astfel.Este determinată o muchie de cost minim e=v1v2 care nu a fost selectată anterior. Dacă vârfurile v1 şi v2 nu aparţin aceluiaşi arbore, atunci proprietatea de aciclicitate este îndeplinită şi muchia e este adăugată la structura curentă. Adăugarea muchiei e selectate este realizată prin reunirea arborilor din care fac parte v1 şi v2 de rădăcini r1, respectiv r2, astfel: dacă TATA[r1]<TATA[r2], atunci arborele rezultat prin reunirea celor doi arbori are ca rădăcină vârful r1, iar vârful r2 devine fiu al lui r1. Altfel, rădăcina arborelui rezultat prin reunire fiind r2, iar r1 devenind fiu al rădăcinii.

Calculul se încheie după ce a fost adăugată şi cea de-a (n-1)-a muchie. Algoritmul Kruskall poate fi implementat prin următoarea sursă C,

#include<stdio.h> #include<conio.h> int radacina(int v,int *tata) { int u=v; while(tata[u]>=0) u=tata[u]; return u; }

Page 50: 94650969 19 ID Program Area Calculatoarelor

Programarea calculatoarelor 50

int kruskal(int a[][3],int nm, int nv) { int tata[50],i,j;int c=0; for(i=0;i<nv;i++)tata[i]=-1; for(j=i=0;i<nv-1;j++){ int v1=a[j][0]; int v2=a[j][1]; int k=radacina(v2,tata);int p=radacina(v1,tata); if(k-p){ if(tata[k]<tata[p]){tata[k]+=tata[p];tata[p]=k;} else{tata[p]+=tata[k];tata[k]=p;} c+=a[j][2];printf("%i -> %i cost %i\n",v1+1,v2+1,a[j][2]);i++;} } return c; } void main() { clrscr();int nv,nm, a[100][3]; printf("Numarul de varfuri:");scanf("%i",&nv); printf("Numarul de muchii");scanf("%i",&nm); printf("Matricea de reprezentare\n"); for(int i=0;i<nm;i++) for(int j=0;j<3;j++)scanf("%i",&a[i][j]); for(i=0;i<nm;i++) for(int j=0;j<2;j++)a[i][j]--; printf("Arborele de cost minim: \n"); int cost=kruskal(a,nm,nv);printf("\ncu costul%i",cost);getch(); }

Test de autoevaluare 3.3.1. Fie graful

1 4 2 3 8 2 6 5 2 8 1 9 12 4 3

Care este evoluţia determinată de aplicarea algoritmului Kruskall?

3.4. Arbori binari. Arbori binari de sortare

Definiţia 3.4.1. Un arbore binar este un arbore orientat cu proprietatea că pentru orice vârf v, od(v)≤2. Dacă od(v)=2, cei doi descendenţi sunt desemnaţi ca descendent stâng (fiu stânga) respectiv descendent drept (fiu dreapta). Pentru vârfurile cu od(v)=1, unicul descendent este specificat fie ca fiu stânga, fie ca fiu dreapta.

Definiţia 3.4.2. Se numeşte nod terminal orice vârf v al arborelui cu od(v)=0. În caz contrar nodul v este neterminal.

Reprezentarea unui arbore binar este realizată printr-o structură arborescentă. Pentru fiecare nod N al arborelui binar, sunt memorate informaţia asociată lui N şi legăturile către descendenţii lui. Absenţa unui descendent este reprezentată prin NULL.

Page 51: 94650969 19 ID Program Area Calculatoarelor

* Material didactic pentru ID * 51

identificator nod legătură fiu stâng legătură fiu drept Definiţia 3.4.3. Fie T=(V,E) un arbore binar cu rădăcina R. Subarborele stâng al lui T este

ST=(V\{R},E\{RS}), unde S este fiul stânga al rădăcinii. Subarborele drept al lui T este DT=(V\{R},E\{RD}), unde D este fiul dreapta al rădăcinii.

Exemplu 3.4.1. Pentru arborele binar,

În plus faţă de metodele A-preordine, A-postordine şi pe nivele, parcurgerile în preordine(RSD),

inordine(SRD) şi respectiv postordine(SDR) sunt special considerate pentru arbori binari şi au multiple aplicaţii. Regula de vizitare pentru aceste tipuri de parcurgere revine la parcurgerea subarborelui stâng şi parcurgerea subarborelui drept corespunzători vârfului curent, la momentul iniţial vârf curent fiind rădăcina arborelui. Diferenţa între aceste trei tipuri de parcurgere este dată de momentul în care devine vizitat fiecare vârf al arborelui. În parcurgerea RSD (rădăcină-subarbore stâng-subarbore drept), fiecare vârf al arborelui este vizitat în momentul în care devine vârf curent; în parcurgerea SRD, vizitarea vârfului curent R este efectuată după ce a fost parcurs subarborele stâng al lui R, respectiv în parcurgerea SDR vizitarea fiecărui vârf este efectuată după ce au fost parcurşi subarborii aferenţi lui.

Exemplu 3.4.2. Pentru arborele de la exemplul 3.4.1., secvenţele de vârfuri rezultate prin aplicarea

parcurgerilor RSD, SRD, SDR sunt: - preordine: 1,2,4,8,5,3,6,9,10,7 - inordine: 4,8,2,5,1,9,6,10,3,7 - postordine: 8,4,5,2,9,10,6,7,3,1.

1 2 3 4 5 6 7

8 9 10 2 3 4 5 6 7 8 9 10 subarbore stâng subarbore drept

Page 52: 94650969 19 ID Program Area Calculatoarelor

Programarea calculatoarelor 52

Definiţia 3.4.4. Un arbore de sortare este un arbore binar cu următoarele proprietăţi, - fiecărui nod i al arborelui îi este ataşată o informaţie INF(i) dintr-o mulţime ordonată de

valori; - pentru fiecare nod i, INF(i) este mai mare decât INF(j), pentru toate nodurile j din subarborele

stâng al arborelui cu rădăcină i; - pentru fiecare nod i, INF(i) este mai mică decât INF(j), pentru toate nodurile j din subarborele

drept al arborelui cu rădăcină i; - pentru orice vârfuri i şi j daca i≠j atunci INF(i)≠INF(j). Exemplu 3.4.3. Arborele binar

este arbore de sortare.

Operaţiile primitive asupra arborilor de sortare sunt inserarea unui nou nod, ştergerea unui nod şi parcurgerea arborelui (în preordine, inordine sau postordine). Inserarea şi ştergerea de noduri aplicate unui arbore de sortare trebuie realizate astfel încât arborele rezultat să fie de asemenea arbore de sortare.

Observaţie Parcurgerea în inordine a unui arbore de sortare determină obţinerea secvenţei informaţiilor asociate vârfurilor arborelui în ordine crescătoare.

Inserarea unui nod într-un arbore de sortare Algoritmul de inserare a unei informaţii nr în arborele de sortare de rădăcină rad este recursiv şi

constă în efectuarea următoarelor operaţii. Vârful curent v la momentul iniţial este rădăcina arborelui. - 1. dacă arborele de rădăcină v este vid, este generat arborele cu un singur nod, cu informaţia ataşată nr; - 2. altfel

- dacă informaţia ataşată nodului v este mai mare decât nr, atunci vârf curent devine fiul stânga al lui v; - dacă informaţia ataşată nodului v este egală cu nr, atunci stop; - dacă informaţia ataşată nodului v este mai mică decât nr, atunci vârf curent devine fiul dreapta al lui v.

Exemplu 3.4.4. Aplicarea algoritmul descris pentru inserarea informaţiei 55 în arborele de sortare din

exemplul 3.4.3. determină următoarele operaţii, INF(v)=50; 50<55, inserează în subarborele cu rădăcina având informaţia ataşată 70 INF(v)=70; 70>55, inserează în subarborele stâng cu rădăcina NULL. Este creat nodul cu informaţie 55, fiu stâng al nodului de informaţie 70.

50

30 70

10 40 90 20 80

Page 53: 94650969 19 ID Program Area Calculatoarelor

* Material didactic pentru ID * 53

Arborele rezultat este

Ştergerea unei informaţii dintr-un arbore de sortare Algoritmul pentru ştergerea unei informaţii nr din arborele de sortare de rădăcină rad este

recursiv şi poate fi descris astfel. Vârful curent v la momentul iniţial este rădăcina arborelui. - 1. dacă arborele este vid atunci stop; - 2. altfel

- a) dacă informaţia ataşată nodului v este mai mare decât nr, atunci vârf curent devine fiul stânga al lui v ; - b) dacă informaţia ataşată nodului v este mai mică decât nr, vârf curent devine fiul dreapta al lui v ; - c) dacă INF(v)=nr atunci:

- c1) dacă subarborele stâng este vid, atunci adresa vârfului v este memorată într-o celulă suplimentară aux, v devine fiul dreapta al lui v, iar celula aux este eliberată din memorie; - c2)dacă subarborele stâng este nevid atunci se determină cel mai mare element din subarborele stâng; c2.1.) dacă fiul stânga al lui v nu are subarbore drept, atunci informaţia ataşată fiului stânga se transferă în vârful curent, iar fiul stânga este înlocuit cu fiul său stânga şi este eliberată memoria corespunzătoare celulei v->fius; c2.2) altfel, se transferă în rădăcină informaţia ataşată ultimului nod p determinat la c2), nodul p este înlocuit cu fiul său stâng şi celula corespunzătoare lui p este eliberată din memorie.

Exemplu 3.4.5. Ştergerea informaţiei 70 în arborele de sortare din exemplul 3.4.4. este realizată astfel: 70>50, decide ştergerea din subarborele drept 70=70, decide ştergerea din arborele curent: rădăcina etichetată cu 70; există subarbore stâng iar

acesta nu are subarbore drept- nodul cu informaţie 70 este etichetat cu 55, iar p este înlocuit cu subarborele său stâng (vid). Arborele rezultat

50 30 70

10 40 55 90 20 80

Page 54: 94650969 19 ID Program Area Calculatoarelor

Programarea calculatoarelor 54

50 30 55

10 40 90 20 80

este arbore de sortare.

Observaţie Punctul c) de la pasul 2 al algoritmului de eliminare a unei informaţii dintr-un arbore de sortare

poate fi înlocuit cu. c) dacă INF(v)=nr atunci, c1) dacă subarborele drept este vid, atunci adresa vârfului v este memorată într-o celulă suplimentară aux, v devine fiul stânga al lui v, iar celula aux este eliberată din memorie;

c2) dacă subarborele drept este nevid atunci se determină cel mai mic element din subarborele drept

c2.1.) dacă fiul dreapta al lui v nu are subarbore stâng, atunci informaţia ataşată fiului dreapta este transferată în vârful curent, iar fiul dreapta este înlocuit cu fiul său dreapta şi este eliberată memoria corespunzătoare celulei v->fiud. c2.2) altfel, se transferă în rădăcină informaţia ataşată ultimului nod p determinat la c2), nodul p este înlocuit cu fiul său dreapta şi celula corespunzătoare lui p este eliberată din memorie.

Teste de autoevaluare. Probleme 3.4.1. Scrieţi o funcţie C pentru implementarea operaţiei parcurgere SRD a unui arbore de

sortare. 3.4.2. Scrieţi o funcţie C pentru implementarea operaţiei de adăugare a unei informaţii într-un

arbore de sortare. 3.4.3. Scrieţi o funcţie C pentru implementarea operaţiei de extragere a unei informaţii dintr-un

arbore de sortare. 3.4.4. Scrieţi o funcţie C pentru implementarea operaţiei parcurgere RSD a unui arbore binare.

3.5. Arbori de structură

Expresiile aritmetice în care intervin numai operatori binari pot fi reprezentate prin intermediul arborilor binari în care fiecare nod neterminal are doi fii.

Definiţia 3.5.1. Un arbore de structură are vârfurile etichetate astfel: - fiecare nod neterminal este etichetat cu un simbol corespunzător unuia dintre

operatori; - fiecare nod terminal este etichetat cu un operand.

Construcţia arborelui de structură corespunzător unei expresii aritmetice date se realizează pe baza parantezării existente în expresie şi a priorităţilor convenţional asociate operatorilor (ordinea operaţiilor) astfel încât rădăcina fiecărui subarbore este etichetată cu operatorul care se execută ultimul în evaluarea subexpresiei corespunzătoare acelui subarbore.

Page 55: 94650969 19 ID Program Area Calculatoarelor

* Material didactic pentru ID * 55

Exemplu 4.5.1. Pentru expresia matematică (a+b)*(c-d)+e/g, arborele de structură corespunzător este,

+

* /

+ - e g a b c d

Construcţia arborelui de structură pentru o expresie s este realizată în două etape, şi anume, 1. ataşarea de priorităţi operatorilor şi operanzilor. Priorităţile ataşate permit eliminarea

parantezelor fără ca semnificaţia expresiei să se modifice; 2. construcţia propriu-zisă.

Prima etapă este realizată astfel, - prioritatea iniţială a operatorilor ‘+’,’-‘ este 1 (dacă expresia nu conţine paranteze atunci

în construcţie aceşti operatori vor fi primii luaţi în considerare în ordinea de la dreapta la stânga);

- prioritatea iniţială a operatorilor ‘/’,’*‘ este 10 (dacă expresia nu conţine paranteze, aceştia sunt consideraţi după operatorii de prioritate 1 în ordinea de la dreapta la stânga);

- prioritatea fiecărui operator este incrementată cu valoarea 10 pentru fiecare pereche de paranteze în interiorul cărora se află;

- prioritatea ataşată fiecărui operand este MAXINT.

După stabilirea sistemului de priorităţi, sunt eliminate parantezele din expresie, ordinea de efectuare a operaţiilor în cadrul expresiei fiind indicată de vectorul de priorităţi ataşat.

Construcţia arborelui de structură pe baza expresiei s din care au fost eliminate parantezele şi a

vectorului de priorităţi, poate fi realizează recursiv în modul următor. La momentul iniţial expresia curentă este expresia dată.

- pentru expresia curentă se determină operatorul/operandul de prioritate minimă care se ataşează ca etichetă a rădăcinii r a subarborelui de structură corespunzător ei; fie i poziţia acestuia în cadrul expresiei;

- dacă expresia are un singur simbol atunci r->fius=r->fiud=NULL; - altfel, se consideră subexpresiile s1 şi s2, constând din simbolurile de pe poziţiile 0 până

la i-1 şi respectiv i+1 până la lungimea şirului s. Arborii de structură corespunzători subexpresiilor s1 şi s2 se ataşează ca subarbore stâng, respectiv subarbore drept vârfului r.

Page 56: 94650969 19 ID Program Area Calculatoarelor

Programarea calculatoarelor 56

Exemplu 3.5.2. Etapele calculului sistemului de priorităţi şi al arborelui de structură pentru expresia de la

exemplul 3.5.1. pot fi descrise astfel,

dim vectorul prioritate 1 (MAXINT) 2 (MAXINT,11) 3 (MAXINT,11,MAXINT) 3 (MAXINT,11,MAXINT) 4 (MAXINT,11,MAXINT,10) 4 (MAXINT,11,MAXINT,10) 5 (MAXINT,11,MAXINT,10,MAXINT) 6 (MAXINT,11,MAXINT,10,MAXINT,11) 7 (MAXINT,11,MAXINT,10,MAXINT,11,MAXINT) 7 (MAXINT,11,MAXINT,10,MAXINT,11,MAXINT) 8 (MAXINT,11,MAXINT,10,MAXINT,11,MAXINT,1) 9 (MAXINT,11,MAXINT,10,MAXINT,11,MAXINT,1,MAXINT)

10 (MAXINT,11,MAXINT,10,MAXINT,11,MAXINT,1,MAXINT,10) 11 (MAXINT,11,MAXINT,10,MAXINT,11,MAXINT,1,MAXINT,10,MAXINT)

După eliminarea parantezelor, expresia rezultată este s=a+b*c-d+e/g. Arborele de structură este,

+ * / + - e g a b c d

Observaţie Construcţia arborelui de structură poate fi realizată în ipoteza în care expresia este

corectă. Definiţia 3.5.2. Se numeşte forma poloneză directă a unei expresii, expresia rezultată în urma

parcurgerii RSD a arborelui de structură . Se numeşte forma poloneză inversă a unei expresii, expresia rezultată în urma parcurgerii SDR a arborelui de structură

Exemplu 3.5.3. Pentru expresia considerată la exemplul 3.5.1., forma poloneză directă este +*+ab-cd/eg.

Forma poloneză inversă a expresiei date este ab+cd-*eg/+. Observaţie Parcurgerea arborelui în inordine determină secvenţa de simboluri rezultată prin

eliminarea parantezelor din expresia dată. Restaurarea unei forme parantezate poate fi realizată printr-o parcurgere SRD şi anume în modul următor. La momentul iniţial vârf curent este rădăcina arborelui de structură. Dacă vârful curent v nu este vârf terminal atunci se generează (s1) eticheta(v)(s2), unde eticheta(v) este operatorul etichetă a vârfului, s1 este secvenţa rezultată prin traversarea SRD a subarborelui stâng, s2 este secvenţa rezultată prin traversarea SRD a subarborelui drept. Dacă v este vârf terminal atunci este generată secvenţa eticheta(v).

Page 57: 94650969 19 ID Program Area Calculatoarelor

* Material didactic pentru ID * 57

Evaluarea expresiilor aritmetice pe baza arborilor de structură Traversarea SRD a arborelui de structură ataşat unei expresii aritmetice permite evaluarea

expresiei pentru valorile curente corespunzătoare variabilelor. Evaluarea poate fi efectuată în mod recursiv astfel. La momentul iniţial vârf curent este rădăcina arborelui. Dacă v este vârf curent atunci noua informaţie asociată lui v este:

- val(eticheta(v)), dacă v este vârf terminal; - val(s1)eticheta(v)val(s2), dacă v este neterminal, unde val(s1), val(s2) sunt valorile rezultate prin evaluările subarborilor stâng şi respectiv drept ai lui v, val(eticheta(v)) este valoarea curentă a variabilei, dacă eticheta lui v este variabilă, respectiv valoarea constantei, dacă eticheta lui v este o constantă.

Exemplu 3.5.4. Prin aplicarea metodei de evaluare descrise pentru a=3, b=2, c=5, d=2, e=6 şi

g=2, obţinem

18 15 3 5 3 6 2 3 2 5 2

Construcţia arborelui de structură asociat unei expresii şi evaluarea expresiei pentru valori date

ale operanzilor pot fi implementate prin intermediul următoarei surse C. #include<stdio.h> #include<conio.h> #include<alloc.h> #include<values.h> #include<string.h> #include<math.h> typedef struct nod{ char inf; float v; struct nod *l,*r; } arb, *arbore; void prioritati(char *s, int *prioritate) { int i,j,dim; //stabilirea prioritatilor for(i=j=dim=0;i<strlen(s);i++) switch(s[i]){ case ')':j-=10;break; case '(':j+=10;break; case '+':{prioritate[dim]=j+1;dim++;break;} case '-':{prioritate[dim]=j+1;dim++;break;} case '*':{prioritate[dim]=j+10;dim++;break;}

Page 58: 94650969 19 ID Program Area Calculatoarelor

Programarea calculatoarelor 58

case '/':{prioritate[dim]=j+10;dim++;break;} default:{prioritate[dim]=MAXINT;dim++;break;} } //eliminarea parantezelor for(i=0;i<strlen(s);) if((s[i]==')')||(s[i]=='(')){ for(j=i+1;j<strlen(s);j++)s[j-1]=s[j]; s[strlen(s)-1]='\0';} else i++; } void cr_arb_str(arbore *rad, unsigned p, unsigned u, char *s,int *pri) { int min=pri[p]; int poz=p; for(int i=p+1;i<=u;i++) if(min>=pri[i]){min=pri[i];poz=i;} (*rad)=(arbore)malloc(sizeof(arb)); (*rad)->inf=s[poz]; if(p==u) (*rad)->l=(*rad)->r=NULL; else{ cr_arb_str(&((*rad)->l),p,poz-1,s,pri); cr_arb_str(&((*rad)->r),poz+1,u,s,pri); } } void forma_poloneza(arbore rad) { if(rad){ printf("%c",rad->inf); forma_poloneza(rad->l); forma_poloneza(rad->r); } } float eval(arbore rad) { char s[1]; if(rad){ if((rad->r==rad->l)&&(rad->l==NULL))return rad->v; else{ switch (rad->inf){ case '+':rad->v=eval(rad->l)+eval(rad->r);break; case '-':rad->v=eval(rad->l)-eval(rad->r);break; case '*':rad->v=eval(rad->l)*eval(rad->r);break; case '/':rad->v=eval(rad->l)/eval(rad->r);break; } return rad->v; }}} void atribuie_arbore(arbore rad) { if(rad){ if((rad->r==rad->l)&&(rad->l==NULL)){ printf("%c =",rad->inf); float t; scanf("%f",&t); rad->v=t; } else {atribuie_arbore(rad->l);

Page 59: 94650969 19 ID Program Area Calculatoarelor

* Material didactic pentru ID * 59

atribuie_arbore(rad->r); }}} void main() { clrscr(); char s[100];int p[100];arbore radacina=NULL; printf("Expresia:");scanf("%s",&s); prioritati(s,p); int n=strlen(s); cr_arb_str(&radacina,0,n-1,s,p); printf("\nForma poloneza inversa "); forma_poloneza(radacina); printf("\n Valori pentru varabile\n"); atribuie_arbore(radacina); printf("\nEvaluarea: %7.3f",eval(radacina)); getch(); }

Teste de autoevaluare Fie expresia Expr=(a-b-c)/e+f*g 3.5.1. Care este arborele de structură asociat expresiei Expr? 3.5.2. Care este forma poloneză inversă asociată expresiei Expr? 3.5.3. Care este forma poloneză directă asociată expresiei Expr?

3.6. Răspunsuri la testele de autoevaluare. Comentarii

3.1.1. Graful nu este arbore, deoarece conţine trei componente conexe: {1,2,3,4,6}, {3} şi {7,8}. 3.1.2. Graful din problema 3.1.1. nu este arbore deoarece este aciclic, dar n=9, m=6. 3.1.3. Graful din problema 3.1.1. nu este conex, deci nu este graf arbore. 3.2.1. Următoarea sursă C implementează problema construcţiei unui arbore orientat,

reprezentat prin intermediul unei structuri dinamice arborescente. Numărul maxim de descendenţi ai unui nod este 4. În cazul unui număr mai mare de descendenţi este preferată în general reprezentarea FIU-FRATE, datorită dimensiunii spaţiului de memorie ocupat. Afişarea informaţiilor arborelui creat este realizată prin traversarea în A-preordine (a se vedea paragraful următor).

#include<stdio.h> #include<conio.h> #include<alloc.h> typedef struct nod{ int inf; struct nod *fiu[4]; } arb, *arbore;

void inserare_tata(arbore *ptata,int k,int info) { arbore nou=(arbore)malloc(sizeof(arb)); nou->inf=info; for(int i=0;i<4;i++)nou->fiu[i]=NULL; (*ptata)->fiu[k]=nou; } void inserare(arbore *ppred) { int j,info; arbore *pred; for(int nr=0;(*ppred)->fiu[nr];nr++){ (*pred)=(*ppred)->fiu[nr]; printf("Numarul de fii ai nodului %i:",(*pred)->inf); scanf("%i",&j); for(int k=0;k<j;k++){

Page 60: 94650969 19 ID Program Area Calculatoarelor

Programarea calculatoarelor 60

scanf("%i",&info); inserare_tata(pred,k,info); } } for(nr=0;(*ppred)->fiu[nr];nr++) inserare(&((*ppred)->fiu[nr])); } void A_preordine(arbore r) { if(r){ printf("%i ",r->inf); for(int i=0;i<4;i++) A_preordine(r->fiu[i]); } } void main(){ clrscr(); int n,j,info; arbore radacina=NULL; printf("Introduceti informatiile pe niveluri\n"); printf("Introduceti radacina\n"); scanf("%i",&info); radacina=(arbore)malloc(sizeof(arb)); radacina->inf=info; for(int i=0;i<4;i++)radacina->fiu[i]=NULL; printf("Numarul de fii ai nodului %i",radacina->inf); scanf("%i",&j); for(int k=0;k<j;k++){ scanf("%i",&info); inserare_tata(&radacina,k,info); } arbore ppred=radacina; inserare(&ppred); printf("Parcurgerea A-preordine a arborelui : \n"); A_preordine(radacina); getch(); }

3.2.2. Pentru arborele orientat din exemplul 3.2.1., prin aplicarea parcurgerii în A-preordine,

rezultă: 1,2,5,6,9,10,11,12,13,7,3,4,8,14,15,16. 3.2.3. Pentru arborele orientat din exemplul 3.2.1. ordinea de vizitare a vârfurilor în parcurgerea

A-postordine este: 5,9,10,11,12,13,6,7,2,3,14,15,16,8,4,1. 3.2.4. Pentru arborele definit în exemplul 3.2.1., prin aplicarea parcurgerii pe niveluri, rezultă

următoarea ordine de vizitare a nodurilor, 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16. 3.2.5. În cazul reprezentării FIU-FRATE a arborelui de traversat, parcurgerea pe niveluri poate

fi implementată prin următoarea funcţie. void parcurgere_pe_nivele(nod R,int FIU[],int FRATE[],int n) { ptcoada C=NULL;push(C,R); while (C) { pop(C,v); VIZIT(v); v=FIU[v]; while (v){ push(C,v); v=FRATE[v]; } } }

Funcţiile push şi pop implementează inserarea unuei celule în coadă, respectiv extragerea unui element al cozii.

3.3.1. Evoluţia determinată de program pentru graful

Page 61: 94650969 19 ID Program Area Calculatoarelor

* Material didactic pentru ID * 61

1 4 2 3 8 2 6 5, 2 8 1 9 4 4 3

⎟⎟⎟⎟⎟⎟⎟⎟⎟⎟⎟⎟⎟

⎜⎜⎜⎜⎜⎜⎜⎜⎜⎜⎜⎜⎜

=

963865864421443351261242132

A

este,

i, j după cea de-a t-a iteraţie

muchia selectată

TATA

Costul

t=0 (-1,-1,-1,-1,-1,-1) t=1,i=0,j=0 (2,3) (-1,-2,2,-1,-1,-1) 1 t=2,i=1,j=1 (2,4) (-1,-3,2,2,-1,-1) 2 t=3,i=2,j=2 (1,6) (-2,-3,2,2,-1,1) 2 t=4,i=3,j=3 (1,5) (-3,-3,2,2,1,1) 3 t=5,i=4,j=4 (-3,-3,2,2,1,1) t=6,i=4,j=5 (1,2) (-5,1,1,2,1,1) 4 MUCHIILE ARBORELUI MINIM: {(2,3),(2,4),(1,6),(1,5),(1,2)} COSTUL: 12

3.4.1, 3.4.2, 3.4.3 În următoarea sursă C sunt implementaţi algoritmii de adăugare, ştergere şi

parcurgere SRD în arbori de sortare. #include<stdio.h> #include<conio.h> #include<alloc.h> typedef struct nod{ int inf; struct nod *l,*r; } arb, *arbore; void inserare(arbore *radacina,int info) { if(*radacina==NULL){

Page 62: 94650969 19 ID Program Area Calculatoarelor

Programarea calculatoarelor 62

arbore nou; nou=(arbore)malloc(sizeof(arb)); nou->inf=info; nou->l=nou->r=NULL; *radacina=nou; } else if((*radacina)->inf>info) inserare(&((*radacina)->l),info); else if((*radacina)->inf<info) inserare(&((*radacina)->r),info); } int extragere(arbore *radacina,int info) { if(*radacina==NULL) return 0; else if((*radacina)->inf>info) return extragere(&((*radacina)->l),info); else if((*radacina)->inf<info) return extragere(&((*radacina)->r),info); else{ if((*radacina)->l==NULL){ arbore aux=*radacina; *radacina=(*radacina)->r; free(aux); } else{ arbore p,p1; for(p=(*radacina)->l;p->r;p1=p,p=p->r); if(((*radacina)->l)->r==NULL){ (*radacina)->inf=p->inf; (*radacina)->l=p->l; free(p); } else{ (*radacina)->inf=p->inf; arbore aux=p; p1->r=p->l; free(aux); } } return 1; } } void srd(arbore radacina) { if(radacina){ srd(radacina->l); printf("%i ",radacina->inf); srd(radacina->r); } } void main() { clrscr(); int n,info; arbore radacina=NULL; printf("Numarul de noduri:"); scanf("%i",&n); printf("Introduceti informatiile\n"); for(int i=0;i<n;i++){ scanf("%i",&info); inserare(&radacina,info); } printf("Parcurgerea SRD a arborelui de sortare: \n"); srd(radacina); printf("\nInformatia nodului de extras:");

Page 63: 94650969 19 ID Program Area Calculatoarelor

* Material didactic pentru ID * 63

scanf("%i",&info); if(extragere(&radacina,info)){ printf("\nArborele rezultat in parcurgere SRD\n"); srd(radacina); } else printf("\nInformatia nu este in arbore"); getch(); }

3.4.4. Utilizând structura de arbore de la problemele 3.4.1-3.4.3, poate fi scrisă funcţia, void rsd(arbore radacina) { if(radacina){ printf("%i ",radacina->inf); srd(radacina->l); srd(radacina->r); }

3.5.1. Arborele de structură este,

+

/ *

- e f g

- c

a b

3.5.2. Forma poloneză inversă: ab-c-e/fg*+ 3.5.3. Forma poloneză directă: +/--abce*fg

Bibliografie 1. I. Gh. Roşca, B. Ghilic-Micu, C. Cocianu, M. Stoica, C. Uscatu, M. Mircea − Programarea

calculatoarelor. Algoritmi în programare, Ed. ASE Bucureşti, 2007 2. I. Gh. Roşca, B. Ghilic-Micu, C. Cocianu, M. Stoica, C. Uscatu − Programarea

calculatoarelor. Ştiinţa învăţării unui limbaj de programare. Teorie şi aplicaţii, Ed. ASE Bucureşti, 2003

3. Liviu Negrescu − Limbajele C şi C++ pentru începători, vol. I, II, Ed. Microinformatica, Cluj-Napoca, 1994

Page 64: 94650969 19 ID Program Area Calculatoarelor

Programarea calculatoarelor 64

4. Elemente de programare orientată obiect

Obiective Obiectivele acestei unităţi de învăţare constau în familiarizarea studenţilor cu noţiunile teoretice de programare orientată obiect şi dobîndirea abilităţilor de bază în acest domeniu, pentru a pregăti trecerea la etapa următoare din studiul programării calculatoarelor. Limbajul folosit pentru exemplificare este C++, o dezvoltare a limbajului C standard utilizat anterior. Cuprins 4.1. Modelul de date orientat obiect ...................................................................................... 64 4.2. Definirea claselor ............................................................................................................. 70 4.3. Constructori ..................................................................................................................... 73 4.4. Destructori........................................................................................................................ 76 4.5. Funcţii prieten.................................................................................................................. 77 4.6. Derivarea claselor ............................................................................................................ 78 4.7. Răspunsuri şi comentarii la testele de autoevaluare .................................................... 93 Timpul mediu de învăţare pentru studiul individual: 5 ore.

Programarea orientată obiect (Object Oriented Programming - OOP) reprezintă o tehnică ce s-a impus în anii ‘90, dovedindu-se benefică pentru realizarea sistemelor software de mare complexitate. Noţiunea de obiect datează din anii ’60, odată cu apariţia limbajului Simula. Există limbaje – ca Smalltalk şi Eiffel – care corespund natural cerinţelor programării orientate obiect, fiind concepute în acest spirit. Recent au fost dezvoltate şi alte limbaje orientate obiect, fie pentru programare generală, fie pentru realizarea de scripturi – Java, Delphi, C++, Visual Basic .NET, C#, Python, Ruby. Unele dintre ele oferă în continuare şi posibilitatea programări procedurale (Delphi, C++). Toate limbajele folosite în prezent oferă şi facilităţi de programare orientată obiect – ADA, Fortran, Cobol, PHP etc. În prezent există în funcţiune sisteme software de mare anvergură realizate în tehnica programării orientată obiect, principiile ei fiind suficient de bine clarificate, astfel încît să se treacă din domeniul cercetării în cel al producţiei curente de programe. Acest capitol prezintă o introducere în lucrul orientat obiect în limbajul C++, fără a acoperi toată problematica specifică. 4.1. Modelul de date orientat obiect OOP reprezintă o abordare cu totul diferită faţă de programarea procedurală, devenită deja „clasică”. Dacă în programarea clasică programatorul era preocupat să răspundă la întrebarea „ce trebuie făcut cu datele?”, adică să definească proceduri care să transforme datele în rezultate, în OOP accentul cade asupra datelor şi legăturilor între acestea, ca elemente prin care se modelează obiectele lumii reale. Se poate afirma, într-o primă analiză, că OOP organizează un program ca o colecţie de obiecte, modelate prin date şi legături specifice, care interacţionează dinamic, adică manifestă un anumit „comportament”, producînd rezultatul scontat. În general, pentru modelul de date orientat pe obiect, se consideră definitorii următoarele concepte: abstractizare, obiect, atribut, metodă, clasă, spaţiu propriu, spaţiu extins, încapsulare, moştenire şi polimorfism.

Abstractizarea constituie procesul de simplificare a realităţii prin reţinerea caracteristicilor şi

comportamentelor esenţiale şi constituirea lor într-un model adecvat rezolvării problemelor.

Page 65: 94650969 19 ID Program Area Calculatoarelor

* Material didactic pentru ID * 65

Obiectul este un model informaţional al unei entităţi reale, care posedă, la un anumit nivel, o mulţime de proprietăţi şi care are, în timp, un anumit comportament, adică manifestă reacţii specifice în relaţiile cu alte entităţi din mediul său de existenţă. Ca model, un obiect este o unitate individualizabilă prin nume, care conţine o mulţime de date şi funcţii. Datele descriu proprietăţile şi nivelul acestora, iar funcţiile definesc comportamentul.

Avînd în vedere proprietăţile comune şi comportamentul similar al entităţilor pe care le modelează, obiectele pot fi clasificate în mulţimi. O mulţime de obiecte de acelaşi fel constituie o clasă de obiecte, descrisă prin modelul comun al obiectelor sale. Exemplu

În figura 10.1, numerele complexe, ca perechi de numere reale de forma (parte reală, parte imaginară) pot fi descrise printr-un model comun, denumit ClasaComplex. Modelul arată că orice obiect de acest fel se caracterizează printr-o pereche de numere întregi şi că pe această mulţime sînt definite operaţii unare şi binare care arată cum „interacţionează” obiectele în interiorul mulţimii: un număr complex poate da naştere modulului şi opusului său, două numere complexe pot produce un alt număr complex ca sumă, produs etc. Generalizînd, se poate afirma că o clasă de obiecte se manifestă ca un tip obiect, iar modelul comun al obiectelor este modelul de definire a tipului obiect. Astfel, obiectele individuale apar ca manifestări, realizări sau instanţieri ale clasei, adică exemplare particulare generate după modelul dat de tipul obiect. Altfel spus, o clasă poate fi considerată ca un tip special de dată, iar obiectele sale ca date de acest tip.

Fig.4.1. Clasă şi obiecte – mulţimea numerelor complexe

Acceptarea acestei semnificaţii pentru clase de obiecte este de natură să simplifice descrierea

obiectelor şi să asigure un tratament al acestora similar tipurilor structurate de date din limbajele de programare: este suficientă o descriere a tipului obiect şi apoi se pot declara constante şi variabile de acest tip. Datele care reprezintă proprietăţile obiectelor se numesc atribute şi sînt de un anumit tip (de exemplu întregi, reale, caractere etc.). Setul de valori ale atributelor unui obiect la un moment dat formează starea curentă a obiectului respectiv. Funcţiile care definesc comportamentul obiectelor sînt cunoscute ca metode ale clasei. Împreună, atributele şi metodele sînt membrii clasei, identificabili prin nume. Pentru a pune în evidenţă faptul că un membru aparţine unui obiect se utilizează calificarea, astfel: nume_obiect.nume_membru. În figura 4.1, a.P_reală referă valoarea 1.0, iar a.Modul referă metoda Modul a obiectului a pentru a produce obiectul rezultat.

Aşa cum sugerează figura 4.1, fiecare obiect trebuie să conţină valorile atributelor sale, deoarece ele definesc starea obiectului respectiv. Spaţiul de memorie ocupat de atributele unui obiect se numeşte spaţiu propriu al obiectului. În multe cazuri, între atribute se află pointeri care indică anumite zone de memorie alocate dinamic pentru obiect (de exemplu clasa listă are ca membru atributul cap care conţine adresa primului nod al unei liste dinamice simplu înlănţuite). Acest spaţiu alocat dinamic aparţine tot obiectului, dar el se numeşte spaţiu extins al obiectului. Gestiunea acestui spaţiu extins trebuie asigurată de metodele clasei.

Metodele, care descriu acţiuni identice pentru toate obiectele clasei, sînt memorate o singură dată, într-o zonă comună tuturor obiectelor clasei. Întrucît metodele descriu comportamentele obiectelor, ele nu pot fi apelate independent, ci numai în legătură cu un anumit obiect.

Page 66: 94650969 19 ID Program Area Calculatoarelor

Programarea calculatoarelor 66

Despre o metodă apelată pentru un anumit obiect se spune că se execută în contextul obiectului respectiv, iar acesta este numit obiect curent. Apelarea metodei este considerată ca trimitere de mesaj către obiectul curent, iar execuţia metodei reprezintă răspunsul (reacţia) obiectului curent la mesajul primit. Faptul că o metodă se execută în contextul obiectului curent înseamnă că are, în mod implicit, acces la toate atributele şi metodele obiectului. Acestea nu trebuie să apară ca parametri ai metodei. Pentru a utiliza alte obiecte, din aceeaşi clasă sau din clase diferite, metoda trebuie să aibă parametri corespunzători. De asemenea, pentru a simplifica scrierea, în interiorul unei metode referirea la membrii obiectului curent se face fără calificare. Exemplu Pe baza acestor convenţii, în funcţiile Conjugat, Suma şi Modul, scrise în pseudocod, s-a specificat cu un parametru mai puţin decît numărul de operanzi pe care îi presupune operaţia respectivă, deoarece un operand este obiectul curent. Referirea la atributele obiectului curent se distinge de celelalte prin lipsa calificării. Descrierea în pseudocod a metodelor Conjugat, Suma şi Modul din clasa CComplex (figura 4.1) poate fi făcută astfel:

void Conjugat(b) { b.p_reala=p_reala; b.p_imaginara=-p_imaginara; } void Suma(b,c) { c.p_reala=p_reala+b.p_reala; c.p_imaginara=-p_imaginara+b.p_imaginara; }

float Modul() { Modul=sqrt(p_reala*p_reala+p_imaginara*p_imaginara); } Deoarece o clasă este un tip de dată, în definirea unei clase B se pot declara atribute de tip A, unde A este la rîndul ei o clasă. Mai mult, o clasă A poate defini atribute de tip A. De exemplu clasa Carte, din figura 4.2 are atributul Autor de tipul Persoana care este, de asemenea, o clasă. Mai mult, Persoana are atributul Sef care este de acelaşi tip (Persoana).

Fig.4.2. Atribute de tip clasă

Page 67: 94650969 19 ID Program Area Calculatoarelor

* Material didactic pentru ID * 67

Definirea atributelor unei clase ca tipuri ale altei clase pune în evidenţă o relaţie între clase şi deci între obiectele acestora. Din punct de vedere funcţional, metodele unei clase au destinaţii diverse. În multe cazuri şi depinzînd de limbaj, unei clase i se poate defini o metodă (sau mai multe) constructor şi o metodă destructor. Un constructor este o metodă care creează un obiect, în sensul că îi alocă spaţiu şi/sau iniţializează atributele acestuia. Destructorul este o metodă care încheie ciclul de viaţă al unui obiect, eliberînd spaţiul pe care acesta l-a ocupat.

Încapsularea exprimă proprietatea de opacitate a obiectelor cu privire la structura lor internă şi la modul de implementare a metodelor. Ea este legată de securitatea programării, furnizînd un mecanism care asigură accesul controlat la starea şi funcţionalitatea obiectelor. Se evită astfel modificări ale atributelor obiectelor şi transformări ale acestora care pot să le „deterioreze”. Potrivit acestui mecanism, o clasă trebuie să aibă membrii împărţiţi în două secţiuni: partea publică şi partea privată. Partea publică este constituită din membri (atribute şi metode) pe care obiectele le oferă spre utilizare altor obiecte. Ea este interfaţa obiectelor clasei respective cu „lumea exterioară” şi depinde de proiectantul clasei. Modalitatea extremă de constituire a interfeţei este aceea a unei interfeţe compusă numai din metode. Dacă se doreşte ca utilizatorii obiectelor clasei să poată prelua şi/sau stabili valorile unor atribute ale acestora, interfaţa trebuie să prevadă metode speciale, numite accesorii, care au ca unic rol accesul la atribute. Partea privată cuprinde membri (atribute şi/sau metode) care servesc exclusiv obiectelor clasei respective. De regulă, în această parte se includ atribute şi metode care facilitează implementarea interfeţei şi a funcţionalităţii interne a obiectului. Exemplu De exemplu, o stivă, ca tip de dată poate fi descrisă de o clasă Stiva în care interfaţa este constituită din metodele Push, Pop, Top, Empty, în timp ce pointerul la capul stivei, Cap şi numărătorul de noduri, Contor, ca atribute, sînt „ascunse” în partea privată. Ea se serveşte de obiectele altei clase, denumită Nod, ale cărei obiecte le înlănţuieşte în stivă (figura 4.3)

Fig.4.3. Interfaţa obiectelor

Trebuie remarcat că încapsularea înseamnă şi faptul că utilizatorul metodelor nu trebuie să cunoască codul metodelor şi nici nu trebuie să fie dependent de eventuala schimbare a acestuia, interfaţa fiind aceea care îi oferă funcţionalitatea obiectelor în condiţii neschimbate de apelare.

Moştenirea reprezintă o relaţie între clase şi este, probabil, elementul definitoriu al OOP. Relaţia permite constituirea unei noi clase, numită derivată (sau fiu) pornind de la clase existente, denumite de bază (sau părinte). Dacă în procesul de construire participă o singură clasă de bază, moştenirea este simplă, altfel este multiplă. Se spune că o clasă D moşteneşte o clasă A, dacă obiectele din clasa D conţin toate atributele clasei A şi au acces la toate metodele acestei clase. Din această definiţie, dacă D moşteneşte A, atunci obiectele din D vor avea toate atributele şi acces la toate metodele lui A, dar în plus:

- D poate defini noi atribute şi metode; - D poate redefini metode ale clasei de bază; - metodele noi şi cele redefinite au acces la toate atributele dobîndite sau nou definite.

Page 68: 94650969 19 ID Program Area Calculatoarelor

Programarea calculatoarelor 68

Exemplu În figura 4.4, clasa Cerc moşteneşte clasa Punct, deci un obiect de tipul Cerc va avea ca membri coordonatele x,y moştenite şi ca atribut propriu Raza. Funcţia Distanţa, definită pentru calculul distanţei dintre punctul curent şi punctul p, dat ca parametru, este accesibilă şi pentru obiectele Cerc şi va calcula distanţa dintre centrul cercului şi un alt punct, primit ca parametru. Funcţia Arie calculează aria din interiorul cercului, fiind nou definită. Funcţia Desenează este redeclarată de clasa Cerc, lucru impus de codul diferit pe care trebuie să-l aibă pentru desenarea obiectelor din această clasă (cerc sau altă figură).

X: intY:intDesenează()Distanţa(p: punct): float

Punct

X= 100Y= 100

Raza: intArie():floatDesenează()Distanţa(p: punct): float

Cerc

X= 200Y= 200

Raza= 50

Fig.4.4. Moştenirea simplă.

Dacă se au în vedere mulţimi de clase, atunci se observă că relaţia de moştenire simplă induce un arbore ierarhic de moştenire pe această mulţime. Există o singură clasă iniţială, rădăcina arborelui, fiecare clasă are un singur ascendent (părinte) şi orice clasă care nu este frunză poate avea unul sau mai mulţi descendenţi (fii). În fine, cu privire la moştenirea simplă se pot face următoarele observaţii: • dacă se aduc modificări în clasa de bază, prin adăugarea de atribute şi/sau metode, nu este necesar să se modifice şi clasa derivată; • moştenirea permite specializarea şi îmbogăţirea claselor, ceea ce înseamnă că, prin redefinire şi adăugare de noi membri, clasa derivată are, în parte, funcţionalitatea clasei de bază, la care se adaugă elemente funcţionale noi; • moştenirea este mecanismul prin care se asigură reutilizarea codului sporind productivitatea muncii de programare. Coborînd (de obicei, în reprezentările grafice ale arborilor de clase, rădăcina se află în partea superioară) în arborele ierarhic al claselor de la rădăcină către frunze, se poate spune că întîlnim clase din ce în ce mai specializate. Prin moştenire se realizează o specializare a claselor. În sens invers, de la frunză către rădăcină, clasele sînt din ce în ce mai generale, avem o relaţie de generalizare. Clasa aflată la baza ierarhiei este cea mai generală. Limbajele de programare orientate obiect au implementate ierarhii standard extinse de clase, care corespund necesităţilor generale ale programării. Utilizatorii pot deriva clase noi din cele standard.

Polimorfismul este un concept mai vechi al programării, cu diferite implementări în limbajele de programare care se bazează pe tipuri de date (limbaje cu tip). El şi-a găsit extensia naturală şi în modelul orientat pe date, implementat prin limbaje cu tip, în care clasa reprezintă tipul de date obiect. • Polimorfismul în limbajele de programare cu tip. Noţiunea de polimorfism exprimă capacitatea unui limbaj de programare cu tip de a exprima comportamentul unei proceduri independent de natura (tipul) parametrilor săi. De exemplu, o funcţie care determină cea mai mare valoare dintr-un şir de valori este polimorfică dacă poate fi scrisă independent de tipul acestor valori. În funcţie de modul de implementare, se disting mai multe tipuri de polimorfism.

Polimorfismul ad-hoc se materializează sub forma unor funcţii care au toate acelaşi nume, dar se disting prin numărul şi/sau tipul parametrilor. Acest polimorfism este denumit şi supraîncărcare, avînd în vedere semantica specifică fiecărei funcţii în parte.

Page 69: 94650969 19 ID Program Area Calculatoarelor

* Material didactic pentru ID * 69

Polimorfismul de incluziune se bazează pe o relaţie de ordine parţială între tipurile de date, denumită relaţie de incluziune sau inferioritate. Dacă un tip A este inclus (inferior) într-un tip B, atunci se poate trimite un parametru de tip A unei funcţii care aşteaptă un parametru de tip B. Astfel, un singur subprogram defineşte funcţional o familie de funcţii pentru toate tipurile inferioare celor declarate ca parametri. Un exemplu clasic este cazul tipului int, inferior tipului float în toate operaţiile de calcul.

Polimorfism parametric constă în definirea unui model de procedură pentru care chiar tipurile sînt parametri. Acest polimorfism, denumit şi genericitate, presupune că procedura se generează pentru fiecare tip transmis la apel ca parametru. Cele trei tipuri de polimorfism există (toate sau numai o parte din ele) în limbajele clasice de programare, dar unele pot să nu fie accesibile programatorului. • Polimorfismul în limbajele orientate obiect. Limbajele orientate obiect sau extensiile obiect ale unor limbaje cu tip oferă, în mod natural, polimorfismul ad-hoc şi de incluziune.

Polimorfismul ad-hoc intrinsec reprezintă posibilitatea de a defini în două clase independente

metode cu acelaşi nume, cu parametri identici sau diferiţi. Acest polimorfism nu necesită mecanisme speciale şi decurge simplu, din faptul că fiecare obiect este responsabil de tratarea mesajelor pe care le primeşte. Polimorfismul este de aceeaşi natură şi în cazul în care între clase există o relaţie de moştenire, cu precizarea că, în cazul în care o metodă din clasa derivată are parametrii identici cu ai metodei cu acelaşi nume din clasa de bază, nu mai este supraîncărcare, ci redefinire, după cum s-a precizat mai sus.

Polimorfimsul de incluziune este legat de relaţia de moştenire şi de aceea se numeşte polimorfism de moştenire. Într-adevăr, relaţia de moştenire este o relaţie de ordine parţială, astfel încît dacă clasa D moşteneşte direct sau indirect clasa A, atunci D este inferior lui A. În aceste condiţii, orice metodă a lui A este aplicabilă la obiectele de clasă D şi orice metodă, indiferent de context, care are definit un parametru de tip A (părinte) poate primi ca argument corespunzător (parametru actual) un obiect de clasă D (fiu). Observaţie: un obiect de clasă A nu poate lua locul unui obiect de clasă D, deoarece A “acoperă” numai parţial pe D, care este o extensie şi o specializare a lui A. • Legare statică respectiv dinamică a metodelor. Legarea statică a metodelor se regăseşte atît în limbajele orientate obiect cît şi în cele clasice. Compilatorul poate determina care metodă şi din care clasă este efectiv apelată într-un anumit context şi poate genera codul de apel corespunzător. Exemplu Fie o clasă A şi o clasă D, unde D este derivată din A. Fie o metodă din clasa A, numită calculează, care este redefinită în clasa derivată, D. Atunci cînd metoda este apelată în contextul unui obiect static, compilatorul poate determina tipul acelui obiect (ca fiind parte a clasei A sau D). Astfel, el va şti ce metodă să apeleze (a clasei de bază sau cea redefinită, a clasei derivate). În acest caz are loc o legare statică a metodelor (decizia este luată în momentul compilării). Fie un pointer p, definit ca pointer spre clasa A. Datorită polimorfismului, în limbajele orientate obiect unui obiect din clasa părinte, desemnat indirect prin referinţă (pointer) şi nu prin nume, i se poate atribui un obiect fiu. În acest context, p poate primi ca valoare, în timpul execuţiei programului, adresa unui obiect din clasa A sau din clasa D. Nu se poate şti la momentul compilării ce se va întîmpla în timpul execuţiei programului, ca urmare nu se poate determina dacă, în contextul dat, trebuie apelată metoda clasei de bază sau metoda clasei derivate. De aceea, în locul din program în care este apelată metoda, compilatorul adaugă o secvenţă de cod care, la momentul execuţiei, va verifica tipul efectiv al obiectului şi, după caz, va realiza apelarea metodei adecvate. În acest caz are loc legarea dinamică a metodelor (sau la momentul execuţiei). Legarea dinamică este evident mai costisitoare decît cea statică, dar reprezintă o necesitate pentru a asigura elasticitatea necesară în realizarea programelor OOP, obiectele putînd avea caracter de variabile dinamice.

Page 70: 94650969 19 ID Program Area Calculatoarelor

Programarea calculatoarelor 70

Teste de autoevaluare 1. Explicaţi noţiunea de obiect din POO. 2. Explicaţi noţiiunea de clasă de obiecte din POO. 3. Care sînt conceptele de bază ale POO? 4. Explicaţi noţiunea de încapsulare din POO. 5. Care sînt formele de polimorfism din limbaje procedurale? Daţi exemple. 6. Care sînt formele de polimorfism din POO? 7. Explicaţi noţiunea de polimorfism de moştenire. 8. Explicaţi noţiunea de legare statică a metodelor. 9. Explicaţi noţiunea de legare dinamică a metodelor.

4.2. Definirea claselor Definiţia unei clase este asemănătoare cu definiţia unui articol, însă în locul cuvîntului rezervat struct se foloseşte cuvîntul class: class nume { descriere membri; }; Membrii unei clase pot fi atribute sau metode. Atributele sînt descrise asemănător declaraţiilor de variabile independente (şi asemănător cîmpurilor unui articol – struct), specificînd tipul şi numele atributului respectiv. Membrii unei clase pot fi de orice tip, mai puţin de acelaşi tip cu clasa descrisă (dar pot fi pointeri către clasa descrisă). Metodele sînt descrise asemănător funcţiilor independente. Ele pot fi descrise integral în interiorul clasei (descriind antetul şi corpul lor) sau specificînd în interiorul clasei doar prototipul funcţiei, corpul urmînd să fie descris ulterior, în afara clasei. Este preferată a doua variantă, deoarece descrierea clasei este mai compactă decît în primul caz. Atunci cînd se descrie ulterior corpul unei metode, pentru a specifica apartenenţa sa la clasa respectivă, numele metodei este prefixat cu numele clasei din care face parte, folosind operatorul de rezoluţie (::), astfel:

tip_rezultat nume_clasă::nume_metodă(lista parametrilor) corp metodă

Mai mult, funcţiile care sînt integral descrise în interiorul clasei sînt considerate funcţii inline1, de aceea ele trebuie să fie simple. Pentru funcţiile mai complexe, întotdeauna se recomandă să fie descrise folosind a doua variantă. Întrucît o clasă este un tip de dată, declararea unui obiect se face asemănător oricărei declaraţii de dată: nume_clasă nume_obiect; Atunci cînd se doreşte lucrul cu obiecte dinamice, se poate declara o variabilă de tip pointer către clasă: nume_clasă* p_obiect; Declararea unui obiect mai este numită şi instanţierea clasei, în sensul că se creează o instanţă a acelei clase, o entitate concretă din mulţimea descrisă de clasa respectivă.. Exemplu

Definirea clasei Complex, care implementează entitatea matematică număr complex. Clasa are atributele p_reala şi p_imaginara şi o metodă pentru afişarea valorii obiectului – afiseaza. 1 Apelul funcţiilor inline nu produce un salt în segmentul de cod către codul executabil al funcţiei, aşa cum se întîmplă în cazul funcţiilor obişnuite. Pentru aceste funcţii, compilatorul inserează în program, în locul apelului, secvenţa de cod corespunzătoare corpului funcţiei, înlocuind parametrii formali cu valorile actuale. Funcţiile inline au un comportament asemănător macrodefiniţiilor.

Page 71: 94650969 19 ID Program Area Calculatoarelor

* Material didactic pentru ID * 71

class Complex { float p_reala,p_imaginara; void Afiseaza(); }; void Complex::Afiseaza() { printf("\n%5.2f%ci*%5.2f\n",p_reala,p_imaginara>=0?'+':'-',

p_imaginara>=0?p_imaginara:-p_imaginara); } Complex tc; Metoda afişează ţine cont de semnul părţii imaginare. Dacă aceasta este negativă, semnul minus este afişat înaintea simbolului i al părţii imaginare. Se declară obiectul tc de tipul Complex. Accesul la membrii obiectului se face folosind operatorul de calificare: nume_obiect.nume_membru unde numele obiectului specifică din ce obiect este accesat atributul respectiv sau în contextul cărui obiect se execută metoda respectivă. Acest mod de accesare este folosit atunci cînd se lucrează cu obiecte statice. În cazul în care nu avem un obiect ci un pointer către un obiect, este necesară şi dereferenţierea pointerului, înainte de accesul la membri. Acest lucru este realizat folosind operatorul -> în locul operatorului de calificare: p_obiect -> nume_membru Pentru implementarea conceptului de încapsulare, în interiorul unei definiţii de clasă pot fi folosiţi modificatori de acces. Aceştia sînt private, protected şi public (urmaţi de caracterul : „două puncte”). Domeniul de acţiune al unul modificator de acces începe în locul unde apare el şi se încheie la apariţia altui modificator sau la terminarea descrierii clasei. Implicit, toţi membri sînt consideraţi sub influenţa modificatorului private, deci orice membru aflat în afara domeniului de acţiune al unui modificator este considerat privat. Modificatorul public face ca toţi membri aflaţi în domeniul său de acţiune să poată fi accesaţi atît de către metodele clasei cît şi de către orice entitate din afara clasei (membri publici). Modificatorul private face ca membrii aflaţi în domeniul său de acţiune să poată fi accesaţi numai de către metodele clasei respective (membri privaţi). Modificatorul protected este similar cu modificatorul private, dar membrii respectivi pot fi accesaţi şi de către metodele claselor derivate, dar numai în obiecte aparţinînd claselor derivate. De obicei atributele unei clase sînt declarate ca fiind private iar metodele sînt împărţite, unele fiind publice (interfaţa clasei) şi unele private (detalii şi mecanisme interne de implementare a clasei. Deşi este tehnic posibil ca toţi membrii unei clase să fie privaţi, un obiect de acest tip nu poate fi folosit, neavînd o interfaţă cu mediul exterior lui. De asemenea, toţi membrii unei clase pot fi publici, dar nu este recomandată această tehnică din motive de protecţie şi securitate. Exemplu

În acest context, clasa Complex definită în exemplul anterior nu poate fi folosită, toţi membrii ei fiind privaţi. Pentru a putea folosi obiecte de tipul Complex, metoda afişează trebuie să fie publică. Descrierea clasei devine: class Complex { float p_reala,p_imaginara; public: void Afiseaza(); };

Page 72: 94650969 19 ID Program Area Calculatoarelor

Programarea calculatoarelor 72

void Complex::Afiseaza() { printf("\n%5.2f%ci*%5.2f\n",p_reala,p_imaginara>=0?'+':'-',

p_imaginara>=0?p_imaginara:-p_imaginara); } Prin adăugarea modificatorului de acces public, metoda afişează este pusă la dispoziţia mediului extern, ca interfaţă a obiectului. Pentru accesul controlat la atributele private, clasele pot pune la dispoziţia mediului extern metode publice de acces, numite uzual metode accesorii. Acestea au rolul de a prezenta mediului extern valorile unora dintre atribute (acelea care pot prezenta interes) sau de a modifica valorile unora dintre atribute, în mod controlat (numai acele atribute pentru care modificarea la iniţiativa mediului extern are sens). Controlul depinde în fiecare caz de scopul clasei respective. Exemplu

Adăugînd metode accesorii clasei Complex, descrierea acesteia devine: class Complex { float p_reala,p_imaginara; public: void Afiseaza(); float GetR(); float GetI(); void SetR(float r); void SetI(float i); }; void Complex::Afiseaza() { printf("\n%5.2f%ci*%5.2f\n",p_reala,p_imaginara>=0?'+':'-',

p_imaginara>=0?p_imaginara:-p_imaginara); } float Complex::GetR() { return p_reala; } float Complex::GetI() { return p_imaginara; } void Complex::SetR(float r) { p_reala=r; } void Complex::SetI(float i) { p_imaginara=i; }

Metodele accesorii definite mai sus (GetR, GetI, SetR, SetI) au rolul de a prezenta valorile atributelor şi respectiv de a stabili noi valori pentru ele. În acest exemplu nu se face nici un fel de control asupra modului în care sînt stabilite noile valori. Exemplu

Folosind descrierile de mai sus, următoarea secvenţă de program: void main() { Complex tc; Complex *pc; tc.SetR(5); tc.SetI(-4);

Page 73: 94650969 19 ID Program Area Calculatoarelor

* Material didactic pentru ID * 73

tc.Afiseaza(); pc=&tc; pc->Afiseaza(); pc->SetR(-2); pc->SetI(3); pc->Afiseaza(); } produce pe ecran următorul rezultat: 5.00-i* 4.00 5.00-i* 4.00 -2.00+i* 3.00

Pentru a evita eventualele confuzii, în interiorul corpului metodelor poate fi folosit pointerul implicit this atunci cînd se referă membrii obiectului curent. Acesta este gestionat automat şi are ca valoare adresa obiectului curent. Ca urmare, metoda SetR ar putea fi rescrisă astfel:

void Complex::SetR(float r) { this -> p_reala = r; } Pointerul this poate fi folosit şi pentru accesarea metodelor obiectului curent, în acelaşi mod.

Ţinînd cont de domeniul de valabilitate al declaraţiilor de tipuri de date, descrierea unei clase se face de obicei la începutul fişierului sursă în care urmează a fi folosită. Pentru o mai mare generalitate şi reutilizare mai uşoară, se preferă ca fiecare clasă nouă să fie descrisă într-un fişier sursă separat, care să fie inclus folosind directiva #include în programe. Teste de autoevaluare

10. Care sînt modificatorii de acces din descrierea claselor şi ce efect are fiecare? 11. Care este modificatorul de acces implicit? 12. În ce ordine trebuie săa apară modificatorii de acces în cadrul unei clase? 13. Este posibil să ca unul dintre modificatorii de acces să nu apară în descrierea unei clase? Pot

lipsi doi modificator de acces? Pot lipsi toţi modificatorii de acces?

4.3. Constructori Declararea obiectelor are ca efect alocarea de spaţiu în memorie, la fel ca în cazul declarării oricărei variabile. Acest spaţiu nu este iniţializat însă. Mai mult, în cazul în care obiectele clasei au şi spaţiu extins de memorie, acesta nu este alocat automat, obiectul declarat fiind astfel incomplet. Atributele unui obiect nu pot fi iniţializate la declarare într-o manieră asemănătoare datelor de tip articol (struct), deoarece de obicei atributele sînt private, deci inaccesibile din exteriorul obiectului. Pentru rezolvarea problemei iniţializării obiectelor există posibilitatea utilizării unor metode speciale, numite constructori. La terminarea ciclului de viaţă al obiectelor, este necesară dezalocarea lor. În general aceasta se realizează automat, dar în cazul lucrului cu spaţiu extins, ea trebuie gestionată în mod explicit. Problema încheierii ciclului de viaţă al obiectelor este rezolvată prin utilizarea unor metode speciale numite destructori. Constructorii şi destructorii nu întorc nici un rezultat prin numele lor şi antetele lor nu precizează nici un tip pentru rezultat (nici măcar void). Constructorii sînt metode care au acelaşi nume cu clasa căreia îi aparţin. O clasă poate avea mai mulţi constructori, cu liste diferite de parametri (ca tip şi/sau număr) – metode supraîncărcate. Dacă nu este definit nici un constructor pentru o clasă, compilatorul va genera un constructor implicit,

Page 74: 94650969 19 ID Program Area Calculatoarelor

Programarea calculatoarelor 74

care nu face decît alocarea spaţiului propriu al obiectului, în momentul în care acesta a fost declarat. Ca urmare, în acest caz vom avea obiecte neiniţializate, urmînd ca iniţializarea atributelor să se facă ulterior, prin intermediul metodelor accesorii. În exemplul anterior, pentru clasa Complex s-a generat un constructor implicit care alocă spaţiu pentru atributele p_reala şi p_imaginara. Iniţializarea s-a făcut prin intermediul metodelor SetR şi SetI. În cazul în care clasa prezintă cel puţin un constructor explicit, compilatorul nu mai generează constructorul implicit. Ca urmare nu se vor putea declara obiecte neiniţializate dacă parametrii constructorului nu au valori implicite.

Constructorii nu pot fi apelaţi explicit, precum metodele obişnuite. Apelul lor se realizează numai la declararea obiectelor. De asemenea, nu se poate determina adresa constructorilor, aşa cum se poate face în cazul funcţiilor obişnuite. Am văzut mai sus că declaraţia unui obiect care nu are constructor explicit este identică cu declaraţia unei variabile simple. În cazul în care clasa prezintă constructori expliciţi valorile pentru iniţializare sînt transmise acestuia la declararea obiectului, asemănător listei de parametri reali la apelul unei funcţii:

nume_clasă nume_obiect(lista_valori); Exemplu

Pentru clasa Complex se poate defini un constructor care să iniţializeze cei doi membri astfel:

class Complex { float p_reala,p_imaginara; public: Complex(float a,float b); }; Complex::Complex(float a,float b) { p_reala=a; p_imaginara=b; }

Avînd acest constructor în cadrul clasei, nu putem declara obiecte neiniţializate (ca în exemplele anterioare) ci doar obiecte iniţializate: Complex a(3,4); //a reprezintă numarul 3+i*4 Complex b(-1,3.2); //b reprezintă numarul -1+i*3.2 Complex c; //incorect

Atunci cînd există un singur constructor explicit, se aplică regulile transferului parametrilor similar ca la funcţiile obişnuite (se realizează conversia parametrilor reali către tipurile formale). Dacă sînt mai mulţi constructori, cu liste diferite de parametri, se alege acela a cărui listă de parametri corespunde ca tip şi număr cu lista valorilor (expresiilor) precizate la declararea obiectului. Pentru constructori, ca şi pentru orice altă funcţie, pot fi precizate valori implicite ale parametrilor. Acolo unde la apel lipsesc parametrii actuali, se folosesc valorile implicite. Ca urmare, la declararea obiectelor pot să nu fie precizate valori pentru toate atributele. Exemplu

Se defineşte clasa Complex astfel: class Complex { float p_reala,p_imaginara; public: Complex(float a=0,float b=0); }; Complex::Complex(float a,float b) { p_reala=a; p_imaginara=b; }

Page 75: 94650969 19 ID Program Area Calculatoarelor

* Material didactic pentru ID * 75

Putem declara următoarele obiecte: Complex a; //a reprezintă numarul 0+i*0 Complex b(-1); //b reprezintă numarul -1+i*0 Complex c(2,3); //c reprezintă numarul 2+i*3

Dacă prototipul constructorului ar fi fost Complex(float a,float b=0); atunci la declaraţia obiectului trebuie precizată obligatoriu valoarea pentru primul parametru (cel care va deveni valoarea atributului p_reala); ca urmare, dintre declaraţiile anterioare ar fi fost corecte numai cele ale obiectelor b şi c. Acolo unde se poate folosi un singur parametru la declararea obiectului, se poate face iniţializarea asemănător iniţializării variabilelor simple. Folosind oricare dintre cei doi constructori din exemplul anterior, putem declara un obiect şi în modul următor:

Complex a = 1; //numarul 1+i*0 Valoarea declarată este atribuită primului parametru al constructorului. Pentru situaţiile în care avem nevoie atît de obiecte iniţializate cît şi de obiecte neiniţializate, se adaugă în descrierea clasei atît un constructor care realizează iniţializarea obiectului cît şi un constructor vid, care simulează constructorul implicit, adăugat de compilator atunci cînd nu se definesc constructori impliciţi. Constructorul vid are următoarea formă: Complex::Complex() { } În acest context, declaraţia Complex a; are ca efect crearea obiectului a neiniţializat (se utilizează constructorul vid, nu constructorul cu valori implicite pentru parametri). Parametrii unui constructor pot fi de orice tip, mai puţin de tipul clasei respective (în exemplele anterioare, constructorul clasei Complex nu poate avea un parametru de tipul Complex. Este posibil însă să avem un parametru de tip pointer către clasa respectivă sau referinţă2 către clasa respectivă. Un constructor care primeşte ca parametru o referinţă către un obiect din acea clasă se numeşte constructor de copiere. Prin utilizarea unui constructor de copiere se poate iniţializa un obiect nou cu atributele unui obiect existent (se realizează o copie a acelui obiect). Constructorii de copiere pot avea şi alţi parametri, dar aceştia trebuie să aibă valori implicite. Compilatorul generează automat un constructor de copiere pentru toate clasele care nu au un constructor de copiere definit explicit. Exemplu

Clasa Complex conţine un constructor de copiere: class Complex { float p_reala,p_imaginara; public: Complex(float a=0,float b=0); Complex(Complex &x); }; Complex::Complex(float a,float b) { p_reala=a; 2 În C++ este implementat transferul parametrilor prin adresă. Pentru a transmite un parametru prin adresă, în lista de parametri se pune înaintea numelui său operatorul de referenţiere &

Page 76: 94650969 19 ID Program Area Calculatoarelor

Programarea calculatoarelor 76

p_imaginara=b; } Complex::Complex(Complex &x) { p_reala=x.p_reala; p_imaginara=x.p_imaginara; } Putem declara următoarele obiecte: Complex a(3,4); //numarul 3+i*4 Complex b = a; //numarul 3+i*4 Complex c(b); //numarul 3+i*4 Obiectul b este o copie a obiectului a şi va avea aceeaşi stare, imediat după declarare (aceleaşi valori ale atributelor). De asemenea, obiectul c este o copie a obiectului b. Pentru ambele obiecte s-a apelat constructorul de copiere. Constructorul implicit de copiere realizează o copie binară a spaţiului propriu de memorie al obiectului sursă. Ca urmare, dacă obiectele au spaţiu extins de memorie, în urma copierii ambele obiecte referă acelaşi spaţiu extins de memorie. Pentru a evita această situaţie anormală, atunci cînd clasele descriu obiecte care lucrează şi cu spaţiu extins este necesară folosirea unui constructor de copiere explicit, care să realizeze, pe lîngă copierea atributelor, alocarea de spaţiu extins propriu pentru obiectul nou şi copierea spaţiului extins al obiectului sursă în acest spaţiu nou.

Constructorii nu pot fi apelaţi în mod explicit în program. Singura situaţie în care poate să apară un astfel de apel este la declararea unui obiect, în modul următor: Complex a = Complex(2,5); În cazul în care o clasă are ca membri obiecte, la declararea unui obiect al clasei se apelează întîi constructorii pentru obiectele membru şi apoi constructorul noului obiect. 4.4. Destructori Destructorii sînt metode speciale, asemănătoare constructorilor, care au rol invers: încheierea ciclului de viaţă al obiectelor. Aşa cum pentru fiecare clasă se generează un constructor implicit (dacă nu a fost prevăzut unul explicit), compilatorul generează şi un destructor implicit, dacă nu a fost prevăzut unul explicit. Spre deosebire de constructori, o clasă poate avea numai un destructor explicit. Ca şi constructorii, destructorii nu întorc nici un rezultat. Numele destructorului este numele clasei precedat de caracterul ~ (tilda). Destructorii nu au parametri. Exemplu class Complex { float p_reala, p_imaginara;

public ~Complex(); }

Complex::~Complex() { //descrierea corpului destructorului } Pentru clasa Complex destructorul nu are nimic de făcut şi nu e necesară descrierea unui destructor explicit. Acest exemplu urmăreşte doar să arate cum se declară un destructor explicit.

Page 77: 94650969 19 ID Program Area Calculatoarelor

* Material didactic pentru ID * 77

Spre deosebire de constructori, destructorii pot fi apelaţi explicit, atunci cînd este necesară ştergerea unui obiect. Apelul se face la fel ca pentru orice altă metodă, în contextul obiectului care trebuie şters:

a.~Complex(); Utilizarea destructorilor este obligatorie atunci cînd se lucrează cu date dinamice, deoarece destructorii impliciţi nu pot elibera spaţiul alocat dinamic. În cazul în care la crearea unui obiect au fost apelaţi mai mulţi constructori, la ştergerea lui se apelează destructorii corespunzători, în ordine inversă. Teste de autoevaluare

14. Explicaţi noţiunea de constructor. 15. Explicaţi noţiunea de destructor. 16. Alegeţi atributele şi metodele pentru o clasă de obiecte care să implementeze entitatea masiv

unidimensional (vector). 17. Alegeţi atributele şi metodele pentru o clasă de obiecte care să implementeze entitatea masiv

bidimensional (matrice). 18. Implementaţi o clasă de obiecte care să descrie noţiunea de stivă, folosind o structură de tip

listă dinamică. 4.5. Funcţii prieten În unele situaţii este nevoie ca funcţii care nu sînt membri ai unei clase să poată accesa atributele protejate ale clasei. În acest scop a fost introdus în C++ conceptul de funcţie prieten. O funcţie prieten nu face parte din clasă, dar poate accesa atributele protejate ale obiectului pe care l-a primit ca parametru. Pentru a specifica o funcţie prieten, în interiorul descrierii clasei se scrie prototipul funcţiei prieten, prefixat cu cuvîntul rezervat friend. Întrucît funcţia prieten nu este membru al clasei, în interiorul său nu este definit pointerul this, ceea ce face ca funcţia prieten să nu poată accesa direct atributele obiectului. De aceea este necesar ca obiectul să fie parametru al funcţiei prieten. Ca urmare, o funcţie prieten are un parametru în plus faţă de o metodă. Modificatorii de acces nu au nici o influenţă asupra funcţiilor prieten, de aceea ele pot fi specificate oriunde în cadrul descrierii clasei. Exemplu

În acest exemplu funcţia Afişează va fi scoasă în afara clasei Complex şi va fi declarată ca funcţie prieten. class Complex { float p_reala,p_imaginara; public: friend void Afiseaza(Complex x); Complex(float a=0,float b=0); Complex(Complex &x); }; void Afiseaza(Complex x) { printf("\n%5.2f%ci*%5.2f\n",x.p_reala,x.p_imaginara>=0?'+':'-',

x.p_imaginara>=0?x.p_imaginara:-x.p_imaginara); } void main()

Page 78: 94650969 19 ID Program Area Calculatoarelor

Programarea calculatoarelor 78

{ Complex a(1,2); Afiseaza(a); } 4.6. Derivarea claselor Derivarea claselor este legată de implementarea conceptului de moştenire. În limbajul C++ este permisă moştenirea multiplă. Pentru a defini o clasă fiu ca fiind derivată dintr-o clasă părinte (sau mai multe clase părinte), se procedează astfel: class nume_clasa_fiu : lista_clase_părinte { descriere membri noi ai clasei fiu};

În lista claselor părinte se specifică numele claselor părinte, separate prin virgulă şi, eventual, precedate de modificatori de acces – se pot folosi modificatorii public sau private. Aceşti modificatori de acces definesc nivelul de protecţie a membrilor clasei părinte în clasa fiu, conform tabelului următor:

Nivel acces în clasa părinte

Modificator de acces în lista claselor

părinte

Nivel acces în clasa fiu

public inaccesibil private private inaccesibil public protected protected private private public public public private private

Pentru fiecare clasă părinte se poate specifica un modificator de acces. Dacă nu se specifică

nici un modificator de acces atunci, implicit, se consideră modificatorul public.

Se observă că o clasă derivată are acces la membrii clasei părinte care au fost definiţi ca fiind publici sau protejaţi şi nu are acces la membrii privaţi. Prin derivare se construiesc ierarhii de clase, deci din clasa fiu se pot deriva alte clase noi. Dacă la derivare s-a folosit modificatorul de acces private, atunci toţi membrii clasei părinte vor deveni privaţi în clasa fiu şi o derivare în continuare nu mai este posibilă, ei fiind inaccesibili pentru orice altă clasă derivată din clasa fiu. Întrucît ierarhiile de clase nu sînt definitive ci oferă posibilitatea extinderii prin adăugarea de noi clase derivate, se preferă ca la derivare să se folosească modificatorul de acces public. De aceea aceste este modificatorul explicit. Exemplu

Fie clasa Punct care implementează entitatea punct geometric. Aceasta are atributele x şi y, care reprezintă coordonatele punctului în plan. Este inclusă o singură metodă, care desenează punctul pe ecran (această metodă nu va implementată în acest exemplu). class punct { int x,y; public: void deseneaza(); }; void punct::deseneaza() { //corpul nu este descris in acest exemplu }

Page 79: 94650969 19 ID Program Area Calculatoarelor

* Material didactic pentru ID * 79

Fie clasa Cerc care implementează entitatea geometrică cerc. Aceasta este descrisă prin coordonatele centrului cercului şi raza sa. Ca urmare clasa Cerc poate fi derivată din clasa Punct, adăugînd un nou atribut (raza) şi o nouă metodă, pentru desenarea cercului. class cerc: punct { float raza; public: void deseneaza(); }; void cerc::deseneaza() { //corpul nu este descris in acest exemplu }

Clasa cerc o să aibă ca membri atributele x, y şi raza şi metodele desenează (moştenită de la clasa Punct, care va fi folosită pentru desenarea centrului cercului) şi desenează (nou definită, care va fi folosită pentru desenarea cercului). În lucrul cu ierarhii de clase se pune problema compatibilităţii tipurilor de date (clase) în cadrul atribuirilor şi a conversiilor tipurilor de date. Ca principiu, un obiect al unei clase părinte poate primi ca valoare un obiect al unei clase derivate. Acelaşi principiu este valabil şi în cazul pointerilor către obiecte. Utilizînd exemplul de mai sus şi declaraţiile

punct a, *pp; cerc b, *pc;

sînt corecte atribuirile

pp=&a; pc=&b; a=b; pp=pc; pp=&b;

Nu sînt corecte următoarele atribuiri: pc=&a; b=a; pc=pp; Pot fi realizate atribuiri folosind conversia explicită a tipurilor de date, astfel: pc=(cerc *)pp; pc=(cerc *)&a; 4.6.1.Redefinirea atributelor Este posibil ca o clasă fiu să redefinească atribute moştenite de la clasa părinte (atribute publice sau protejate, întrucît cele private sînt oricum inaccesibile în clasa fiu). În acest caz, clasa fiu va avea două atribute cu acelaşi nume. Implicit, utilizarea numelui atributului respectiv referă atributul redefinit. Pentru a accesa atributul moştenit, trebuie folosit operatorul de rezoluţie, prefixînd numele atributului cu numele clasei părinte.

Page 80: 94650969 19 ID Program Area Calculatoarelor

Programarea calculatoarelor 80

Exemplu Fie o clasă Clasa_parinte care are un atribut a de tip float, atribut protejat, şi o clasă Clasa_fiu

care redefineşte atributul a, de tip double. x este un obiect de tipul Clasa_fiu. class Clasa_parinte { protected: float a; //descrierea restului clasei }; Class Clasa_fiu: public Clasa_parinte { protected: double a; //descrierea restului clasei } Clasa_fiu x; Expresia

x.a referă atributul a al clasei derivate, de tip double. Pentru a accesa atributul a moştenit de la clasa părinte, în cadrul unei metode a obiectului x trebuie folosită expresia Clasa_parinte::a 4.6.2.Redefinirea metodelor La fel ca în cazul atributelor, o clasă fiu poate să redefinească metodele moştenite de la clasa părinte, în cazul în care metoda moştenită nu corespunde necesităţilor. În exemplul anterior, clasa Cerc redefineşte metoda desenează. Dacă a este un obiect de tipul Cerc, atunci apelul a.desenează(); sau desenează(); – efectuat din interiorul clasei Cerc – va lansa în execuţie metoda redefinită, cea descrisă de clasa Cerc, care va desena conturul cercului. Atunci cînd e nevoie să se apeleze metoda moştenită numele acesteia se prefixează cu numele clasei părinte, folosind operatorul de rezoluţie: punct::deseneaza(); Un astfel de apel poate să apară în interiorul metodei desenează a clasei Cerc pentru a desena centrul cercului. 4.6.3.Constructori şi destructori în relaţia de moştenire Constructorii şi destructorii nu se moştenesc precum alte metode. La crearea unui obiect al unei clase fiu se apelează întîi constructorul clasei părinte şi apoi constructorul clasei fiu. Dacă sînt mai multe clase părinte (moştenire multiplă) se apelează constructorii claselor părinte, în ordinea în care acestea apar în lista claselor părinte. La ştergerea unui obiect al unei clase fiu se apelează destructorii în ordine inversă faţă de constructori: întîi destructorul clasei fiu şi apoi destructorii claselor părinte, în ordine inversă celei în care acestea apar în lista claselor părinte. Pentru a preciza parametrii reali utilizaţi pentru fiecare din constructorii claselor părinte, antetul constructorului clasei derivate are o formă specială: class Clasa_fiu: clasa_p1, clasa_p2, clasa_p3 { //attribute public: Clasa_fiu(); //constructorul clasei fiu }

Page 81: 94650969 19 ID Program Area Calculatoarelor

* Material didactic pentru ID * 81

Clasa_fiu::clasa_fiu(…):clasa_p1(…),clasa_p2(…),clasa_p3(…) { //descrierea corpului constructorului }

Se observă că în descrierea clasei, constructorul se descrie în mod obişnuit, dar ulterior, în antetul constructorului apar apeluri ale constructorilor claselor părinte. Ordinea în care apar aceste apeluri nu are nici o importanţă, deoarece ordinea de apelare este dată de ordinea în care sînt specificate clasele părinte.

Dacă una din clasele părinte nu are constructor, atunci nu o să apară un apel corespunzător în antetul constructorului clasei fiu, pentru ea apelîndu-se automat constructorul implicit.

Dacă nici una dintre clase nu are constructor explicit, atunci se folosesc constructorii impliciţi pentru toate clasele.

O situaţie deosebită este aceea în care clasa fiu nu are constructor explicit, dar cel puţin una

din clasele părinte are un constructor explicit. Deoarece în această situaţie nu se pot descrie explicit parametrii pentru apelarea constructorilor claselor părinte, aceşti constructori trebuie să aibă valori implicite pentru toţi parametrii. 4.6.4.Clase virtuale În cazul moştenirii multiple pot să apară situaţii în care o clasă derivate moşteneşte un atribut (sau mai multe) de mai multe ori, prin intermediul mai multor linii de moştenire. Aceste situaţii produc ambiguităţi legate de referirea atributului respectiv. Limbajul C++ oferă un mecanism simplu prin care să se revină astfel de situaţii, prin utilizarea claselor virtuale. Fie următoare ierarhie, în care clasa cf este derivată din clasele cp1, cp2 şi cp3, toate acestea fiind la rîndul lor derivate din clasa cb. Clasa de la baza ierarhiei, cb are un atribut x. Acest atribut va fi moştenit în clasele cp1, cp2, cp3. Clasa cf va moşteni 3 exemplare ale atributului x.

Fig.4.5. Exemplu de moştenire multiplă

Pentru a evita această situaţie, clasa cb poate fi declarată ca virtuală la descrierea claselor cp1,

cp2 şi cp3, astfel: class cp1: virtual public cb { }; class cp2: virtual public cb { }; class cp3: virtual public cb {

Page 82: 94650969 19 ID Program Area Calculatoarelor

Programarea calculatoarelor 82

}; class cf: public cp1, public cp2, public cp3, { };

Considerînd aceste declaraţii, clasa cf moşteneşte o singură dată atributul x, prin intermediul

clasei cp1. Ca principiu, atributul este moştenit prin intermediul clasei care apare prima în lista claselor părinte. În cazul în care în lista claselor părinte apar şi clase virtuale, se apelează întîi constructorii claselor virtuale, în ordinea în care au fost specificate, apoi constructorii claselor nevirtuale, în ordinea în care sînt acestea specificate. 4.6.5.Funcţii virtuale Există situaţii în care nu se poate decide în momentul compilării care este contextul curent în care se apelează o metodă, care a fost redefinită într-o clasă fiu. Astfel de situaţii apar atunci cînd se lucrează cu pointeri. Fie o clasă cp care conţine metoda executa, şi o clasă derivată din ea, numită cf, care redefineşte metoda executa, cu aceeaşi listă de parametri. Fie următoarele declaraţii: cp a; //a este obiect de tipul cp cf b; //b este obiect de tipul cf cp* po; // po este pointer catre clasa cp Pointerul po poate lua ca valoare atît adresa unui obiect de tipul cp cît şi adresa unui obiect de tipul cf. Fie apelul po->executa(); În momentul compilării nu se poate stabili ce metodă să se apeleze, a clasei părinte sau a clasei fiu. În astfel de situaţii compilatorul generează un apel către metoda clasei părinte. Limbajul C++ oferă posibilitatea de a întîrzia decizia pînă la momentul execuţiei. În acest scop metoda clasei părinte se declară ca fiind virtuală prin scrierea cuvîntului rezervat virtual înaintea antetului său. Este suficient ca metoda clasei de bază să fie declarată ca fiind virtuală, în mod automat şi metodele claselor derivate vor fi virtuale. Constructorii, destructorii şi funcţiile inline nu pot fi virtuale. 4.6.6.Clase abstracte

În limbajul C++ este definit conceptul de funcţie virtuală pură. Acest concept este necesar în

cazul ierarhiilor de clase. Există situaţii în care toate clasele ierarhiei trebuie să conţină o anumită metodă, implementarea ei fiind diferită în fiecare clasă derivată. Clasa de la baza ierarhiei este prea generală pentru a putea implementa metodele. În această situaţie în clasa de bază se includ metode virtuale pure. Prezenţa unei astfel de metode obligă toate clasele derivate să o conţină, fie că o redefinesc fie că nu.

O metodă virtuală pură se declară asemănător cu o metodă virtuală, adăugînd la sfîrşitul antetului =0:

virtual tip_rezultat nume_metoda(lista parametri) =0;

Metodele virtuale pure nu au un corp, nefiind implementate.

O clasă care conţine o metodă virtuală pură se numeşte clasă abstractă. O clasă abstractă nu poate fi instanţiată, ea conţinînd metode care nu sînt implementate.

Page 83: 94650969 19 ID Program Area Calculatoarelor

* Material didactic pentru ID * 83

Clasele derivate din clase abstracte pot să redefinească metodele virtuale pure sau nu. Dacă metoda virtuală pură nu este implementată, atunci şi clasa respectivă este clasă abstractă şi nu poate fi instanţiată.

De exemplu, într-o ierarhie de clase care descriu figuri geometrice, fiecare clasă are nevoie de

metode pentru desenarea figurii respective, calculul ariei sau perimetrului. La baza ierarhiei se află o clasă abstractă care include metode virtuale pure pentru aceste operaţii. Clasele derivate, vor implementa metodele conform specificului fiecărei figuri geometrice. Exemplu

Clasa TVector este un exemplu de implementare a entităţii masiv unidimensional (vector) printr-o clasă de obiecte. Exemplul este realizat conform următoarelor specificaţii: • elementele masivului sunt de tip float, • spaţiul pentru elementele masivului este alocat dinamic şi poate fi redimensionat la nevoie (numai

mărirea spaţiului), • sînt implementate operaţiile de bază necesare funcţionării corecte a clasei şi cîteva operaţii

demonstrative; pot fi adăugate şi alte operaţii de prelucrare a vectorilor. #define TIP float //tipul elementelor din vector class TVector { int max, n; //dimensiune maxima si curenta TIP* x; //adresa zonei in care se afla elementele public: //metode de acces int Get_max() {return max;} int Get_n() {return n;} TIP Get_elem(int i) {return x[i];} //constructori TVector(){x=NULL; max=n=0;} TVector(int max) {this->max=max; n=0; x=new TIP[max];} TVector(int n, TIP* a); //constructor de copiere TVector(TVector &b); //destructor ~TVector(); //metode TIP minim(); void realocare(int newmax); void sortare(); void interclasare(TVector &a, TVector &b); TVector* MinPoz(TIP* min); void adauga(TIP a); void insereaza(int p, TIP a); TIP extrage(int p, int *er); }; //constructor cu initializare, //valorile elementelor se afla deja in memorie TVector::TVector(int n, TIP* a) { int i; x=new TIP[n]; this->n=max=n; for(i=0;i<n;i++) x[i]=a[i]; } //destructor TVector::~TVector()

Page 84: 94650969 19 ID Program Area Calculatoarelor

Programarea calculatoarelor 84

{ if(x) delete x; x=NULL; max=n=0; } //constructor de copiere TVector::TVector(TVector &b) { int i; max=b.Get_max(); n=b.Get_n(); if(max>0) x=new TIP[max]; else x=NULL; for(i=0;i<n;i++) x[i]=b.Get_elem(i); } //metoda pentru determinarea elementului minim din obiect TIP TVector::minim() { int i; TIP min; min=x[0]; for(i=1;i<n;i++) if(min>x[i]) min=x[i]; return min; } //metoda pentru marirea spatiului disponibil //pentru elementele obiectului void TVector::realocare(int newmax) { int i; TIP* a; if(newmax>max) { a=new TIP[newmax]; max=newmax; if(x) { for(i=0;i<n;i++) a[i]=x[i]; delete x; } this->x=a; } } //metoda pentru sortarea elementelor obiectului void TVector::sortare() { int i,m; m=0; TIP aux; while(!m) { m=1; for(i=0;i<n-1;i++) if(x[i]>x[i+1]) { aux=x[i]; x[i]=x[i+1]; x[i+1]=aux; m=0; } } }

Page 85: 94650969 19 ID Program Area Calculatoarelor

* Material didactic pentru ID * 85

void TVector::interclasare(TVector &a, TVector &b) { int i,j, l1, l2; l1=a.Get_n(); l2=b.Get_n(); this->~TVector(); if(l1+l2>0) { this->realocare(l1+l2); i=0; j=0; n=0; while((i<l1)&&(j<l2)) if(a.Get_elem(i)<b.Get_elem(j)) x[n++]=a.Get_elem(i++); else x[n++]=b.Get_elem(j++); while(i<l1) x[n++]=a.Get_elem(i++); while(j<l2) x[n++]=b.Get_elem(j++); } } //metoda pentru determinarea minimului //si a tuturor pozitiilor pe care apare //pozitiile minimului vor forma un nou obiect de tip TVector, //creat dinamic in metoda. TVector* TVector::MinPoz(TIP* min) { TVector *a; int i,l; TIP *b; if(n>0) { b=new TIP[max]; *min=x[0]; l=1; b[0]=0; for(i=1;i<n;i++) if(*min>x[i]) { *min=x[i]; l=1; b[0]=(TIP)i; } else if(*min==x[i]) { b[l]=(TIP)i; l++; } a=new TVector(l,b); } else a=NULL; return a; } //metoda pentru adaugarea unui element nou la sfirsitul vectorului void TVector::adauga(TIP a) { if(n==max) realocare(max+1); x[n++]=a; } //metoda pentru inserarea unui element pe o anumita pozitie void TVector::insereaza(int p, TIP a) { int i; if(n==max) realocare(max+1); for(i=n-1;i>=p;i--)

Page 86: 94650969 19 ID Program Area Calculatoarelor

Programarea calculatoarelor 86

x[i+1]=x[i]; x[p]=a; n++; } //metoda pentru extragerea unui element de pe o pozitie data TIP TVector::extrage(int p, int *er) { int i; TIP rez; *er=1; if((p<n)&&(p>=0)) { rez=x[p]; for(i=p;i<n-1;i++) x[i]=x[i+1]; n--; *er=0; } return rez; }

O îmbunătăţire a acestei clase constă în eliminarea dependenţei de tipul elementelor masivului (float în exemplul de mai sus). Descrieţi o metodă prin care să se realizeze acest lucru.

Exemplu

Clasa TFisR reprezintă un exemplu de implementare a entităţii fişier binar organizat relativ printr-o clasă de obiecte. Această clasă poate fi utilizată pentru prelucrarea oricărui fişier organizat relativ deoarece nu depinde de structura articolelor fişierului. Structura logică a articolelor este descrisă în aplicaţia care utilizează clasa pentru gestiunea unui fişier.

Obiectul care gestionează fişierul are nevoie de dimensiunea articolului logic (în octeţi). Pentru operaţiile de citire/scriere din memoria internă. adaugă automat indicatorul de stare la începutul articolului logic pentru a forma articolul fizic. class TFisR { FILE* f; //adresa fisierului char nume[50]; //numele extern al fisierului unsigned int dim; //dimensiunea unui articol logic int NrSpatii(); //numar total spatii in fis. crt. int Preformare(int nr); //preformare/extindere fis.crt. public: //constructori TFisR() { f=NULL;nume[0]='\0';dim=0;} //obiect nul TFisR(char* n, unsigned int dim, char m = 'N'); ~TFisR(void); //destructor, reseteaza obiectul //metode de acces char* Nume(){return nume;} unsigned int Dim(){return dim;} int Deschis() {return f==NULL?0:1;} int Deschide(char* n, unsigned int dim, char m = 'N'); int Inchide(); int Pozitia(); int Sfirsit(){return Pozitia()<NrSpatii()?0:1;} int Pozitionare(int p); int CitesteUrmatorul(void* adresa); int CitestePozitia(int poz, void* adresa); int ScriePozitia(void* adresa, int poz);

Page 87: 94650969 19 ID Program Area Calculatoarelor

* Material didactic pentru ID * 87

int Sterge(int poz); }; //constructor, deschide un fisier //I: nume fisier, dimensiune articol, mod deschidere (implicit N - fisier nou) TFisR::TFisR(char* n, unsigned int dim, char m) { if(toupper(m)=='N') f=fopen(n,"wb+"); else f=fopen(n,"rb+"); if(!f) nume[0]='\0'; else { strcpy(nume, n); this->dim=dim; //Pozitionare(0); } } //destructor TFisR::~TFisR() { if(f) { fclose(f); f=NULL; nume[0]='\0'; dim=0; } } //deschidere fisier nou, dupa ce obiectul a fost deja creat //I: nume fisier, dimensiune articol, mod deschidere (implicit N - fisier nou) //E: cod succes 0 - fisier inchis, 1 - fisier deschis int TFisR::Deschide(char* n, unsigned int dim, char m) { if(f) Inchide(); if(toupper(m)=='N') f=fopen(n,"wb+"); else f=fopen(n,"rb+"); if(!f) nume[0]='\0'; else { strcpy(nume, n); this->dim=dim; //Pozitionare(0); } return Deschis(); } //inchidere fisier curent //I: - //E: cod eroare 0 - fisier inchis, 1 - fisier deschis int TFisR::Inchide() { if(f) { fclose(f); f=NULL; nume[0]='\0'; dim=0; } return Deschis(); } //numar total spatii in fisier (nr. articole fizice)

Page 88: 94650969 19 ID Program Area Calculatoarelor

Programarea calculatoarelor 88

//I: - //E: nr. articole, 0 daca fisierul e inchis int TFisR::NrSpatii() { long p; int nr; nr=0; if(f) { p=ftell(f); fseek(f,0,SEEK_END); nr=ftell(f)/(dim+1); fseek(f,p,SEEK_SET); } return nr; } //pozitia curenta in fisier //I: - //E: pozitia curenta, in nr. de articole, 0 daca fisierul e inchis int TFisR::Pozitia() { int nr; nr=0; if(f) nr=ftell(f)/(dim+1); return nr; } //preformare fisier //I: numar de articole pentru preformare/extindere //E: cod succes, 1 - succes, 0 - fisierul era inchis int TFisR::Preformare(int nr) { int i,er; char *art; er=1; if(f) { fseek(f,0,SEEK_END); art=(char*)malloc(dim+1); art[0]=0; for(i=0;i<nr;i++) fwrite(art,dim+1,1,f); er=0; free(art); } return er; } //pozitionare //I: pozitia dorita, in nr. relativ articol //E: cod eroare, 0 - succes, 1 - pozitia ceruta e prea mare, 2 - fisierul era inchis int TFisR::Pozitionare(int p) { int er; er=2; if(f) if(p<NrSpatii()) { fseek(f,p*(dim+1),SEEK_SET); er=0; } else er=1; return er; }

Page 89: 94650969 19 ID Program Area Calculatoarelor

* Material didactic pentru ID * 89

//citire in acces secvential, urmatorul articol //I: adresa la care se depune articolul citit //E: cod eroare, 0 - articolul a fost citit, 1 - fisierul era inchis, 2 - sfirsit fisier int TFisR::CitesteUrmatorul(void* adresa) { char* art; int er=1; if(f) { art=(char*)malloc(dim+1); while(!Sfirsit() && er) { fread(art,dim+1,1,f); if(art[0]==1) { er=0; memcpy(adresa,art+1,dim); } } if(er) er=2; free(art); } return er; } //citire in acces direct //I: cheia articolului (nr. relativ), adresa unde se depune articolul //E: cod eroare, 0 - articolul a fost citit, 1 - fisierul era inchis sau pozitie prea mare, 2 - cheie invalida int TFisR::CitestePozitia(int poz, void* adresa) { char* art; int er; er=Pozitionare(poz); if(!er) { art=(char*)malloc(dim+1); fread(art,dim+1,1,f); if(art[0]==0) er=2; else { er=0; memcpy(adresa, art+1, dim); } free(art); } return er; } //scriere articol in acces direct //I: adresa articolului care trebuie scris, cheia articolului //E: cod eroare, 0 - scriere reusita, 1 - fisierul era inchis, 2 - cheie invalida (deja e un articol acolo) int TFisR::ScriePozitia(void* adresa, int poz) { char* art; int n,er=1; if(f) { n=NrSpatii(); if(poz>=n) Preformare(poz-n+1); art=(char*)malloc(dim+1); Pozitionare(poz); fread(art,dim+1,1,f); if(art[0]==1) er=2; else { er=0;

Page 90: 94650969 19 ID Program Area Calculatoarelor

Programarea calculatoarelor 90

memcpy(art+1,adresa,dim); art[0]=1; Pozitionare(poz); fwrite(art,dim+1,1,f); } free(art); } return er; } //sterge articolul cu cheia data //I: cheia articolului de sters //E: cod eroare, 0 - stergere reusita, 1 - fisierul era inchis sau pozitie prea mare, 2 - cheie invalida (spatiu gol), int TFisR::Sterge(int poz) { char* art; int er; er=Pozitionare(poz); if(!er) { art=(char*)malloc(dim+1); fread(art,dim+1,1,f); if(art[0]==0) er=2; else { er=0; art[0]=0; Pozitionare(poz); fwrite(art,dim+1,1,f); } free(art); } return er; }

Exemplul următor demonstrează utilizarea clasei TFisR pentru gestionarea unui fişier organizat relativ. Fişierul ales pentru exemplificare conţine date despre studenţi, cheia relativă fiind numărul matricol asociat fiecărui student. Sînt prezentate cîteva din operaţiile uzuale asupra unui astfel de fişier. #include <stdio.h> #include <conio.h> #include <malloc.h> #include <ctype.h> #include "TFisR.h" #define MAXNOTE 15 typedef struct { char nume[30]; int grupa; int an; char note[MAXNOTE]; int nrm; }STUDENT; void main() { TFisR f; STUDENT s; char numef[30],r; int i,nr,er;

Page 91: 94650969 19 ID Program Area Calculatoarelor

* Material didactic pentru ID * 91

//creare si populare fisier nou printf("\n\nCreare si populare fisier nou"); printf("\nNume fisier:"); gets(numef); f.Deschide(numef,sizeof(STUDENT)); printf("\nNumar matricol:"); scanf("%d",&nr); while(!feof(stdin)) { er=f.CitestePozitia(nr,&s); if(!er) printf("\nNr. matricol invalid, alocat deja altui student"); else { printf("Nume:"); fflush(stdin); gets(s.nume); printf("Grupa:"); scanf("%d",&s.grupa); printf("An:"); scanf("%d",&s.an); printf("Note:\n"); for(i=0;i<MAXNOTE;i++) { printf("Nota %2d: ",i+1); scanf("%d",&s.note[i]); } s.nrm=nr; er=f.ScriePozitia(&s,nr); } printf("\nNumar matricol(^Z):"); scanf("%d",&nr); } f.Inchide(); //vizualizare fisier in acces secvential (lista) printf("\n\nVizualizare fisier in acces secvential"); printf("\nNume fisier:"); gets(numef); f.Deschide(numef,sizeof(STUDENT),'e'); printf("\n%3s %30s An Grupa Note","Nrm","Nume si prenume"); while(!f.Sfirsit()) { er=f.CitesteUrmatorul(&s); if(!er) { printf("\n%3d %30s %2d %5d ",s.nrm,s.nume,s.an,s.grupa); for(i=0;i<MAXNOTE;i++) printf("%2d",s.note[i]); } } printf("\n\n"); f.Inchide(); //vizualizare in acces direct printf("\n\nVizualizare date in acces direct"); printf("\nNume fisier:");

Page 92: 94650969 19 ID Program Area Calculatoarelor

Programarea calculatoarelor 92

gets(numef); f.Deschide(numef,sizeof(STUDENT),'e'); printf("\nNumar matricol:"); scanf("%d",&nr); while(!feof(stdin)) { er=f.CitestePozitia(nr,&s); if(er) printf("\nNr. matricol invalid (nealocat)%s",er==1?" - mai mare decit dim. fis.":""); else { printf("\n%3s %30s An Grupa Note","Nrm","Nume si prenume"); printf("\n%3d %30s %2d %5d ",s.nrm,s.nume,s.an,s.grupa); for(i=0;i<MAXNOTE;i++) printf("%2d",s.note[i]); } printf("\nNumar matricol(^Z):"); scanf("%d",&nr); } f.Inchide(); //stergere articole din fisier printf("\n\Stergere date in acces direct"); printf("\nNume fisier:"); gets(numef); f.Deschide(numef,sizeof(STUDENT),'e'); printf("\nNumar matricol:"); scanf("%d",&nr); while(!feof(stdin)) { er=f.CitestePozitia(nr,&s); if(er) printf("\nNr. matricol invalid (nealocat)%s",er==1?" - mai mare decit dim. fis.":""); else { printf("\n%3s %30s An Grupa Note","Nrm","Nume si prenume"); printf("\n%3d %30s %2d %5d ",s.nrm,s.nume,s.an,s.grupa); for(i=0;i<MAXNOTE;i++) printf("%2d",s.note[i]); printf("\nConfirmare stergere (D/N - orice): "); r=getche(); if(toupper(r)=='D') f.Sterge(nr); } printf("\nNumar matricol(^Z):"); scanf("%d",&nr); } f.Inchide(); //eliminare obiect fisier f.~TFisR(); //vizualizare fisier in acces secvential (lista) printf("\n\nVizualizare fisier in acces secvential, folosind un obiect dinamic"); printf("\nNume fisier:"); gets(numef);

Page 93: 94650969 19 ID Program Area Calculatoarelor

* Material didactic pentru ID * 93

TFisR* g; g=new TFisR(numef,sizeof(STUDENT),'e'); printf("\n%3s %30s An Grupa Note","Nrm","Nume si prenume"); while(!(g->Sfirsit())) { er=g->CitesteUrmatorul(&s); if(!er) { printf("\n%3d %30s %2d %5d ",s.nrm,s.nume,s.an,s.grupa); for(i=0;i<MAXNOTE;i++) printf("%2d",s.note[i]); } } printf("\n\n"); g->Inchide(); delete g; }

1. Extindeţi clasa TFisR prin adăugarea de metode care implementează şi alte operaţii cu fişiere organizate relativ.

2. Definiţi o problemă de gestiune a datelor care se bazează pe un fişier organizat relativ. Utilizînd clasa TFisR, scrieţi un program multifuncţional pentru gestiunea acestui fişier.

Teme:

1. Implementaţi o clasă de obiecte care să descrie entitatea masiv bidimensional (matrice). Scrieţi un program care să demonstreze modul de lucru cu această clasă.

2. Implementaţi o clasă de obiecte care să descrie entitatea fişier organizat indexat. Scrieţi un program care să demonstreze modul de lucru cu această clasă.

Rezumat În această unitate de învăţare au fost studiate elemente de bază ale programării orientate obiect. În urma studiului, studenţii au dobîndit cunoştinţele teoretice de bază ale programării orientate obiecte: obiect, clasă, atribut, metodă, încapsulare, polimorfism, moştenire.

De asemenea, au fost studiate elemente de programare orientată obiect specifice limbajului C++: definirea claselor, modificatori de acces, constructor, destructor, funcţii prieten, derivarea claselor.

Exemplele prezentate şi temele propuse urmăresc dezvoltarea abilităţilor practice necesare rezolvării problemelor de programare orientată obiect, pregătind studenţii pentru trecerea la următoarea etapă de instruire pentru rezolvarea problemelor de informatică economică. 4.7. Răspunsuri şi comentarii la testele de autoevaluare 18. Să se implementeze clasa Stivă dinamică. O listă dinamică este formată din noduri, deci putem defini întîi clasa Nod, urmînd a folosi tipul Nod pentru a descrie clasa Stivă. Pentru acest exemplu, datele memorate în nodurile stivei sînt de tip float. Exempul de mai jos conţine şi o funcţie main, cu rolu lde a demonstra modul de utilizare a clasei.

Page 94: 94650969 19 ID Program Area Calculatoarelor

Programarea calculatoarelor 94

#include <stdio.h> typedef float TIP_INFO; class Nod { TIP_INFO info; Nod* next; public: float GetInfo(); Nod* GetNext(); Nod(float a, Nod* n); }; float Nod::GetInfo() { return info; } Nod* Nod::GetNext() { return next; } Nod::Nod(float a, Nod* n) { info=a; next=n; } class Stiva { Nod* Cap; public: Stiva(); Stiva(float a); ~Stiva(); void Push(float a); float Pop(); int Empty(); void Afiseaza(); }; void Stiva::Afiseaza() { Nod* x; x=Cap; while(x) { printf("%5.2f ",x->GetInfo()); x=x->GetNext(); } } Stiva::~Stiva() { Nod* x; while(Cap) { x=Cap; Cap=Cap->GetNext(); delete x; } } float Stiva::Pop() { float x; Nod* y; y=Cap; x=Cap->GetInfo(); Cap=Cap->GetNext(); delete y; return x; } void Stiva::Push(float a) { Cap=new Nod(a,Cap); } int Stiva::Empty()

Page 95: 94650969 19 ID Program Area Calculatoarelor

* Material didactic pentru ID * 95

{ return Cap?0:1; } Stiva::Stiva(float a) { Cap= new Nod(a,NULL); } Stiva::Stiva() { Cap=NULL; } void main() { Stiva s; int i; float x; if(s.Empty()) printf("\nStiva este goala"); else printf("\nStiva contine date"); for(i=0;i<10;i++) s.Push((float)i); s.Afiseaza(); x=s.Pop(); s.Afiseaza(); if(s.Empty()) printf("\nStiva este goala"); else printf("\nStiva contine date"); }

Clasa Nod conţine atributele info (informaţia utilă din nod) şi next iar ca metode un constructor care iniţializează atributele obiectului şi metode accesorii pentru accesarea valorilor atributelor.

Clasa Stiva conţine un singur atribut, Cap care are ca valoare adresa primului nod al stivei (vîrful stivei). Constructorii clasei asigură crearea unei stive vide sau a unei stive cu un element. Metodele asigură adăugarea unei informaţii în stivă, respectiv extragerea unei informaţii. Metoda Afiseaza asigură afişarea pe ecran a informaţiilor din stivă. Bibliografie 1. I. Gh. Roşca, B. Ghilic-Micu, C. Cocianu, M. Stoica, C. Uscatu, M. Mircea − Programarea

calculatoarelor. Algoritmi în programare, Ed. ASE Bucureşti, 2007 2. I. Gh. Roşca, B. Ghilic-Micu, C. Cocianu, M. Stoica, C. Uscatu − Programarea

calculatoarelor. Ştiinţa învăţării unui limbaj de programare. Teorie şi aplicaţii, Ed. ASE Bucureşti, 2003

3. Liviu Negrescu − Limbajele C şi C++ pentru începători, vol. I, II, Ed. Microinformatica, Cluj-Napoca, 1994

Page 96: 94650969 19 ID Program Area Calculatoarelor

Programarea calculatoarelor 96

Bibliografie 1. I. Gh. Roşca, B. Ghilic-Micu, C. Cocianu, M. Stoica, C. Uscatu, M. Mircea − Programarea

calculatoarelor. Algoritmi în programare, Ed. ASE Bucureşti, 2007 2. I. Gh. Roşca, B. Ghilic-Micu, C. Cocianu, M. Stoica, C. Uscatu − Programarea

calculatoarelor. Ştiinţa învăţării unui limbaj de programare. Teorie şi aplicaţii, Ed. ASE Bucureşti, 2003

3. Liviu Negrescu − Limbajele C şi C++ pentru începători, vol. I, II, Ed. Microinformatica, Cluj-Napoca, 1994