Sunteți pe pagina 1din 318

Vlad Hutanu Tudor Sorin

'

INFORMATICĂ

(filiera teoretică, profilul real, specializarea


matematică-informatică) şi
(filiera vocaţională, profil militar MApN, specializarea
matematică-informatică)

Ciclul superior al liceului,


clasa a XI-a

Editura L&S Soft


Bucureşti
Copyright 2006 © L&S SOFT
Toate drepturile asupra acestei lucrări aparţin editurii L&S SOFT.
Reproducerea integrală sau parţială a textului din această carte este posibilă
doar cu acordul în scris al editurii L&S SOFT.

Manualul a fost aprobat prin Ordinul ministrului Educaţie i şi Cercetării


nr. 4446 din 19.06.2006 in urma evaluării calitative organizate de către
Consiliul Naţional pentru Evaluarea şi Difuzarea Manualelor şi este
realizat in conformitate cu programa analitică aprobată prin Ordin al
ministrului Educaţiei şi Cercetării nr. 3252 din 13.02.2006.

Referenţi ştiinţifici :

Prof. Dr. Victor Mitrana, Facultatea de Matematică , Universitatea Bucureşti


Prof. grad 1Valiana Petrişor, Colegiul Naţional Bilingv George Coşbuc

Tiparul executat la S .C . LUM INATIPO s.r.l.


Str. Luigi Galvani nr. 20 bis, sector 2, Bucureşti

Anul tipăririi: 2006

Descrierea CIP a Bibliotecii Naţionale a României


HUTANU, VLAD
Info rma tică : manual pentru ciclul superior a l liceului : clasa a XI-
a - (filiera teoretică, profilul real, specializarea matematică-informatică)
şi (filiera vocaţională, profil militar MApN, specializar·ea matematică­
informatică) 1 Vlad Huta nu, Tudor Sorin. - B u cu reşti : Editura L & S Soft,
2006
ISBN (10) 973-88037-1-3; ISBN (13) 978-973-88037-1-8

l. Tudor. Sorin

004(075.35)

Editura L&S SOFf: .1:


Adresa: Str. Stânjeneilor nr. 8, bl. 29, se. A, et. 1, apt. 12, Sector 4, Bucureşti;
Tei./Fax: 031-l 05.62.84;
Mobil: 0722-530.390; 0722-57.37 .O 1;
E-mail: tsorin@ls-infomat.ro;
Web Site: www.ls-infomat.ro.
Cuprins
Capitolul 1. Tablouri .................................... . .... ... ...................... 7
1.1. Noţiunea de tablou ...................... ....................................... ..... ..... ...... 7
1.2. Cum citim şi cum afişăm un tablou bidimensional? ............................. ....... 8
1.3. Aplicaţii cu tablouri bidimensionale ....................................... ................. 10
Probleme propuse ...... ....................................... ...................................... 16
Răspunsurile la testele grilă .... .. ................................ ................................. 19

Capitolul 2. Subprograme ............... .. ............. ............... ............ 20


2.1. Noţiunea de subprogram ....................................... .. .......................... 20
2.2. Subprograme în Pascal. .................................................................................. 22
2.2.1. Un exemplu de utilizare a funcţiilor..................................................... 22
2.2.2. Un exemplu de utilizare a procedurilor............................................... 24
2.2.3. Structura unui subprogram ................................................................. 25
2.2.3.1. Structura subprogramelor de tip funcţie .............................. 25
2.2.3.2. Structura subprogramelor de tip procedură ........................ 26
2.2.4. Definirea şi declararea unui subprogram ............................................ 27
2.2.5. Apelul subprogramelor........................................................................ 30
2.2.5.1. Apelul funcţiilor.................................................................... 30
2.2.5.2. Apelul procedurilor .............................................................. 31
2.2.5.3. Transmiterea parametrilor la apel....................................... 31
2.2.5.4. Cum memorează subprogramele parametrii tri mişi?.......... 33
2.2.5.5. Transmiterea parametrilor prin valoare ............................... 33
2.2.5.6. Transmiterea parametrilor prin referinţă.............................. 35
2.2.6. Variabile locale şi globale................................................................... 36
2.2.7. Greşeli frecvente ................................................................................. 38
2.2.8. Unităţi de program ............................................................................... 39
2.3. Subprograme în C++ ....................................................................................... 42
2.3.1. Exemple de utilizare a funcţiilor .......................................................... 42
2.3.2. Structura unei funcţii. .......................................................................... 44
2.3.3. Declararea variabilelor ......... ............................................................... 46
2.3.4. Transmiterea parametrilor.................................................................. 49
2.3.5. Definirea şi declararea unui subprogram ............................................ 53
2.4. Aplicaţii care folosesc subprograme............... ................................................. 55
Probleme propuse ................ ............................ ......... .......................... .... 62
Răspunsuri. .. ................... ... .................................... .. .. ............................ 72

Capitolul 3. Şirur i de ca ractere ........... ..... .... ..................... .......... 73


3.1. Gene ral ităţi......... .... .... ........................ .... ........................... ..... ........ 73
3.2. Şi ruri de caractere in Pascal. ............. .... ............... ... .. ............ .. ............ 74
3.2.1 . Noţiuni introductive ............................................................................. 74
3.2.2. Concatenarea şi rurilor..... ................... ................................................ 76
4 Cuprins

3.2.3. Compararea şirurilor........................................................................... 77


3.2.4. Lungimea şiruri lor de caractere.......................................................... 79
3.2.5. Subşiruri. ............................................................................................. 80
3.2.6. Conversii de la şiruri la valori numerice şi invers ................................ 84
3.2.7. Citirea şi scrierea datelor de tip String din şi în fişiere text... .............. 88
3.3. Şiruri de caractere în C++ ................................................................... 89
3.3.1. Generalităti. ....................................................................................... 89
3.3.2. Citirea şi scrierea şirurilor de caractere .............................................. 89
3.3.3. Tipul char*.......... ... ........................... .. ... .. .. ..... ... .. .. ... .... ...... .... .. .. . 92
3.3.4. Lungimea unui şir de caractere .......................................................... 93
3.3.5. Copierea şi concatenarea şirurilor de caractere................................. 94
3.3.6. Căutarea unui caracter într-un Şir....................................................... 95
3.3.7. Compararea şirurilor. .......................................................................... 97
3.3.8. Subşiruri.. ............................................................................................ 99
3.3.9. Alte funcţii utile în prelucrarea şirurilor.............................................. 101
3.3.1 O. Conversia şirurilor în valori numerice şi invers ................................ 104
3.3.11. Citirea şi scrierea şirurilor de caractere din şi în fişiere text... ......... 108
3.3.1 1.1. Operaţia de citire ............................................................. 108
3.3.11.2. Operaţia de scriere .......................................................... 109
3.3.12. O modalitate de conversie de la şir la alt tip .................................... 109
Probleme propuse ..................................... ..... ........... ............................ 11 O

Capitolul 4. Structuri de date neomogene ................................. 112


4.1. N oţi uni introductive ........................................................ .. ................ 112
4.2. Structuri ne omogene în Pascal. ................. ............................ ...... ....... 112
4.2.1 . Tipul Record..... ................................................................................ 112
4.2.2. Accesul simplificat la câmpuri ........................................................... 114
4.2.3. Înregistrări imbricate................... ....................................................... 115
4.2.4. Vectori de înreg istrări. ................. ...................................................... 115
4.2.5. Înregistrare cu variante...................................................................... 116
4.3. Structuri neomogene în C++ ...... ................................. ................... .... 118
4.3.1. Tipul struct.... .................................................................................... 118
4.3.2. lnregistrări imbricate .......................................................................... 120
4.3.3. înregistrări cu structură variabilă ....................................................... 121
Probleme propuse .. .................... ....................................... ... ................. 123

Capitolul 5. Structuri de date ................................................... 124


5.1. Conceptul de structură de date ........ ... ........................ .................. ...... 124
5.2. Structura de tip listă liniară .................................................................126
5.2.1. Prezentarea structurii. ....................................................................... 126
5.2.2. liste alocate secvenţial... .................................................................. 127
5.2.3. liste alocate înlănţuit......................................................................... 128
5.2.4. Implementarea alocării înlănţuite prin utilizarea vectorilor................. 129
5.3. Structura de tip stivă ...... .............................. ..................................... 133
5.4. Structura de tip coadă ....... ................... ................... ......... ................. 138
Probleme propuse ...... .......... .. .... .......................................... ................. 138
Răspunsuri ....... ................. ....... ................................................... ......... 140
"'Ual de informatică pentru clasa a Xl-a 5

:Zoitolul 6 . Introducere în recursivitate .... ... ............................. 141


~ • Prezentare generală ........................................ .. ............... ............... 141
:2 Mod ul în care se realizează autoapelul. .............................. ... ............... 141
6.2.1. Realizarea autoapelului în Pascal.. ................................................... 141
6.2.2. Realizarea autoapelului în C++ ......................................................... 142
~ 3 Mecanismul recursivităţii. .................................................................. 143
: ~. Cum gândim un algoritm recursiv? ..... .................................................. 147
~ 5. Aplicaţii recursive ..... ..... .. .. ................................................. .............. 148
6.5.1. Aplicaţii la care se transcrie o formulă recursivă ............................... 148
6.5.2. Aplicaţii la care nu dispunem de o formulă de recurenţă .................. 153
=~oble me propuse .......................................................... .. ..................... 159
~-aicaţii 1 Rezolvări. ............................................................................... 166

cap itolul 7. Metoda Divide et lmpera ............................. ... ...... . 172


- • Prezentare generală .... ........ ......... ... ..... . .......................................... 172
- 2. Ap licaţii .. . ................................................................................. ..... 172
7.2.1 . Valoarea maximă dintr-un vector...................................................... 172
7.2.2. Sortarea prin interclasare................................................................. 174
7.2.3. Sortarea rapidă................................................................................. 176
7.2.4. Turnurile din Hanoi. .......................................................................... 179
7.2.5. Problema tăieturllor.......................................................................... 180
- .3. Fractali ......................................................................................... 183
7.3.1. Elemente de grafică .......................................................................... 183
7.3.1.1. Generalităţi (varianta Pascal) ............................................ 183
7.3.1.2. Generalităţi (varianta C++) ............................................... 185
7.3.1.3. Setarea culorilor şi procesul de desenare (Pascal şi C++)... 186
7.3.2. Curba lui Koch pentru un triunghi echilateral.. .................................. 188
7.3.3. Curba lui Koch pentru un pătrat.. ...................................................... 191
7.3.4. Arborele ............................................................................................. 193
Probleme propuse ........................................................ ......................... 195
Răs punsuri. .. ................ ............ ..... ... ................................................... 196

Capitolul 8. Metoda Backtracking ...... ....................................... 199


8.1. Prezentarea metodei ..... : ............. .................................................... 199
8.1 .1 . Când se utilizează metoda backtracking?........................................ 199
8.1 .2. Principiul ce stă la baza metodei backtracking ................................. 199
8.1.3. O modalitate de implementare a metodei backtracking .................... 201
8.1.4. Problema celor n dame ..................................................................... 204
8.2. Mai puţine linii în programul sursă ..... ....... .................... ... .................... 207
8.3. Cazul în care se cere o singură soluţie. Ex.: problema colorării hărţilor....... 210
8.4. Aplicaţii ale metodei backtracking în combinatorică ................................. 212
8.4.1. O generalizare utilă ........................................................................... 212
8.4.2. Produs cartezian ............................................................................... 213
8.4.3. Generarea tuturor submulţimilor unei mulţimi. .................................. 215
8.4.4. Generarea combinărilor............................................... ..................... 217
6 Cuprins

8.4.5. Generarea aranjamentelor................................................................ 219


8.4.6. Generarea tuturor partiţiilor m~lţimii {1 ,2, ... , n}................................ 221
8.5. Alte tipuri de probleme care se rezolvă prin utilizarea metodei backtracking .... 223
8.5.1 . Generalităti ....................................................................................... 223
8.5.2. Generarea partiţiilor unui număr natural. .. ........................................ 224
8.5.3. Plata unei sume cu bancnote de valori date ..................................... 226
8.5.4. Problema labirintului. ........................................................................ 228
8.5.5. Problema bilei. .................................................................................. 231
8.5.6. Săritura calului. ................................................................................. 233
Probleme propuse .......................... ............. .......................... ................ 235
Indicaţii. .......................... .......................... .......................... ................ 238

Capitolul 9. Grafuri ........................ ........................ .................. 239


9.1. Grafuri neorientate .................... .......................... ........................... .. 239
9.1.1. Introducere ........................................................................................ 239
9.1.2. Definiţia grafului neorientat............................................................... 240
9.1.3. Memorarea grafurilor......................................................................... 242
9.1.4. Graf complet. ..................................................................................... 247
9.1.5. Graf parţial , subgraf.. ........................................................................ 248
9.1.6. Parcurgerea grafurilor neorientate.................................................... 250
9.1.6.1. Parcurgerea tn lăţime (BF- bredth first) ........................... 250
9.1.6.2. Parcurgerea în adâncime (OF - depth first) ...................... 253
9.1.6.3. Estimarea timpului necesar parcurgerii grafurilor.............. 255
9.1. 7. Lanţuri. ......................................<- ......................... . .............. ............. . 255
9.1 .8. Graf conex ......................................................................................... 259
9.1.9. Componente conexe ......................................................................... 260
9.1.1 O. Cicluri. ............................................................................................. 262
9.1.11. Arbori. .............................................................................................. 264
9.1.11.1. Noţiunea de arbore................................ ......................... 264
9.1.11.2. Noţiunea de arbore parţial. .............................................. 266
9.2. Grafuri orientate ........ .................... ............... ............ : ...................... 267
9.2.1. Noţiunea de graf orientat.................................................................. 267
9.2.2. Memorarea grafurilor orientate ......................................................... 270
9.2.3. Graf parţial, subgraf... ....................................................................... 272
9.2.4. Parcurgerea grafurilor. Drumuri. Circuite.......................................... 273
9.2.5. Graf tare conex. Componente tare conexe....................................... 275
Probleme propuse .......... .... .......................... .......................... ............... 278
Răspunsuri .......................... .......................... .......................... ............ 286

Anexa 1. Memento ........................ ........................ .................. 289

Anexa 2. Aplicaţii practice ale grafurilor ........................ ............ 309

Anexa 3. Codul ASCII ........................ ........................ ............... 316


--- Capitolul 1
Tablouri

1.1. Noţiunea de tablou

Anul trecut am studiat tablourile unidimensionale numite uneori, prin analogie


cu matematica, vectori. În acest an studiem tablourile bidimensionale numite, tot
prin analogie cu matematica, matrice .

..~ ..- .."' Definiţia 1.1. Un tablou este o structură omogenă (formată din elemente
~

de acelaşi fel) cu un număr bine determinat de componente. Tabloul se


- · ~:~ ·
' identifică printr-un singur nume, iar componentele sale se identifică prin
intermediul unui sistem de indici.
Alăturat avem reprezentat un tablou. Un
element al acestuia, a 1 .:t, se găseşte pe linia i şi
coloana j. Este esenţial de reţinut faptul că toate
elementele tabloului au acelaşi tip.
A=

am- l,l alll-1,2 alll-l,n

am.l a ",.2 a",~~~

1 . Un magazin oferă spre vânzare n produse. Se doreşte să s_e reţină


vânzările lunare, valorice, din fiecare tip de produs. Putem organiza
datele sub formă de tablou astfel:
• vom numerota cele n produse cu 1 , 2 , .. ., n;
• lunile le vom numerota cu 1, 2, ... , 12;
• elementul a 1 .:l semnifică valoarea incasată din vânzarea produslui i in luna
j a anului - prin urmare, elementele tabloului sunt de tip real.

.$ lntrebări posibile
a) Dacă magazinul vinde 5 produse, câte elemente are tabloul?
b) Care sunt indicii de adresare în tablou pentrt.~ a afla vânzările din produsul
5 în luna septembrie? · -
c) Cum se poate calcula suma încasată în luna mai?
8 Capitolul 1. Tablouri

d) Ştiind că, din punct de vedere economic, anul este împărţit în patru
trimestre şi că fiecare trimestru are trei luni, .cum se poate calcula suma
încasărilor din trimestrul patru al anului?

e) Cum putem determina produsul din vânzarea căruia s-a incasat anual
suma maximă?
f) Care este luna cu cele mai mari încasări?

2. Tot aşa, se poate memora, sub formă de tablou, situaţia la învăţătură a celor m
elevi ai unei clase. Dacă numerotăm elevii cu 1, 2, ... , m şi materiile pe care aceştia
le studiază cu 1, 2, ... , n , atunci a 1 d reprezintă media pe care o are elevul i la
materia j.

J~ Întrebări posibile
a) Dacă în clasă sunt 30 de elevi şi aceştia studiază 8 materii, câte elemente
are matricea?
b) Care este materia la care elevii au cele mai bune rezultate?
c) Care este media generală a elevului i?
d) Care este media generală a elevilor unei clase?
Şirul exemplelor ar putea continua pentru că sunt foarte multe situaţii in care
se utilizează tablouri bidimensionale (matrice).

1.2. Cum citim şi cum afişăm un tablou bidimensional?


În clasa a X-a am învăţat sa lucrăm cu masive unidime.nsionale. Pentru a
adresa un element al unui vector se utilizează un singur indice. In cazul matricelor,
vom utiliza doi indici. Mai jos, puteţi observa cum se declara o matrice cu 1O linii şi
9 coloane, cu elemente de tip intreg:

var astablou1
sau direct
var asarray [1 •• 10,1 •• 9) of real;

+ În Pascal, matricea are liniile 1,2,... ,10 şi coloanele 1,2, ... ,9 şi, de exemplu,
elementul de pe linia a treia şi coloana a patra se adresează prin a [ 3, 4 l .
+ În C++, matricea are liniile 0,1, ... ,9 şi coloanele 0,1, ... ,8 şi, de exemplu,
elementul de pe linia a treia şi coloana a patra se adresează prin a[2] [3] .
Uneori, pentru simplitate, vom folosi liniile şi coloanele matricei Tncepand de
la 1 . ln aceste cond iţii se pierde o linie şi o coloană, fJecare de indice o.
Consideram acest fapt neesenţial.
Manual de Informatică pentru clasa a Xl-a 9

De multe ori nu ljtim câte linii şi câte coloane va


trebui sa aiba tabloul. In acest caz, tabloul se declara
cu un numar maxim de linii şi un numar maxim de
coloane, în aşa fel încât acesta sa corespunda oricarui
set de date de intrare. Evident, într-un astfel de caz
exista o risipă de memorie interna, dar... programul nu
se va termina cu eroare. În figura alăturată, aveţi
reprezentat grafic un tablou cu 8 linii şi 8 coloane, din
care, într-un anumit caz, utilizam numai primele 41inii şi
primele 5 coloane.

Figura 1.1. Exemplu de tablou

Programul care-I utilizeaza va f uncţiona corect daca avem cel mult a linii şi
cel m uit s coloane.

fik::~~··În programul urmator se citeşte şi se afişeaza un tablou. Iniţial se citesc


(L-~ : numarul de linii şi de coloane ale tabloului (m şi n). Observaţi modul in
~.~ care am afişat tabloul - de aşa natura încât şi pe ecran sa arate ca o
matrice, adica fiecare linie să fie scrisa pe un rând.

var m,n,i,j : integer; #include <iostream.h>


a:array[1 •• 10,1 •• 9] of integer; main()
begin { int m,n,i,],a[10] [9];
write ( 1 m• 1 ) ; cout<<•m••; cin>>m;
readln(m) 1 cout<<"n••; cin>>n;
write ( 1 11= 1 )1 for (i=O;i<m;i++)
readln(n); for(]=O;j<n;:J++)
for ! :•1 to m ăo ( cout<< "a[" <<i+1<< 1 , 1

for j:•l ton do <<]+1<< "] .. ";


begin cin»a[i] [] ) ;
write ( 1 A [ 1 , i, 1 , 1 , j, • 1. • ) ; }
readln(a[i,j]); for (i•O;i<m;i++)
and J { for (j•O;j<n;j++)
cout«a[i] [:J]«' 1
;
for i:•1 to m ăo cout<<endl;
bagin )
for j:•l ton do }
write (a{i,j], 1 1
)1
writaln;
end
and.

+ În memorie, tablourile sunt reţinute pe linii. Aceasta inseamna ca la început


este memorată prima linie, apoi a doua, ş .a. m.d.
10 Capitolul 1. Tablouri

1.3. Aplicaţii cu tablouri bidimensioAale

O Aplicaţia 1.1. lnterschimbare de linii. Se citeşte un tablou cum linii şi n


coloane. Se citesc, de asemenea, şi două numere naturale distincte x şi y,
cuprinse între 1 şi m. Se cere să se interschimbe linia x cu linia y. La început vom
afişa tabloul iniţial, apoi pe cel obţinut prin interschlmbarea liniilor x şi y.

Exemplu: Considerăm tabloul de mai jos, cu m.. 3, n•3 şi liniile x .. 2, y=3:

(~ !!]
În urma interschimbării liniilor 2 şi 3 se obţine:

~ Rezolvare. După cum am învăţat, pentru a interschimba conţinutul a două


variabile se utilizează o a treia, de manevră. De această dată , se interschimbă două
liniî. Am fi tenta~ ca manevra să fie un vector cun componente. Cu puţină atenţie ne
dăm seama că putem folosi ca man~vră o singură variabilă de acelaşi tip cu cel al
componentelor de bază ale tabloului. In rest, analizaţi programul de mai jos:

type matrice= array[1 •• 10, #include <iostream.h>


1 •• 101 of byte; main()
var mat: matrice; { int mat [10] [10] ,m,n,i,
m, n, i, j, x, j,x,y, m.an;
y, man : integer; cout<<•m•"; cin>>m;
begin cout<<"n•"; cin>>n;
{-citirea datelor-} for (i•O ; i<m;i++)
write ( •m= ') 1 readln(m); for ( j =O ; j <n;:l++)
write {'n= '); readln(n); {
for i := 1 tom do cout<<"mat["<<i+1<<','
for j := 1 to n do << j+l<<"l=";
begin cin»mat[i] [j];
write ( •mat [', i, •, •,:!, '] •'); };
readln(mat[i,j]); cout<<•x•";
end; ein>>x;
write('x= '); readln(x); cout<< "y•";
write{ 'Y• ') 1 readln(y); cin>>y;
{- tiparesc tabloul initial-) cout<<endl;
for i : • 1 to m do for {i•O;i<m;i++)
begin {
for j := 1 to n do for {jRO;j<n;j++)
write {mat[i,j],' '); cout<<mat[i][j]<<' •;
writeln; cout< <endl;
end; )
Manual de informatică pentru clasa a Xl-a 11

{-interachimbare linii-} for(j•Od<n; j++)


for j:=l ton do { man=mat[x-l]~j];
begin mat [x-1] [j] -mat (y-1] [j] ;
man z• mat[x,j]; mat[y-1] [j]•man;
mat (x, j ] z• mat [y, j ]; )
mat[y,j] 1• man; cout<<endl;
end; for (i•O;i<m;i++)
writeln; ( for (j•O;j<n;:l++)
cout«mat [il [j l « ' '1
{-tiparesc tabloul inversat-) cout<<endl;
for i :s 1 to m do }
begin }
for j := 1 to n do
write(mat[i,j],' ');
writeln;
end;
end.

O Aplicaţia 1.2. Spirala. Se citeşte un tablou cu n linii şi n coloane. Se cere să se


afişeze elementele tabloului în ordinea rezultată prin parcurgerea acestuia în
spirală, Tncepând cu primul element din linia 1, în sensul acelor de ceas.

Exemplu. Fie tabloul:


,

afişate
(CJ]
in ordinea:
Elementele vor fi
1 2 3 6 9 8 7 4 5.

0 Rezolvare. Pentru a putea rezolva problema, privim tabloul ca pe un ansamblu


alcătuit din mai multe dreptunghiuri "concentrice":

/-------"'

Tabloul de mai sus este un ansamblu format din trei dreptunghiuri


"concentrice" - ultimul are un singur element şi nu a putut fi reprezentat. După ce
stabilim numărul de pătrate (cum?), afişăm elementele aflate pe fiecare latură a
fiecărui pătrat Tn ordinea cerută, având grijă ca elementele aflate Tn colţuri să nu fie
afişate de două ori.
12 Capitolul 1. Tablouri

type matrice • array(l •• lO, --


#include <iostream .h>
1 •• 10] of integer; main(}
var mat: -trice; ( int mat[l.O] [10),n,i ,j,k;
n, i, j, k: integer; cout<<•n •"; cin>>n;
begin for(i•1;i< =n;i++}
{-citirea datelor-} for (j•l;j<=n;j ++)
write ( 1 n• 1 } ; readln(n }; ( cout<<"m at[•<<i<< ', 1
for i r• 1 to n do <<j<<•J• •;
for j := 1 to n do cin» mat[i] [j);
begin }
wri te ( 1 - t ( • , i, 1 , 1 , j, 1 l • 1 ) ; for(k=l ; k <=n/2+1;k ++}
readln(m at[i,j]}; { for(i•k;i<~n-k+l;i++}
end; cout<<ma t[k) [i)<<end l;
{-liatare -) for(i • k+l;i<•n -k+l. ; i++)
for k := 1 to n div 2 +· 1 do cout<<m at[i] [n-k+l]<< endl;
bagin for(i•n-k ;i>•k;i-- }
for i r• k to n-k+1 do cout<<ma t[n-k+l] [i] <<endl;
writeln( mat[k,i) }; for(i • n-k;i>• k+l;i--}
for i := k+l to n-k+1 do cout<<ma t[i] (k]<<end l;
writeln(m at[i,n-k+ 1]}1 )
for i :• n-k downto k do }
vriteln(m at[n-k+l ,i)}l
for i r • n-k downto k+1 do
writeln(m at[i,k)}
end;
end.

Ll Aplicaţia 1.3. Pe o tablă cu n linii şi m coloane (n, m numere naturale,


1~n~30 ), sunt plasate pe unele pozi~i jetoane cu litere, conform unui joc corect
de SCRABBLB. Ştiind că vocalele au eate un punct, iar consoanele eate două,
stabiliţi valoarea totală a cuvintelor de pe tablă. Valorije n şi m şi configuraţ
ia tablei
se citesc din fişierul "tabla. txt", conform exemplului (locurile goale de pe tablă
sunt memorate sub forma unor caractere punct).

Exemplu. Pentru datele de intrare:


3 5
D.SAU
ALT:I.
• .A. •
se afişează 2 o
(DA•3, STA•S, ALT:I•6, SAtJ•4 , AI=2).

0 Rezolvare. Căutarea cuvintelor pe tablă . şi cumula rea punctelor lor este, de


obicei, prima soluţie la care ne găndim. Algoritmul corespunzător acestei soluţii pare
tnsă destul de complicat. Simpla parcurgere a matricei de caractere şi adunarea
valorilor corespunzătoare literelor nu este o strategie bună, deoarece se pierde
Manual de Informatică pentru clasa a Xl-a 13

~j ul literelor care apar într-un cuvânt pe orizontala şi unul pe verticala şi care ar


rebui luate de do uă ori ln calcul. De aceea, vom analiza ln pi!JS, pentru fiecare litera,
:iaca ea face parte dintr-un singur cuvânt (are litere vecine doar pe orizontală sau
doar pe verticala) şi se puncteaza obişnuit, sau face parte din doua cuvinte (are litere
.-ecine şi pe orizontală şi pe verticală) şi atunci se punctează dublu.

char
n,m,i,j,dir:~e;
int n,m,i,j,dir,p,pt;
p,pt ~integer;
ifstream f(•tabla.txt•);
f:textl
begin
void main()
aaaign(f, 1 tabla.txt 1 )1 {
reaet(f); f>>n>>m;
readln(f,n,m); for(i•lti<•nti++)
for i ~·l to n do for(:f•1I:f <•ml:f++)
begin f.»a [il [j) 1
for js•l tom do pt=O;
read(f,a[i,:fl)l for(i=1;i<• nii++)
readln(f) for(j•ll:f <-.m;:f++)
endl if (a[i][:fll• 1 , ' )
pt ~·o,
{
for i ~·1 to n do if(a[ill:fl •• 'A'I 1
for j :,.1 to m do 1
if a[i,j]<>',' then a[i) [j] •• E' 11
a[i) [:fl •• 'J:'
begin
a[i) [:fl •• 1 0' 1
if (a[i,j]c'A') or a[iJ[:fl •• 'U')
(a[i.j)•'E') or p=li
(a[i,:f]• 1 l: 1 ) or alsa p•:ZI
(a[i, j)= 1 0') or dir•QI
(a[i,:f]= 1 U') then P ~•1 if (i>l && a[i-1] [:f]l•', 1

elae p: • :Z; 11 i<n &&


dir~•01
a[i+1l [:fl 1• 1 • • )
if (i>1) and (a[i-1,:fl<> 1 , 1
)
dir++;
or if (:f>l &r. a[i) [j-1]1•'. 1
(i<n) aud (a[i+1,j)<>' . 1 )
11 :f<m &&
then dir ~ •dir+l; a[i) [:f+ll J=l. 1)
if (:f>1) and (a[i,j-1)< > 1 • 1
)
dir++l
or pt+•p*dir;
(j <m) and (a[i,j+1)< > 1 , 1 )
}
then dir:=dir+1; cout<<Pt<<andll
pt ~ -Pt+p*dir )
end1
writeln(pt)
end.

Dacă utilizăm artificiul de a borda matricea cu caractere punct:

r,pentru i=O,n+l execută

1
a o, i~ 1
• 1 &n+l, i~ 1 •
1

~
14 Capitolul 1. Tablouri

atunci testarea vecinilor elementelor de pe margine se realizează întocmai ca şi in


cazul celor din interiorul matricei:
.-
dir~dir+l (cel puţin un vecin pe direcţia verticală)

rdacă ai.~-1~' • ' sau ai,:l+l:;l!:' • '


dir~dir+l (cel puţin un vecin pe direcţia orizontală)

Se mai poate analiza şi cazul in care o literă nu are nici un vecin pe niciuna
dintre d irecţiile orizontală sau verticală (caz Tn care litera nu s-ar puncta deloc), însă
problema precizează că literele sunt aşezate corect conform jocului de scrabble,
deci nu pot fi amplasate litere izolate.

Cl Aplicaţia 1.4. Sortarea fără comparaţii este o metodă de sortare care permite
sortarea an numere naturale, fără a face nici măcar o comparaţie intre ele.

Vom prezenta algoritmul pe un exemplu in care se sortează crescător 6


numere naturale: 6 , 36, 41, 25, 40 şi 30.

Iniţial, şirul celor n numere se imparte în 10 clase: prima clasă conţine


numerele care se termină cu o, a 2-a clasă, cele care se termină cu 1, ... a 10
clasă, cele care se termină cu 9 .

Pentru memorarea numerelor care aparţin fiecărei clase vom utiliza o matrice,
denumită Mat, cu 10 coloane, in care prima linie are indicele o. Elementele din linia o
reţin numărul de elemente din şir care se găsesc pe coloana respectivă:

o 1 2 3 4 5 6 7 8 9

11 215
1 :·: 1 . 1 o 1 o 1 o 1 1 .:. 1 o 1 o 1 o 1

Obţinem din nou şirul de numere, aşezându -le în ordinea col<;?anelor şi în


ordinea in care le-am pus in fiecare coloană: 40, 30, 41, 25 , 6, 36. lmpărţim din
nou şirul în 10 clase, după a 2-a cifră a numerelor:
o 1 2 3
'2 5 6 7 8 9

1: 1 o 1
215
1 :·~ 1 :: 1 o 1 o 1 o 1 o 1o 1
Manual de informatică pentru clasa a Xl-a 15

Obţinem din nou şirul de numere, aşezându-le in ordinea coloanelor şi în


ordinea în care le-am pus în fiecare coloană: 6, 25, 30__, 36, 40, 41. De
această dată, şirul este sortat.

~ Observaţii
../ Dacă k este numărul maxim de cifre a numerelor din şir, operaţia de
împărţire a numerelor în 10 clase se va relua de k ori.

Algoritmul este "mare consumator" de memorie!

var A:array [1 • • 1001 of #include <iostream. h>


integer; int A(100],Mat(100l [100],n,
Mat:array[0 •• 100,0 •• 100] NrCif,i , :f,k,Cif,Zece;
of integer;
n,HrCif,i,j,k,Cif,Zece: main()
integer; { cout<<"n•" ; cin>>n;
begin for (i•l;i<•n; i++)
write('n• '); readln(n); { cout<<"A("<<i<<"l•";
for i :• 1 to n do cin»A[i] 1
}
begin
write('A[',i, ' l= '); Zece=1;
readln(A[i]) for(HrCif=1;HrCif<=4;HrCif++)
end; { if (NrCif>1) Zece*•10;
Zece : •1; for (i=1;i<•n;i++)
for NrCif:=l to 4 do {Cif= {A(i] 1 zece) % 10;
begin Mat(O] [Cif]++;
if HrCif>1 then Mat[Mat[O] [Cif)) [Cif]=A[i];
}
Zece:=Zece*10;
for i:•1 to n do //Extrag din mat. in vector
begin k=O;
Cif:=(A[il div Zece) for (i•O;i<=9;i++)
mod 10;
if (Mat[O] [i])
Mat[O,Cif] :•Mat[O,Cif]+1; for (j=1;j<•Mat[0] [i];j++)
Mat[Mat[O,Cif],Cif] : •A[i] { k++;
end; A[k]=Mat[j] (i];
}
{Extrag din mat. in vector}
k:=O; for (i•O;i<•9;i++)
for i:=O to 9 do Mat[O] [i]•O;
}
if Mat(O,i]<> O then
for :1:=1 to Mat(O , il do for (i•1;i<=n;i++)
begin cout<<A[i]<<endl;
}
k:=k+1;
A[k]: • Mat[:f,i]
end;
for i:=O to 9 do
Mat[O,il:=O;
end;
for 1 : =1 to n do
Writeln(A(i]) ,·
end .
Capitolul 1. Tablouri
16

Proble.me prop use


de tablou, în
1. Se citeşte un tablou cun linii şi n coloane, numere întregi. Un astfel
lor, se numeşt e tablou pătratle .
care număru l liniilor coincide cu numărul coloane

a) Pentru un tablou pătratic A, numim diagonală principală , elemen


tele aflate pe
"linia" care uneşte A[l, 11 cu A[n,nl .

Exemp lu. Pentru tabloul de mai jos

(~}
elementele sunt: 1 , s şi 9.

Se cere:

a1) suma elemen telor aflate pe diagonala principală;


a2) suma elemen telor aflate deasupra diagonalei principale;
a3) suma elemen telor aflate sub diagonala principală.

tele aflate pe
b) Pentru un tablou pătratic A, numim diagonală secundară, elemen
•unia" care uneşte A (n, 11 cu A [l,nl .

Exemp lu. Pentru tabloul

(~}
elementele sunt: 7 , s şi 3.

Se cere:

b1) suma elemen telor aflate pe diagonala secundară;


b2) suma elemen telor aflate deasupra diagonalei secundare;
b3) suma elemen telor aflate sub diagonala secundară.

Cerinţe suplime ntare:

• pentru fiecare cednţă se va face un program separat;


• în nici un program nu se va folosi instrucţiunea if .

în linia k ,
2. lnterschimbaţi coloanele unei matrice c1~ m linii şi n coloane astfel încât
elementele să fie in ordine crescătoare.
Manual de informatică pentru clasa a Xl-a 17

3. Un teren este dat sub forma unui tablou A cu n linii şi m coloane. Elementul
A [i 1 j l reţine altitudinea pătrăţelului de coordonate i şi j. Să se afişeze
coordonatele "pătrăţelelor vârf" (un pătrăţel este vârf dacă toţi vecinii săi au o
altitudine strict mai mică).

4. Determinaţi elementele şa ale unei matrice cu n linii şi m coloane (elemente


minime pe linie şi maxime pe coloană sau maxime pe linie şi minime pe coloană) .

../ Indicaţie. Atenţie! Maximele sau minimele pe linii sau coloane pot să nu fie
unice. Dacă vom considera numai o valoare dintre acestea, s-ar putea să pierdem
soluţii.

5. Pentru o matrice a, cu .n linii şi m coloane citită din fişierul"matl. txt", se cere


să se afişeze cel mai mare element din matrice şi indicii acestuia. Scrieţi programul
pseudocod corespunzător.

6. Pentru o matrice de numere reale a, cu 5 linii şi 4 coloane, citită de la tastatură,


se cere să se afişeze suma elementelor strict pozitive din matrice.

7. Pentru o matrice a , cu n linii şi n coloane citită din fişierul"mat3. txt", se cere


să se decidă pe care dintre cele două diagonale, suma elementelor constitutive
este mai mare. Se va afişa un mesaj corespu nzător.

8. Pentru 5 cifre n 1 a 1 b 1 c 1 d citite de la tastatură , se cere să se creeze fişierul


"mats. txt" care să conţină o matrice cu 2n linii şi 2n coloane, formată din 4
submatrice disjuncte, fiecare cu n linii ş i n coloane, una dintre ele fiind formată
numai cu componente egale cu a, una numai cu componente egale cu b , una cu c
şi una cu d. Este necesa ră construirea in prealabil a matricei i n memorie?

9. Fereastra. Fiind dată o matrice cu m linii şi n coloane. Se cere să se afişeze


toate submatricele cu 3 linii şi 3 coloane ale matricei iniţiale. Un astfel de procedeu
este utilizat atunci când, de exemplu, o imagine este mult prea mare şi ea este
afişată cu ajutorul unei ferestre.

Exemplu: m=4 , n=4. Matricea iniţială şi submatricele sunt prezentate mai jos:

1 2 3 lf
5 G7 8 1 2 3 2 3 lf 5 G7 G7 8
9 H> 11 12 5 G7 G7 8 9 1 o 11 10 11 12
13 1LJ 15 1G 9 1o 11 10 11 12 13 1lf 15 1LJ 15 1G

10. Fiind dată o matrice cum linii şi n coloane, se cere să se afişeze elementele în
ordinea sugerată în figura de mai jos:

.+
i~
Figura 1.2. Exemplu
18 Capitolul 1. Tablouri

11. Fiind dată o matrice cu m linii şi n coloane se cere să se ~ A


afişeze elementele în ordinea sugerată în imaginea alăturată.

Figura 1.3. Exemplu


4 4
12. Fiind dată o matrice cun linii şi n coloane(pătratică) cu numere naturale şi fiind
date · două elemente ale matricei de coordonate (xl,yl) şi (x2,y2) , care dintre
relaţiile de mai jos testează dacă elementele se găsesc pe o dreaptă paralelă cu
una dintre diagonalele matricei (principală sau secundară)?

a) if x1-x2 ...y1-y2 a) if (x1-x2==y1-y2)


then writeln('Da') cout<<"Da"l
else writeln('Nu') ; else cout<<"Nu" ;
b) if x1-y1=x2-y2 b) if (x1-yl ..•x2-y2)
then writeln('Da') cout<<"Da"
else writeln('Nu'); else cout<<"Nu";
c) i f abs(x1-y1)=abs(x2-y2) c) if (abs(x1-y1)==abs(x2-y2))
then writeln('Da') cout<<"Da" ;
else writeln('Nu')l else cout<<"Nu" l
d) if abs(x1-x2)=abs(yl-y2) d) i f (abs(x1-x2)==abs(yl-y2))
then writeln('Da') cout<<"Da";
else writeln('NU')I el se cout<< "Nu"l

13. Se dă un vector v cu m*n componente numere întregi. Care dintre secvenţele


de mai jos copiază vectqrul v în matricea Mat cu m linii şi n coloane? Copierea se
realizează completând mai întâi elementele de pe linia 1, apoi elementele de pe
linia 2, ... , iar la sfârşit, elementele de pe linia m.

./ Copierea se realizează prin utilizarea unui singur ciclu for!

a) for k : =1 to m*n do Mat[1+k a) for <k=1 ; k<=m*n;k+ +)


div n,1+k mod m] : =V[k] ; Mat[l+(k-1)/n] [1+(k-1)% mJ=V[kl ;
b) for k : =1 to m*n do b) for (k=1Jk<=m*n;k++)
Mat[l+(k-1) div m, 1+(k-1) mod Mat[l+(k-1)/m] [1+(k-1)% n] =V[k]I
n] : =V[k] ; c) for (k=1; k<=m*nJk+ +)
c) for k :=1 to m*n do Mat[l+(k-1)/n] [1+(k-1)% n]=V[k];
Mat[1+(k- 1) div n,1+(k-1) mod d) for (k=1;k<=m*n;k++)
n] : =V[k] ; Mat[1+k/n] [1+k%n]=V[k];
d) for k:=1 to m*n do Mat[1+k
div n,l+k mod n]:=V[k];
~u al de informatică pentru clasa a Xl-a 19

~. Se dă un vector v cu m*n componente numere întregi şi o matrice Mat cu m


.,.. ş i n coloane. Care dintre secvenţele de mai jos copiază matricea Mat în
.-:ctorul v? Copierea se realizează astfel: mai întâi se copiază linia 1, apoi linia 2,
'ar la sfârşit, linia m.

a)
for j:=l tom do
V[j+(i-l)*m):=Mat[i,j]; V[j+(i-l)*m)•Mat[i] [j];
!>) for i:•l to m do b) for (i•l;i<-m;i++l
for j:=1 to n do for (j•1;j<•n;j++)
V[j+(i-l)*n):•Mat[i,j]; V[j+(i-l)*nJ=Mat[i] [j];
C} for i:=l tom do c) for (i•1;i<•m;i++)
for j:=l ton do for (j=l;j<=n;j++)
V[i+(j-l)*n] :~Mat[i,j]; V[i+ (j-1) *nl •Mat [i) [j);
d) for i: • l to m do d) for (i•l;i<•m;i++l
for j:•l ton do for (j•1d<=nd++)
V[j+(i-1)*n] :•Mat[j,i); V[j+(i-1l*n]=Mat[i,j];

~ă spunsurile la testele grilă

"2. d); 13. c); 14. b).


Capitolul 2

Subprograme

2.1. Noţiunea de subprogram

Definiţia 2.1. Prin subprogram vom înţelege un ansamblu alcătuit din


declarări şi instrucţiuni scrise în vederea unei anumite prelucrări,
ansamblu implementat separat şi identificat printr-un nume.

Până fn prezent am fost doar utilizatori de subprograme. Exemple de astfel


de subprograme folosite sunt:

• matematice: sin, cos, abs, exp, trunc (Pascal) 1 floor (C++);


• de manipulare a fişierelor: close (Pascal) 1 • closo () (C++).

in acest capitol rnvăţăm să lucrăm cu subprograme. Pentru a înţelege


noţ.iunea de subprogram, vom porni de la două exemple.

:h..; 1. Se consideră funcţia:


·•'"
~ ~~·

·~ X+ 1
pentru x e [-1,1];

!
1+ x 2 '
f (x) = X + 1,'
pentru x e (- oo ,-1);
6
pentru x e ( 1, oo ).
1+ X

Se citesc două valori reale a şi b. Să se scrie un program care afişează care dintre
valorile f (a> şi f (b) este cea mai mare.

Ce observăm? Că atât pentru calculul valorii funcţiei in punctul a, cât şi


pentru calculul valorii funcţiei în punctul b, aplicăm acelaşi tip de raţionament:
încadrăm valoarea respectivă (a sau b) într-unul dintre cele trei intervale şi aplicăm
formula de calcul corespunzătoare.

Cum procedăm? Prin utilizarea cunoştinţelor dobândite până in prezent,


scriem secvenţa de program care calculează valoarea funcţiei calculată pentru x=a
şi se mai scrie o dată (sau se copiază şi se adaptează) secvenţa de program care
calculează valoarea funcţiei calculată pentru x•b.
if~n ual de informatică pentru clasa a Xl-a 21

Oare nu se poate lucra şi altfel? Răspunsul este afirmativ. Se scrie un


:;'"'::>program care calculează valoarea funcţiei într-un punct x oarecare şi se
:=elează subprogramul: o dată pentru x=a şi încă o dată pentru x=b. Valorile
:a culate la cele două apeluri, se compară între ele şi se afişează rezultatul cerut.
:;:: 2 • Se citeşte n, un număr natural. Se citesc apoi n numere reale. Se
--X: cere să se afişeze cele n numere în ordinea crescătoare a valorilor lor.

Desigur, ştim să rezolvăm această problemă în mai multe feluri, pentru că


:;- 11 să memorăm un şir de valori (folosind un vector) şiam studiat mai multe
-etode prin care se poate obţine ordonarea crescătoare a unui şir de valori
:>~os i nd algoritmi de sortare). De această dată, vom implementa o metodă
:_noscută, cea de sortare, dar vom utiliza subprogramele.

Vom scrie un subprogram care citeşte un vector, unul care tipăreşte un


ector şi un al treilea care sortează vectorul după una din metodele cunoscute.

în acest caz, programul ar arăta astfel:

Pasul 1 - se apelează subprogramul care citeşte vectorul.


Pasul 2 -se apelează subprogramul care sortează vectorul.
Pasul 3 - se apelează subprogramul care tipăreşte vectorul.

Faţă de scrierea clasică, aici problema a fost descompusă in trei


probleme mai simple (citire, sortare şi tipărire). în general, o problemă complexă
se rezolvă mai uşor dacă o descompunem in alte subprobleme mai mici. Apoi,
şansele de a greşi la scrierea unui subprogram sunt cu mult mai mici decât acelea
::e a greşi la scrierea unui program mare.

ln plus, dacă într-un alt program este necesară sortarea altui vector de
~u mere reale, metoda clasică ne permite să alegem din secvenţa de instrucţiuni ce
'ormează programul pe cele ce realizează sortarea, să le copiem Tn noul program
şt să facem eventualele adaptări (numărul de componente şi numele vectorului pot
1 altele). Aceste operaţii sunt destul de greoaie şi necesită multă atenţie. Prin
mplementarea modulară, cu ajutorul subprogramelor, "preluarea" se realizează
mult mai uşor.

Putem acum enumera unele dintre avantajele utilizării subprogramelor:

• reutilizarea codului - odată scris, un subprogram poate fi utilizat de către


mai m uite programe;
• elaborarea algoritmilor prin descompunerea problemei în altele mai
simple - în acest fel, rezolvăm cu mult mai uşor problema;
• reducerea numărului de erori care pot apărea la scrierea programelor;
• depistarea cu uşurinţă a erorilor - verificăm la început subprogramele,
apoi modul în care le-am asamblat {le-am apelat din cadrul programului);
• realizarea unor programe uşor de urmărit (lizibila) .
22 Capitolul 2. Subprograme

2.2. Subprograme în Pascal

2.2.1. Un exemplu de utilizare a funcţiilor

O Problema 2.1. Se citeşte n, număr natural. Să se scrie programele care


afişează valoarea calculată a expresiilor:

Et = 1+.!_ +.!_+· ··+.!_. E2 =(l +_!_+_!_+···+_!_)n·


2 3 n' 2 3 n

~ Rezolvare. Funcţia care calculează :e:1 este:


function subp(nainteger)areall
var •arealJ
iainteger1
begin
81•01
for ir=l to n do SI•B+l/i;
subp1•s;
end;

Analiza programului anterior

-+ Antetul funcţiei este "funotion subp(nainteger) :real;".

-+ Funcţia se numeşte "subp".

-+ Ea este recunoscută de compilator prin faptul că antetul este precedat de


cuvântul cheie "function".

-+ Funcţia are un parametru numit n. Rolul său este important şi anume


precizează pentru ce valoare trebuie calculată expresia. Aşa cum vom
vedea, există posibilitatea să avem mai mulţi parametri, de diferite tipuri.
-+ Funcţia are un anumit tip, care precizează natura rezultatului. .în exemplu,
tipul este real întrucât expresia calculată este de acest tip.

-+ Funcţia are variabile proprii - adică variabile care sunt definite în cadrul el. În
exemplu, ele sunt s şi i . Aceste variabile se numesc variabile locale.

-+ Am văzut că funcţia întoarce un anumit rezultat - în exemplu, de tip real.


Observaţi mecanismul prin care am obţinut aceasta. Calculez expresia în
mod obişnuit. Rezultatul este reţinut de variabila locală s. Prin atribuirea
"subp=s 1 ", funcţia a primit ca valoare de retur conţinutul variabilei s. ·

În continuare, prez&ntăm cele două programe care utilizează funcţia:


'·'anual de informatică pentru clasa a Xl-a 23

var n:integer;
function subp(n:integer)z real;
var szreal;
i:integer;
begin
s:=O;
for i:•1 to n do s:•s+1/i;
subp:•s;
end;
begin
write ('n•'); readln(n);
write(subp(n):5: 2);
end.

var n,i:integer;
rez,prodzreal;
function subp(nzinteger)z real;
var szreal;
i:integer;
begin
s:=O;
for iz•l to n do s:•s+l/i;
subp:•s;
and;
begin
write ('n•'); readln(n);
rez:•subp(n);
prod:•l;
for iz•l to n do prodz•prod*rez;
write(prodz5:2);
end.

-+ Funcţia este apelată întotdeauna din cadrul unei expresii.


Apelul funcţiei în primul proaram s-a făcut astfel: "write(subp(n) z5:2) ;",
ar în al doilea, "rez a•aubp(n) ". In primul caz expresia este trecută ca parametru
oentru write, iar in al doilea, avem o expresie de atribuire.

În terminologia utilizată în teoria subprogramelor - în particular, în cazul


funcţiilor- se utilizează termenii parametri formali şi parametri efectlvl.

Definiţia 2.2. Parametrii care se găsesc în antetul funcţiei se numesc


parametri formali.

Atunci când scriem o funcţie , nu cunoaştem valoarea propriu-zisă a


parametrilor. Funcţia trebuie să întoarcă rezultatul corect, oricare ar fi valoarea lor.
Din acest punct de vedere ei se numesc formali.

Definiţia 2.3. Parametrii care se utilizează la apel se numesc parametri


efectivi.

La apel, lucrurile stau altfel: valorile acestora sunt cunoscute. Prin urmare
aceştia se numesc parametri efectlvl.
24 Capitolul 2. Subprograme

Între parametrii formali si parametrii efectivi trebuie să existe o concordanţă,


care va fi studiată la momentul potrivit.

2.2.2 Un exempl u de utilizare a proced urilor

D Problema 2.2. Se citeşte n, număr natural. Se citeşte un vector cu n


componente numere reale. Se cere să se tipărească vectorul sortat.

0 Rezolvare . Programul este următorul:


type vector•a rray [1 •• 9) of integer;
var n,i:integ er;
v:vector ;
procedur a citesc;
begin
write('n•' ); readln(n );
for i:=l to n do
begin
write('v [•,i,']=' ); readln(v [i]);
end;
end;
procedur e tiparesc ;
begin
for i:•l ton do writeln( v[i));
end;
procedur a sortez;
var gasit:boo lean;
man:inte ger;
begin
repeat
gasit:=f alse;
for i:=l to n-1 do
if v[i)>v[i +l)
then
begin
gasit:atr ue;
man:•v[i ]; v[i):•v[ i+lll v[i+l) :•man
end
until not gasit;
e nd;
begin
citesc;
sortez;
tiparesc ;
end.

Programul conţine trei proceduri: citesc, sortez şi tiparesc . Forma


simplificată prin care este descrisă o procedură este:

procedur a nume;
begin

end;
Vanual de informatică pentru clasa a Xl-a 25
----------------~----------------------------------------

+ Apelul unei proceduri se face prin utilizarea numelui ei, sub forma nume 1 •
Mai precis, apelul unei proceduri este instrucţiun~, numită instrucţiunea
de apel.
-+ La apel, controlul programului este transferat la prima instrucţiune a
procedurii - după cum se întâmplă şi la funcţii. După executarea procedurii,
se revine in programul principal/a prima instrucţiune care urmează celei de
apel- în cazul de faţă se întâlneşte o altă instrucţiune de apel.
~ Ca şi funcţiile, procedurile se pot plasa În cadrul programului intre
declaraţiile de variabile şi instrucţiunea compusfJ.

-+ Orice variabilfJ a programului principal este şi variabii(J a procedurii (invers nu


este adev~rat).

l

În acest exemplu, procedurile sunt apelate fără utilizarea parametrilor. În
realitate, procedurile pot avea parametri întocmai ca şi funcţiile.

2.2.3. Structura unui subprogram

În esenţă, un subprogram este alcătuit din:

• Antet - conţine mai multe informaţii important~ necesare compilatorului,


numele subprogramului, lista parametrilor formali. In cazul subprogramelor de
tip funcţie, conţine şi tipul rezultatului (valoarea întoarsă de funcţie).

• Bloc - cuprinde definiţiile tipurilor de variabile, ale variabilelor, ale


subprogramelor şi o instrucţiune compusă. Structura sa este identică cu cea a
programului principal, indiferent dacă este bloc de procedură sau de funcţie.

~ Programul principal conţine un antet (program nume) şi un bloc.



Pentru prezentarea structurii subprogramelor de tip funcţie vom utiliza
diagramele de sintaxă.

2.2.3.1. Structura subprogramelor de tip funcţie

În figura următoare este prezentată structura antetului:

LISTA
ANTET DE
--FUNCŢIE PARAMETRI
FORMALI

IDENTIFICATOR
DE TIP

Figura 2.1. Structura antetului unui subprogram de tip funcţie


26 Capitolul 2. Subprograme

Exemplu:
function suma(x,y:int eger):real;
begin
sum.a:=x+y;
end

- antetul este "function suma (x, y a integer) a real:";

- identificatorul (numele) funcţiei este suma. Lista parametrilor formali este:


"x, y: integer". Funcţia este de tip real (identificatorul de tip);

-blocul este (în acest caz lipsesc variabilele locale):

begin
suma:•x+y;
end

Important: identificatorul de tip (tipul valorii întoarse de funcţie) poate fi: de


r~~i·' orice tip ordinal, de orice tip real, de orice tip pointer sau de tipul string
(acest tip va fi studiat în acest manual).

2.2.3 .2. Structura subprogramelor de tip procedură

În figura următoare este prezentat antetul de procedură:

LIST.J.
ANTET IDENTIFICATO R PARAMETRI
PROCEOURE
-PROCEDURĂ FORMALI

Figura 2.2. Structura antetului unui subprogram de tip procedura

Exemplu:
procedura suma(var szreal; x,yzreal);
begin
SI"'X+y;
end;

-antetul este: "procedura suma(var sareal1 x,y:real) 1"

- identificatorul (numele procedurii) este suma;

-lista parametrilor formali este: "var s :real1 x,yareal;";

- blocul este:
begin
SI•X+y;
end;
v,a.,ual de informatică pentru clasa a Xl-a 27

: .2.4. Definirea şi declararea unui subprogr:.am

Deşi aparent asemănătoare, cele două noţiun i diferă . Buna înţelegere a lor
-e aj ută să evităm anumite erori. Mai mult, aceste noţiuni sunt utilizate de orice
,...baj de programare, nu numai de Pascal.

Definiţia 2.4. A defini un subprogram,. înseamnă a-1 scrie efectiv,


după structura anterior prezentată. O problemă importantă este locul
unde se defineşte subprogramul.

Pascal, subprogramele se definesc în trei locuri:

În blocul programului care-I utilizează • numit şi program principal • între


declaratia variabilelor şi instrucţiunea compusă:
program exemplul!
var x:integer;
procedura t ;
begin
writeln(x)
end;
begin
XJ • 3!
t
end .

2. În cadrul unităţilor de program· vor fi studiate ulterior.

3. În blocul unul alt subprogram. Această formă este proprie limbajului


Pascal. În exemplu·, procedura t, are ca subprogram procedura z. Iniţial
programul va atribui variabi.lei x valoarea 3. Apoi va apela procedura t . Ea
tipăreşte conţinutul lui x (3), apoi apelează procedura z. La rândul ei,
aceasta afişează un mesaj: • eu sunt z •.
program exemplul ;
var x:integer1
procedura t;
procedura z;
begin
writeln(•eu sunt z') ;
end;
begin
writeln(X)I z;
end;

begin
x:=3;
t
end.
28 Capitolul 2. Subprowame

Definitia 2.5. A declara un subprogram, înseamnă a~l anunţa. Un


subprogram nedeclarat nu poate fi folosit. ~

1. În cazul in care subprogramul este definit in blocul programului principal


sau in blocul subprogramului, se consideră că a fost şi declarat pentru
acesta, deci poate fi folosit. În acest caz, declaraţia coincide cu definiţia.

Exemple

• În programul exemplul, procedura t este definită în blocul programului


principal. Ea poate fi apelată din programul principal.

• În programul exemplu2, procedura t este definită în blocul programului


principal, deci poate fi apelată de acesta. Procedura z este definită în blocul
procedurii t. Ea poate fi apelată din t. În schimb, nu poate fi apelată din
programul principal.

2. În blocul programului principal sau în blocul unui subprogram se definesc, În


ordine, subprogramele s 1 , s 2 , ... , sk. În acest caz, automat s 1 este
declarat pentru s 2, s 3, ... , sk, apoi s 2 este declarat pentru 6 3, ... , sk, iar sk-1
este declarat pentru sk.

Exemple

• Procedurile al şi s2 pot fi apelate din programul principal. Procedura sl


poate fi apelată din procedura s2, dar s2 nu poate fi apelată, în absenţa altei
clauze, din sl:

procedura sl;
begin
writeln( 'sl')
end;
procedura s2;
begin
sl;
writeln ( • s2');
end;
begin
sl,;
s2;
end.

• Procedura test conţine definiţia a două proceduri sl şi s2. Nici una din
aceste proceduri nu se consideră declarată pentru programul principal.
Amândouă sunt declarate pentru procedura test. Pentru procedura s2, sl
este declarată, dar pentru sl procedura s2 nu este declarată.
\1anual de informatică pentru clasa a Xl-a
29

proced ura test;


proced ura sl;
begin
writel n( 'sl')
end;
proced ura s2;
begin
Sll
writel n( 's2 ') 1
end1
begin
Sll

end1

begin
test1
end.

3. În situaţia prezentată în cazul 2, se poate totuşi ca orice subpro


gram definit
la nivelul unui bloc să fie declarat pentru toate subprogramel
e definite la
nivelul aceluiaşi bloc, ca in exemplul următor.

Declaraţia se fac.e prin antet, urmat de cuvântul cheie "forw ard".

f Observaţi că, în acest caz, definiţia nu coincide cu declaraţia.


• proced ura b1 forwar d1

proced ura &1


begin
writel n('Eu sunt a')l
bl
end1

proced ura b;
begin
writel n( 'Eu sunt b')
Glldl

begin
&1
end.

4. În cazul in care subprogramul a fost definit în cadrul unei unităţi


de program,
declaraţia unităţii,clauza "uses", are efect de declaraţie pentru toate
subpro grame le definit e in cadrul el (unităţile de program vor fi
studiate în
cadrul acestui capitol).
30 Capitolul 2. Subprograme

2.2.5. Apelul subprogramelor

Definiţia 2.6. A apela un subprogram, înseamnă a-1 lansa în


executare. Pentru a putea fi apelat din cadrul unui bloc, un subprogram
trebuie declarat la nivelul blocului respectiv.

2.2.5.1. Apelul funcţiilor

în programul de mai jos este apelată o funcţie care calculează produsul.a


două numere întregi. Programul tipăreşte suma între 1 şi produsul calculat. In
exemplu, valoarea găsită este 7:

var p,x,yainteger;

function prod(x , y z i~teger)ainteger;


begin ·
proda•x*y;
end1

begin
x:=2;
y:-3;
pa • l+prod(x,y);
writeln(p)
end .

-+ Apelul unei funcţii se realizează din interiorul unei expresii. în exemplu,


expresia este: "l+prod(x,y)". Rezultatul ar fi fost obţinut mai simplu dacă
scriam "writeln(l+prod(x, y)) ; ".

~b Observaţii
-/ În cadrul expresiei, apelul este un operand. El Intră în calcul cu valoarea
returnată de funcţie.

-/ După apelul funcţiei, se continuă evaluarea expresiei.

Mai jos este prezentat operandul care reprezintă apelul funcţiei.

LISTĂ
APEL IDENTIFICATOR PARAMETRI
FUNCŢIE FUNCŢIE
EFECTIV!

Figura 2.3. Structura operandulul care reprezintA apelul funcţiei


\1anual de informatică pent ru clasa a Xl-a
31

2.2. 5.2. Apelul proc edu rilor _

Cele câteva exemple date până acum fac inutilă prezentarea unui alt
exemplu. Trebuie să ştim că:

1. Apelul unei proceduri se face print


r-o instrucţiune, numită Instrucţiune
procedurală.Sintaxa acestei instrucţiuni este prezentată
mai jos:

INST RUC TIUN E LISTĂ


-PROCEDURALĂ 1DEN TI FI CA TOR PAR AME TRI
IEFEC TIVI

Figura 2.4. Structura instrucţiunii procedurale

2. După apel, se va executa instrucţiunea care urmează instrucţiunii procedurale.

2.2. 5.3. Transmiterea para met rilor la ape


l
În ce priveşte mecanismul de transmite
re a parametrilor nu există nici o
diferenţă între proceduri şi funcţii. Din acest motiv,
~rata
transmiterea parametrilor se va
unitar.

Para metr ii form ali sunt cei trecuţi în ante


tul subprogramului.
Para metr ii efec tlvl sunt cei trecuţi la apel
ul subprogramului.

?• Aşa cum am văzut, subprogra


mele pot lucra cu variabilele globale ale
blocului care conţine defin iţia lor. Atunci
care este motivul pentru care sunt
necesari parametrii?

Cunoştinţele dobândite până in


acest moment nu permit o justificare
comp letă. Putem spune numai că
utilizarea parametrilor permite ca subprogra
să fie scris independent de programu mul
l principal. Să luăm un exemplu: ni se cere
scriem un subprogram care calculează să
suma a două valori reale. Ce adunăm?
Pentru un program ar trebui să adunăm x cu y, iar pentru altul, m cu n.
var x,y, sum :inte ger;
proc edur a sum a(a, b:in tege r; var
s:in tege r);
begi n
s:•a +b;
end;
begi n
x:•3 ; Yl=4 ;
sum a(x, y,su m); writ eln( sum );
suma(~,3,sum); writ eln(
sum );
end.
32 Capitolul 2. Subprograme

În programul anterior, procedura suma calculează suma a două numere


întregi. Ea este apelată de două ori din cadrul programul~i principal.

- parametrii formali sunt: "a, b: integer; var s: integer; ";


- pentru primul apel, parametrii efectivi sunt: x, y, sum;
- pentru al doilea apel, parametrii efectivi sunt: 2, 3, sum.

lată câteva reguli care trebuie respectate la apel:

lO> Numărul parametrilor formali trebuie să coincidă cu numărul


parametrilor efectivi. În exemplu, acest număr este 3.

);> Tipul parametrilor formali trebuie să coincidă cu tipul parametrilor


efectivi sau tipul parametrilor efectivi să poată fi convertit implicit către
tipul parametrilor formali, la fel ca în cazul atribuirii.

· De exemplu, dacă parametrii formali a şi b sunt de tipul integer, nu este


permis ca parametrii efectiv! să fie de alt tip (de exemplu, de tipul real).

fJ Nu este obligatoriu ca numele parametrilor formali să coincidă cu numele


• parametrilor efectivi.

lO> Nu este permis să definim tipul parametrilor în cadrul antetului subpro-


gramului. Tipul trebuie să fie predefinit sau să fie declarat în prealabil.

Exemple
type vector=array [1 •• 9] of integer;

procedura test(n:integrer, v:vector) ; {corect}


procedura test(n:integrer, v:array[1 .. 9] of integrer); (incorect}

Parametrii formali sunt de două feluri: transmişi prin valoare şi transmişi


prin referinţă. În cazul in care aceştia sunt precedaţi de cuvântul cheie var, se
consideră că sunt transmişi prin referinţă, contrar, prin valoare.

Tratarea transmiterii prin valoare şi prin referinţă se va face în paragrafele


u rmătoare. Pentru moment, prezentăm sintaxa parametrilor formali şi a
parametrilor efectivi:
LISTĂ
-PARAME TRI IDE NTI F I CA TOR T I P
FORMAU

LISTĂ
PARAM ETRI
EFECTI V!
EXPRESI E

Figura 2.5.
Sintaxa parametrilor trimişi
ranual de informatică pentru clasa a Xl-a 33

2.2 .5.4. Cum memorează subprogramele parametrii transmişi?

În acest paragraf vom analiza modul prin care sunt memoraţi parametrii
""al1 smişi în momentul lan sării în executare a subprogramului.

~ Pentru memorarea parametrilor, subprogramele folosesc o zonă de memorie


numită stivă (mai exact, această zonă se numeşte segment de stivă).

~ Memorarea parametrilor transmişi se face în ordinea în care aceştia


figurează în antet: de la stânga la dreapta.

-+ Pentru parametrii transmişi prin valoare, se memorem valoarea transmisă,


iar pentru cei transmişi prin referinţă, se memorează adresa variabilei
transmisă ca parametru.

~ În cadrul subprogramului, parametrii transmişi şi memoraţi în stivă sunt


variabile. Numele lor este cel din lista parametrilor formali.
'
Reluăm exemplul anterior:
• antetul este: "procedura suma(a,b:integer1 var s:integer) ;";
• la prtmul apel, parametrii sunt: x, y, sum, unde x reţine "3" şi y "4".

În stivă sunt memorate variabilele subprogramului. În figură, sunt prezentate


ŞI valorile memorate:

stiva~~~--3------4--~---a-d-re_s_a_v_a_ri_a_bi-le_i_s_um____~l
a b s
~a al doilea apel, parametrii sunt: 2, 3, sum.

stiva ~1 2 1 1 3 adresa variabilei sum


1
a b s
f: Observaţii

./ La revenirea în blocul apelant, conţinutul variabilelor memorate în stivă se va
pierde.
./ Memorarea în stivă are şi alte consecinţe, dar acestea vor fi tratate la
momentul potrivit (vezi recursivitatea!).

2.2.5.5. Transmiterea parametrilor prin valoare

Transmiterea prin valoare se utilizează atunci când suntem interesaţi ca


subprogramul să lucreze cu acea valoare, dar în prelucrare, nu ne interesează ca
parametrul efectiv (cel din blocul apelant) să reţină valoarea modificată în subprogram.
'
34 Capitolul 2. Subprograme

Astfel, se pot transmite prin valoare:

);> valorile reţinute de variabile - în acest caz, parametrii efectiv! trebuie să fie
numele variabilelor.

Exemplu:
var n:integer;
prooedure test(n:integer);
begin
n:=n+l;
writeln(n)
end;
begin
n:•l;
test (n);
writeln(n);
end.

-parametrul n este transmis prin valoare.

-+ În programul principal, avem declarată variabila n, care este iniţializată cu 1.

-+ Apelăm procedura. La apel, se rezervă spaţiu în stivă, spaţiu care are


numele parametrului (deci tot n) şi este Iniţializat cu valoarea memorată de
variabila n a programului principal. În acest moment avem două variabile n şi
ambele reţin valoarea 1.

-+ În procedură, variabila n este incrementată (adică la vechiul conţinut se


adaugă 1). Evident, este vorba de variabila memorată în stivă.

+ Aflşăm conţinutul variabilei n (cea din stivă), deci se tipăreşte 2.


+ La Ieşirea din procedură, variabila n (din stivă) se pierde - adică nu mal are
spaţiu alocat. Prin urmare, valoarea 2 este pierdută.

+ În programul principal se tipăreşte conţinutul variabilei n, adică 1.

);> parametrii efectiv! sunt valori sau expresii, care mai întâi se evaluează.

Exemplu:
prooedure test(n:integer);
begin
writeln(n)
end;

begin
test(3);
test(3+4*5);
end.
anual de informatică pentru clasa a Xl- a 35

În procedură se creează o variabilă în segmentul de stivă, numită n, care la


: ""mul apel reţine valoarea 3 şi la al doilea, valoarea 23. La ieşirea din procedură
;onţinutul variabilei se pierde.

Aşa cum am văzut, în cazul transmiterii prin valoare, se pot folosi ca


:arametri efectiv! expresii. De asemenea, expresiile pot avea ca operanzi funcţii.
_""n ătorul program citeşte două valori x şi y şi calculează expresia (x-y) * (x+y).

Exemplu:
program test_param;
var x,y:integer;
function suma (a,b:integer):int eger;
begin
suma:=a+b;
end;
function dif (a,b:integer):int eger;
begin
dif:=a-b;
end;
function prod(a,b:integer ):integer;
begin
prod:=a*b;
end;
begin
write('xa'); readln(x);
write('y='l; readln(y);
writeln(prod(dif (x,y),suma(x,y)) );
end.

2.2.5.6. Transmiterea parametrilor prin referinţă

Parametrii sunt transmişi prin referinţă atunci când ne interesează ca, la


revenirea din subprogram, variabila transmisă să reţină valoarea stabilită în timpul
executării subprogram ului.

+ În cazul transmiterii prin referinţă, parametrii efectiv! trebuie să fie variabile.


+ În cazul subprogramelor de tip procedură, transmiterea prin referinţă este
mecanismul clasic prin care acestea întorc valori către blocul apelant.
+ Pentru ca un parametru să fie transmis prin referinţă este necesar ca
parametrul formal să fie precedat de cuvântul cheie "var".

+ În cazul transmiterii prin referinţă, subprogramul reţine în stivă adresa variabilei.

? în acest caz, ne putem întreba care este mecanismul prin care, deşi pentru o
• variabilă transmisă se reţine adresa sa, În subprogram putem adresa
variabila normal (nu indirect)? La compilare, orice referinţă la variabila
respectivă, este "tradusă" ca adresare indirectă.
36 Capitolul 2. Subprograme

Exemplu. Programul următor utilizează o procedură care interschimbă valorile


reţinute de două variabile. Acestea sunt transmise prin referinţă.

var x,y:integer;
procedura interschimb(var x,y:integer)l
var man:integer1
begin
man:•x; XI•YI Yl~l
end;
begin
x:•21 yz.,3;
intarschimb(x,y);
writa(x,• •,y);
end.

2.2.6. Variabile locale şi globale

~'
]l.B
Definiţia 2.7. Variabilele globale sunt variabilele programului principal.
Ele sunt rezervate într-o zonă specială de date, numită segment de date.
În versiunea 7 . o a limbajului Pascal, variabilele numerice sunt
iniţializate cu o.

Definiţia 2.8. Variabilele locale sunt variabile declarate în interiorul


subprogramelor. Aceste variabile sunt memorate în segmentul de stivă,
după variabilele generate de parametri. Este sarcina programatorului să
asigure iniţializarea lor cu valorile dorite. La ieşirea din subprogram
conţinutul variabilelor locale se pierde.

Un termen des folosit în practica limbajelor de programare este acela de


vizibilitate. A preciza vizibilitatea unei ·:tariabile inseamnâ a spune care sunt
blocurile de unde se poate adresa (ca să-i atribuim o valoare, de exemplu).

+ Variabilele globale sunt viz.ibile la nivelul programului principal şi la nivelul


subprogramelor.
var x:integar1 Programul alăturat tipăreşte de trei ori
procedura a; 2, conţinutul variabilei globale x.
procedura b1
begin
writaln(x)
end;
begin
Xl•2;
b;
writeln(x)
end;
begin
a;
xz•2;
writeln(x) 1
end.
.lanual de informatică pentru clasa a Xl-a 37

-+ Variabilele locale sunt vizibile doar la nivelul subprogramului în care au fost


declarate şi la nivelul subprogramelor definite în acesta. .~
procedure a; Programul alăturat tipăreşte de doua ori 2,
var x:integer; conţinutul variabilei locale x. Dacă am fi
procedure b;
încercat să tipărim x din cadrul programului
begin
writeln(x) principal, programul ar fi dat eroare de
end; compilare.
begin
x:=2;
b;
writeln(x)
end;
begin

,
a;
end.

Atenţie! Există situaţii în care o variabilă globală nu poate fi adresată din


cadrul subprogramului, adie~ atunci când acesta conţine declaraţia unei alte
' variabile, cu acelaşi nume. In acest caz, prin nume, se adresează variabila
locala. Regula este următoarea: În cazul rn care două variabile au acelaşi
nume, dar vizibilităţi diferite, se adreseam i'ntotdeauna variabila cu
vizibilitatea mai redusă . Evident, în cazul existenţei a două variabile cu
aceeaşi vizibilitate şi acelaşi nume, compilatorul va da eroare de sintaxă.

Programul de mai jos tipăreşte 2, nu 3:


var x 1 integer;
procedura a;
begin
x:=2;
writeln(x)
end;
begin
x:=J;
a;
end.

Alt termen des utilizat în practica programării este durata de viaţă a unei
-ariabile. El se referă la perioada de timp în care variabila este alocata în memorie.
!.stfel, avem:

durată statică - variabila are alocat spaţiu pe tot timpul executării programului.
durata locală - variabila are alocat spaţiu doar în timpul cât se execută un
anumit bloc.
durată dinamică - alocarea spaţiului de memorie se face în timpul executarii prin
subprograme speciale sau operatori (în Pascal avem subprograme speciale).
Variabilele cu această durată de viaţă se numesc dinamice şi nu se tratează în
acest manual.
38 Capitolul 2. Subprograme

Din acest punct de vedere putem spune că variabilele globale au dura~ de


viaţă statică,iar variabilele locale au durata de viaţă locală.
Un ultim termen este dat de clasa de memorare a unei variabile. Se referă
la locul unde variabila este memorată. Astfel, o variabilă poate fi memorată în
segmentul de date, în segmentul de stivă sau în heap.

Astfel, pe lângă tip, o variabilă se caracterizează prin: clasă de memorare,


vizibilitate şi durată de viaţă.

2.2.7. Greşeli frecvente

1. Greşeli care apar la transmiterea tipurilor nestandard

Reamintim faptul că un tip este standard dacă este cunoscut de limbaj (de
exemplu, integer). În caz contrar tipul este nestandard (de exemplu, tipul care
descrie un vector cu componente de tip real) . lată o procedură scrisă greşit:
procedura er (var Vlarray[1 • • 99) of real);
begin

end;

Eroarea este dată de faptul că tipul nestandard al variabilei v este descris în


lista parametrilor. Programul va da eroare de sintaxă.
Pentru a proceda corect, tipul este descris separat, în cadrul programului
principal, prin type.
type vect=array[1 •• 99] of real;
var v1vect;
procedura er (var Vlvect)t
begin

endt

2. Greşeli care apar la atribuirea valorii de retur a unei funcţii (valoarea pe


care funcţia o intoarce)

lată o funcţie, scrisă eronat, care întoarce o valoare întreagă a (o sumă).

function suma( n,i:integer):inte gert


var s1integer;
begin
calculez s;

swna(n,i):=st
end;

Eroarea constă în faptul că in atribuirea "suma(n, i ; ::•s;" au fost trecuţi


parametri. Corect este "suma: =s; ".
'.~anual de informatică pentru clasa a Xl-a - 39

2. 2.8. Unităţi de program

În activitatea practică se fac aplicaţii cu un număr mare de programe care de


-,uite ori sunt de mari dimensiuni. O preocupare constantă a informaticienilor este
:re a pune la dispoziţia programatorilor instrumente eficiente de programare. În
acest sens, limbajul Pascal oferă posibilitatea lucrului C\J unităţi de program
unit-uri).

Definiţia 2.9. Prin unitate de program înţelegem un ansamblu de date,


proceduri şi funcţii, plasate şi compilate împreună, care pot fi uşor
utilizate în scrierea altor programe, fără a se cunoaşte amănunte
necesare realizării lor.

Forma generală a unei unităţi de program este următoarea:

UNIT <nume unitate>;


J:NTERFACE
[USES <numele altor unităţi de program pe care le utilizează>;]
[tipuri de date şi variabile globale în programul care utilizează
unitatea]
antete de proceduri şi funcţii

IMPLEMEN'l'ATION
[tipuri de date ş i variabile locale (valabile numai pentru unitatea de
program)]
procedurile şi funcţiile utilizate de unitatea de program
(BEGJ:N
.. .. ... o eventuală secvenţă de program care se execută
....... în momentul declarărlî unităţii de program în
....... programul care o utilizează.
END.]

_. Prima parte, obligatorie, este precedată de clauza "INTERFACE". Aici se


găsesc definiţiile tipurilor de date, variabilelor, şi sunt enumerate procedurile
şi funcţiile ce pot fi utilizate din programul principal. Pentru orice tip de
variabilă declarat aici, se pot declara variabile de acest tip atât în cadrul
unităţii cât şi în cadrul programului care foloseşte unitatea. Se observă că o
unitate de program are în mod obligatoriu două părţi, opţional, o a treia.
Toate procedurile şi funcţiile care aici sunt numai enumerate pot fi folosite de
orice program care foloseşte unitatea.

_. A doua parte, obligatorie, se găseşte plasată sub clauza ":IMPLEMEN'l'ATION''.


Aici se pot declara tipurile şi variabilele vizibile numai pentru unitatea dată.
Tot aici se scriu clar toate procedurile şi funcţiile care în parte au fost numai
declarate, precum şi altele ce pot fi folosite numai în cadrul unităţii.

_. Partea opţională a fost prezentată în cadrul formei generale a unităţii.


40 Capitolul 2. Subprogramc

;jţ;, "" În continuare, vom da un exemplu pur didactic de unitate de program


Q~: numită OPERA'l'J:J:. Aceasta conţine patru·~ proceduri: de adunare
···~· scădere, înmulţire şi împărţire de variabile reale. Variabilele a şi b sur:
globale pentru programul care utilizează această unitate, iar variabila c poate f
utilizată numai în cadrul unităţii. În secvenţa de iniţializare se citesc valorile
variabilelor a şi b.

Unitatea de program este prezentată in continuare:


unit operatii;
intorfaco
var a,brroal;
procedura adun(a,b:roal);
procedura scad(a,b:roal);
procedura produs(a,brroal);
procedura impart(a,b:raal);
implomaDtation
var caraal;
procedura adun(a,brraal);
begin
cr•a+b;
writaln(calr2);
ond;

procedura scad(a,brreal)t
begin
cr•a-b;
writoln(crls2);
end;
procedura produs(a,baroal);
bagin
ca•a*b;
writoln(cal:2);
end
procedura impart(a,brREAL);
begin
if b~ o thon writaln('~rtiroa nu se poate face ')
olso
begin
cr•a/b;
writoln(ca3a2);
endt
and;
begin
writo( 1 a• 1 ) 1
roadln(a) ;
wri taln ( 1 b= 1 ) 1
roadln(b);
end.
~"'u al de informatică pentru clasa a Xl-a 43

..;; Funcţia are un anumit tip, care precizează natura rezultatului. În exemplu,
tipul este doub:Le întrucât expresia calculată este de acest t\p.
-+ Funcţia are variabile proprii - adica variabile care sunt definite în cadrul ei. În
exemplu, s şi i. Aceste variabile se numesc variabile locale.
-+ Am vazut întoarce un anumit rezultat - în exemplu, de tip double.
că funcţia
Observaţi mecanismul prin care am obţinut aceasta. Calculez expresia în
mod obişnuit. Rezultatul este reţinut de variabila locală s. Prin instrucţiunea
"return s;", funcţia a primit ca valoare de retur conţinutul variabilei s.

În terminologia utilizata în teoria subprogramelor - în particular, în cazul


.:-cţiil or- se utilizează termenii parametri formali şi parametri efectiv/.

Definiţia 2.1 O. Parametrii care se gasesc în antetul funcţiei se numesc


parametri formali.

Atunci când scriem o funcţie nu cunoaştem valoarea propriu-zisă a


:.:.rametrilor. Funcţia trebuie sa intoarca rezultatul corect, oricare ar fi valoarea lor.
: 11 acest punct de vedere ei se numesc formali.

Definiţia 2.11. Parametrii care se utilizează la apel se numesc parametri


efectiv!.

La apel, lucrurile stau altfel: valorile acestora sunt cunoscute. Prin urmare,
aceştia se numesc parametri efectivi.

=entru apelul "rez=subp (n) 1 ", parametrul efectiv este n.

:J Problema 2.4. Se citeşte n, numar natural. Se citeşte un vector cu n


:omponente numere reale. Se cere sa se tipareasca vectorul sortat.

#include <iostream.h>
void citeac(int vt[lO],int n)
{ int i1
for(i-=O;i<n;i++)
{ cout<<"v["<<i+l<<"l•"l
cin>>vt[i];
}
}

void sortez(int vt[lO],int n)


{ int gasit,i,man1
do
( gasit•O;
for (i=O;i<n-lli++)
if (vt(i]>vt(i+l])
{ man=vt[il;
vt[i]=vt[i+l]l
vt[i+l]=manl
ga,sit•l; }
} while (gasit);
}
44 Capitolul 2. Subprograme

void scriu(int vt[lO],int n)


{ int i;
for(i=O;i< n;i++)
cout<<vt[i )<<endl;
}

main()
{ int v[lO),n;
cout<<•n=• ;
cin>>n;
citesc(v,n );
sortez(v,n );
scriu(v,n) 1
}

Programul conţine trei funcţii: citesc, sortez şi tiparesc.

:> La apel, controlul programului este transferat la prima instrucţiune a funcţiei.


Du,m executarea funcţiei, se revine in programul principal la prima
instrucţiune care urmem celei de apel - in cazul de faţlJ se Întâlneşte o alt~
instrucţiune de apel.

:> Cele trei funcţii au tipul vo:l.d, adică nu au valoare de retur. Ele returneam
rezultatul prin intermediul parametrilor.

2.3.2. Structura unei funcţii

În esenţă, o funcţie este alcătuită din:

+ Antet - acesta conţine mai multe informaţii importante necesare


compilatorului: numele funcţiei, lista parametrilor formali, tipul rezultatului.

Structura antetului este:

tip nume(list a parametr ilor formali)

Lista parametrilor formali este de forma:


parametru 1 , parametru2 , • •• , parametru n

Exista şi posibilitatea ca lista parametrilor formali sa fie vidă. Fiecare


parametru are forma:

tip nume.

+ O instrucţiune compusă - aceasta cuprinde declaraţiile variabilelor locale,


şi instrucţiunile propriu-zise.

Poate fi tip al unei funcţii orice tip de data cu excepţia masivelor.


-~al de informatică pentru clasa a Xl-a 45

E:xemple de antete:

- .int suma(int a, int b) - funcţia se numeşte suma, returnează un


•ezultat de tip int şi are doi parametri formali de tip int, numiţi a şi b.
- ?Oid t (int n, float v[20l) - funcţia se numeşte t, este de tip void
nu returnează rezultat prin nume), are doi parametri formali, primul numit n, de
· p int, al doilea num'it v, de tip float * (un tip care reţine adrese de vectori
cu elemente de tipul float).

O funcţie returnează rezultatul la întâlnirea instrucţiunii return, care are


forma:
return expresiei

Trebuie ca tipul expresiei să coincida cu tipul funcţiei.

La întâlnirea instrucţiunii return, după atribuirea valorii, execuţia funcţiei se


r.: ,eie şi se revine la funcţia care a apelat-o. În absenţa instrucţiunii return,
l!.t ecuţia funcţiei se încheie după execuţia ultimei instrucţiuni. În acest caz nu se
r-:xll'ce nici o valoare.

-+ O funcţie poate fi apelata de sine statător (prin nume şi lista parametrilor


efectivi), dar poate fi inclusă şi în cadrul expresiilor, caz în care, la evaluarea
expresiei este apelată. Această ultimă formă de apel nu este valabila în cazul
funcţiilor de tip void.

•include <iostream.h> În programul alăturat este apelată o


funcţie care calculează produsul a
int prod (int x, int y) două numere întregi.
{ return x*y 1 )
main() Programul tipăreşte suma între 1 şi
{ int x•l,:y-31 produsul calculat. În exemplu, 7.
cout<<l+prod(x,y )l
)

Apelul funcţiei s-a realizat din interiorul expresiei: "l+prod(x,y) ".

1
•,
Observaţii

. În cadrul expresiei, apelul este un operand. El intră in calcul cu valoarea


returnată de funcţie.
\

După apelul funcţiei se continuă evaluarea expresiei.

La apel, ordinea de evaluare a parametrilor nu este definită. De exemplu,


dacă apelăm funcţia test astfel:

test(2-3,2+3),

nu ştim dacă înainte se efectuează scăderea sau adunarea.


46 Capitolul 2. Subprograme

2.3.3. Declararea variabilelo r

Până în prezent am declarat variabile doar în corpul funcţiilor - inclusiv în cel


al funcţiei main ( ) . Variabilele declarate astfel se numesc locale.

+ Fiecărui program i se alocă trei zone distincte în memoria internă în care se


găsesc memorate variabilele programului:

Figura 2.5. Cele trei zone in memoria internA

De asemenea, există posibilitatea ca variabilele să fie memorate într-un


anumit registru al microprocesorului. În acest caz timpul de acces la astfel de
variabile este foarte mic, deci se pot obţine programe optimizate.

+ În general, o variabilă se caracterizează prin 4 atribute. Acestea sunt:

• clasa de memorare;
• vizibilitate;
• durata de viaţă;
• tipul variabilei, singurul pe care l-am studiat până în prezent.

1. Clasa de memorare - precizează locul unde este memorată variabila


respectivă. O variabilă poate fi memorată în segmentul de date, în cel de
stivă, în heap sau într-un registru al microprocesorului.

2. VIzibilitatea - precizează liniile textului sursă din care variabila respectivă


poate fi accesată.

Astfel avem:

a. VIzibilitate la nivel de bloc (instrucţiune compusă).

b. Vizibilitate la nivel de fişier • în cazul în care programul ocupă un


singur fişier sursă, singurul caz pe care îl tratăm acum.

c. Vizibilitate la nivel de clasă · este în legătură cu programarea pe


obiecte, pe care o veţi studia individual.
a,ual de· informatică pentru clasa a Xl-a 47

Durata de viaţă • reprezintă timpul in care variabila respectivă are alocat


spaţiuin memoria internă. Astfel avem:

a. Durată statică · variabila are alocat spaţiu in tot timpul execuţiei


programului.
b. Durată locală - variabila are alocat spaţiu in timpul in care se execută
instrucţiunile blocului respectiv.
c. Durată dinamică · alocarea şi dezalocarea spaţiului necesar variabilei
respective se face de către programator prin operatori sau funcţii
speciale.

În C++ variabilele pot fi împărţite în trei mari categorii: globale, locale şi


: namice. Noi studiem numai primele două categorii, variabilele globale şi locale .

.& Variabile globale

Acestea se declară in afara corpului oricărei funcţii, ca mai jos:


#include <iostream.h>
int a ;
int t()
( a a 3;
cout<<a;
}

int b;
main()
{ b • 4;
cout<<a<<endl;
t(}J
}

Variabilele a şi b sunt globale. În astfel de cazuri, variabilele respective pot


utilizate de toate funcţiile care urmea~ În textul surs~ declaraţiei variabilei
·espective. Din acest motiv, astfel de variabile se numesc globale.

l La declarare, variabilele globale sunt iniţializate cu o.



.\tributele variabilelor globale sunt:

1. Clasa de memorare - segmentul de date.

2. Vizibilitatea - În cazul în care declaraţiile acestora sunt înaintea tuturor


'u ncţi i lor , acestea sunt vizibile la nivelul întregului program (fiş ier) . Dacă anumite
'u ncţii se află plasate înaintea declaraţi ilor acestor variabile, atunci ele sunt vizibile
doar pentru funcţiile care sunt plasate după aceste declaraţii. In exemplul anterior,
variabila a poate fi accesată din corpul oricărei funcţii, dar variabila b poate fi
accesată doar din funcţia main ( ) .
48 Capitolul 2. Subprograme

3. Durata de viaţă a variabilelor globale este statică. Ele au spaţiu rezervat în tot
timpul execuţiei programului.

B} Variabile locale

Acestea sunt declarate în corpul funcţiilor. Mai precis, pot fi declarate în orice
bloc (instrucţiune compusă) al acestora.

~ Variabilele declarate în corpul funcţiei main () sunt tot locale. În programul


~~ următor, variabilele a şi bsunt locale.

Variabila a este declarată în corpul funcţiei t ( ) , iar variabila b este declarată


în corpul funcţiei main ( ) .

Exemplu:
void t()
{ int a•3;
}

main()
{ int b•4;
}

1. Clasa de memorare a variabilelor locale este implicit segmentul de stivă .


Există posibilitatea ca acestea să fie alocate în registrele microprocesorului, caz în
care declaraţia lor trebuie precedată de cuvântul cheie "register".

Exemplu:
register int b•41

Variabilele locale nu sunt iniţializate implicit cu o. În ipoteza în care acestea


nu sunt iniţializate explicit de programator, reţin o valoare oarecare, numită
valoare reziduală. '

2. VIzibilitatea variabilelor locale este la nivelul blocului in care au fost declarate.

În funcţia următoare am declarat două variabile de tip int, numite b şi c .


Variabila b este vizibilă la nivelul funcţiei, dar variabila c este vizibilă doar la nivelul
blocului în care a fost declarată.

Exemplu:
void t()
{ int b•4t
{ int c • 3;
cout<<b<< " "<<c;
}
}
'Aanual de informatică pentru clasa a Xl-a 49

(? .. În programul următor am declarat trei variabile, toate numite a . Una este


CX: globală, iar două sunt locale, dar declarate în blocuri diferite:
#include <iostream.h>
int a;
void t ()
{ int a=4;
{ int a=3;
cout<<a<<endl;
}
cout<<a<<endl;
}

main()
{ a=S;
t();
cout<<a;
}

in program, se afişează conţinutul tuturor acestor variabile (3, 4, s).

f · , Observaţii

./ În cazul în care, într-un anumit bloc sunt vizibile (se pot accesa) mai multe
variabile, toate cu acelaşi nume, dar au domenii de vizibilitate diferite, se
accesează variabila cu vizibilitatea cea mai mică. De exemplu, dacă în
programul anterior se tipăreşte variabila a din cadrul subblocului funcţiei, se
tipăreşte 3, pentru că acesta este conţinutul variabilei cu cea mai mică
vizibilitate (cea declarată în subblocul respectiv).

./ _!=xistă posibilitatea ca, un ciclu for să conţin~ declaraţia unei variabile locale.
In secvenţa următoare se calculează suma primelor 4 numere naturale.
Variabila i este declarată (şi în consecinţă vizibilă) doar în blocul for:

int n=4, s=O; •


for (int i=l;i<=n;i++ ) s+=i;
cout <<s;

3. Durata de viaţă a variabilelor locale este atât timp cât durează execuţia blocului
respectiv.

2.3.4. Transmiterea parametrilor

Aşa cum am arătat, parametrii care se găsesc în antetul funcţiei se numesc


parametri formali, iar cei care se găsesc în instrucţiunea de apel se numesc
parametri efectivi.

<dz::·. Priviţi programul următor. Acesta conţine o funcţie care calculează suma
~~ · a două numere naturale.
50 Capitolul 2. Subprograme

#include <iostream.h> Parametrii formali sunt a şi b.


int suma(int a , int b) Funeţia este apelată de mai
{ return a+b; multe ori. Parametrii efectivi sunt,
} pe rând:
main() 7 · 2, 3;
{ int c=4,d=3; 7 2+7, 3-1*2;
cout<<suma(2,3)<<endl;
cout<<suma(2+7,3-1*2)<<e nd1; 7 c, d;
cout<<suma(c,d)<<end1; 7 1. 9, 3. 3.
cout<<suma(1.9,3.3)<<end l;
}

După fiecare apel al funcţiei se tipăreşte suma obţinută. Între parametrii


formali şi cei efectivi trebuie să existe o anumită concordanţă, care este descrisă
prin regulile t:rmătoare:

• Numărul parametrilor formali trebuie să coincidă cu numărul


parametrilor efectivi. La această regulă. există o excepţie care va fi
prezentată într-un paragraf separat. In exemplul dat, numărul
parametrilor formali este 2, iar cel al parametrilor efectivi este tot 2
(pentru un apel).
• Tipul parametrilor formali trebuie să coincidă cu tipul parametrilor
efectiv! sau tipul parametrilor efectiv! să poată fi convertit implicit
către tipul parametrilor formali, la fel ca în cazul atribuirii.

suma< 2, 3 > - parametrii formali sunt expresii constante de tip întreg;

swna(2+7, 3-1*2) - parametrii formali sunt expresii constante de tip întreg;


în acest caz, înainte de apel se evaluează expresiile de mai sus;

suma(c,d) - parametrii formali sunt expresii de tip întreg (dat de tipul


variabilelor);

suma< 1. 9, 3 . 3 > - parametrii formali sunt expresii constante de tip real. în


acest caz, ele sunt convertite către int la fel ca în cazul atribuirii (se
trunchiază zecimala). Prin urmare, suma calculată este 4=1+3.

~ Nu este obligatoriu ca numele parametrilor formali să coincidă cu numele


1• parametrilor efectivi.

Vom analiza modul în care sunt memoraţi parametrii transmişi în momentul


lansăriiîn execuţie a funcţiei.

-+ Pentru memorarea parametrilor subprogramele folosesc segmentul de


stivă,întocmai ca pentru variabilele locale.

-+ Memorarea parametrilor transmişi se face în ordinea în care aceştia


figureaza în antet: de la stânga fa dreapta.
Manual de informatică pentru clasa a Xl-a 51

-+ În cadrul subprogramului, parametrii transmişi şi memoraţi în stivă sunt


variabil.e. Numele lor este cel din lista parametrilor formali.

-+ Variabilele obţinute în urma memorării parametrilor transmişi sunt variabile


locale.

lată, de exemplu, cum sunt memoraţi în stivă parametrii în cazul primului apel:

stiva ---+1 2 1 3

a b

L Observaţii

/ la revenirea în blocul apelant, conţinutul variabilelor memorate în stivă se
pierde- dupa cum ştim, durata de viaţa a variabilelor locale este locală.

/ Memorarea în stivă are şi alte consecinţe, dar acestea vor fi tratate la


momentul potrivit.

Există două mecanisme de transmitere a parametrilor, transmiterea prin


valoare şi transmiterea prin referinţă. Acestea vor fi tratate în continuare.

A. Transmiterea prin valoare se utilizează atunci când suntem interesaţi ca


subprogramul sa lucreze cu acea valoare, dar în prelucrare, nu ne
interesează ca parametrul efectiv (cel din blocul apelant) să reţină valoarea
modificată în subprogram.

Se pot transmite prin valoare:

1. Valorile reţlnute de variabile;


2. Expresii (acestea pot conţine şi funcţii).

1. Valorile reţlnute de variabile. În acest caz, parametrii efectivi trebuie să fie


numele variabilelor care se trimit prin valoare.

Exemplu:
#include <iostream.h>
void test (int n)
{ n+=l;
cout<<n<<endll
}

main()
{ int n•l1
test (n);
cout<<n<<endl;
}

Parametrul n este transmis prin valoare.


52 Capitolul 2. Subprograme

~ in main ( ) , avem declarată variabila n, care este iniţializată cu 1.

~ Apelăm funcţia. La apel, se rezervă spaţiu în stivă, spaţiu care are numele
parametrului (deci tot n) şi este initializat cu valoarea memorată de variabila
n a programului principal. in acest 'moment avem două variabile n şi ambele
reţin valoarea 1.

~ in funcţie, variabila n este incrementată (adică la vechiul conţinut se adaugă 1).


Evident, este vorba de variabila rezervată în cadrul ei.

~ Tipărim conţinutul variabilei n (cea din stivă), deci se tipăreşte 2.

~ La ieşirea din funcţie, variabila n (din stivă) se pierde - adică nu mai are
spaţiu alocat. Prin urmare, valoarea 2 este pierdută.

~ in main ( ) se tipăreşte conţinutul variabilei n, adică 1.

2. Parametrii efectlvl sunt expresii care mal întâi se evaluează.


Exemplu:
#include <iostream.h>
void test (int n)
{ cout<<n<<endl;
}

main()
{ test(3);
test(3+4*5);
}

~ Transmiterea prin valoare a masivelor permite ca funqiile ~ intoar~ noile


valori ale acestora (care au fost atribuite in funqii). De ce? Numele masivului
este un pointer către componentele lui. Prin valoare se transmite acest
nume. Cu ajutorul acestuia accesăm componentele masivului.

~; :i;,. in programul următor funcţia vector iniţializează vectorul transmis ca


~~ : parametru, iar în main( > se afişează rezultatul:
#include <iostream.h>
void vector (int x(lOJ)
{ for (int i•O;i<lO;i++) x[i) •i;
}

main()
{ int a[lO];
vector(a)J
for (int i•O;i<lOJi++) cout<<a(i)<<" "1
}
"'anual de informatică pentru clasa a Xl-a 53

; . Transmiterea prin referinţă. Parametrii sunt transmişi prin referinţă atunci


:.ând ne interesează ca la revenirea din subprogram, variabila transmisă să reţină
aloarea stabilită în timpul execuţiei subprogramului.

~ in cazul transmiterii prin referinţă, parametrii efectivi trebuie să fie referinţe la


variabile.

~ În cazul transmiterii prin referinţă, subprogramul reţine în stivă adresa


variabilei.

;::: • Programul următor utilizează o funcţie care interschimbă valorile reţinute


-X. de două variabile. Acestea sunt transmise prin referinţă .

#include <iostream.h>
void intersc(int &a,int &b)
{ int man=a;
a=b;
b=man;
}

main()
{ int x=2,:y=3;
intersc(x,y) 1
cout<<x<<" "<<y;
}

2. 3.5. Definirea şi declararea unui subprogram

Deşi aparent asemă nătoare, cele două noţiuni diferă . Buna înţelegere a lor
-e ajută să evităm anumite erori. Mai mult, aceste noţiuni sunt utilizate de orice
"'lbaj de programare, nu numai în C++.

Definiţia 2.12. A defini un subprogram, inseamn~ a-/ scrie efectiv, după


structura anterior prezentată. O problemă importantă este locul unde se
defineşte subprogramul.

Definiţia 2.13. A declara un subprogram, inseamn~ a-1 anunţa. Un


subprogram nedeclarat nu poate fi folosit.

Definiţia unui subprogram ţine loc şi de declaraţie!

Programul următor conţine două funcţii: sl şi s2. Definiţiile ambelor


Ex: funcţiise găsesc înaintea funcţiei main (). Din acest motiv ele pot fi
apelate din main ( ) . Definiţia funcţiei sl este înaintea definiţiei lui s2,
deci funcţia sl poate fi apelată din s2. În schimb, din sl nu poate fi
apelată funcţia s2, pentru că definiţia lui s2 este după cea a lui sl.
54 Capitolul 2. Subprograme

#include <iostream . h>


void sl()
{ cout<<"Eu sunt sl"<<endl;
}

void s2()
{ s1 (); cout<<"Eu sunt s2"<<endl;
}

main()
{ s1 () 1
s2();

În situaţia prezentată, totuşi se poate ca sl să apeleze pe s2, chiar dacă


sunt definite în aceeaşi ordine.

În astfel de cazuri se foloseşte prototipul funcţiei (antetul urmat de "; ").


Prototipul are rolul de a declara o funcţie. El nu conţine definiţia acesteia.

#include <iostream.h>
void s2();
void sl()
{ s2 () 1 cout<<"Eu sunt sl"<<endl;
}

void s2()
{ cout<<"Eu sunt s2•<<endl;
}

main()
{ sl ();
}

f
, (, l
Programatorii în C++ obişnuiesc să scrie mai întâi prototipurile tuturor
• funcţiilorutilizate de program - fără main () - iar după funcţia ma in ( > să le
definească. În acest fel, orice funcţie - mai puţin ma in ( ) - poate fi apelată
din oricare alta:
#include <iostream.h>
void sl(); // prototip sl
void s2(); // prototip s2
main()
{ sl();
}

void al()
{ s2 (); cout<<"Eu sunt sl" <<endl;
}

void s2 ()
{ sl (); cout<<"Eu sunt s2"<<endl;
}
~an ual de informatică pentru clasa a Xl- a ss

2 .4. Aplicaţii care folosesc subprograme ··

::1 Aplicaţia 2.1. Revenim la exemplul de funcţie dat în paragraful 2.1. Acesta va fi
:-ezentat în totalitate. Se consideră funcţia:

x+ 1 , pentru xe [-1,1];
2
f(x)= ~-\Î, pentru xe(-oo,-1};

1 - -,
1 +X
pentru x e ( l , oo ).

Se citesc doua valori reale a şi b. sa se scrie un program care afişeaza care


: 1tre valorile f (a) şi f (b) este cea mal mare.

;::1 Rezolvare. Programul este prezentat în continuare:


"" ., .
!• :, Varianta Pasc,~~ . r,.
var a,b,va,vbsreal; # include <iostream.h>
function f(xsreal)sreal; float f ( float x)
var y:real; ( float Y1
begin y•x+11
y:=x+l1 if{x<-1) return y;
if x<-1 then fs • y else if{x>l) return 6/y1
else if x>l then f:s6/y else return y/(l+x*x);
else f:•y/(l+x*x)
end;
void main()
begin ( float a,b,va,vb;
write(•a~'l; readln(a)1 cout<<"a•"1 cin>>a;
write('b='); readln(b); cout<<"b•"; cin>>b;
vas•f(a)1 va•f (a) 1
vb:•f {b) 1 vb• f.(b) 1
if va>vb if (va>vb) cout<<va<<endl1
then writeln(va) elae cout<<vb<<endl1
alse writeln(vb) }
end.

J Aplicaţia 2.2. Se citesc două numere intregi m şi n . Se cere să se tiparească


:el mai mare divizor comun şi cel mai mic multiplu comun al lor.

~ Rezolvare. Cunoaşteţi deja modul in care se calculeaza cel mai mare divizor
:omun pentru două numere naturale date. Cel mai mic multiplu comun a două
"Umere se poate determina impărţind produsul lor la cel mal mare divizor comun al
or. Prin urmare, scriem o funcţie cmmdc cu doi parametri formali m şi n , care
·.,toarce o valoare intreagă- cel mai mare divizor comun al lor.
56 Capitolul 2. Subprograme

var m, n:integer; #include <iostream.h>


function cmmdc(m,n:integer) int cmmdc(int m, int n)
:integer; { while (ml •n)
begin if (m>n) m-=n;
while m<>n do else n-=m;
if m>n then m:-m-n return m;
else n::n-m; }
cnundc:=m;
void main()
end;
{ int cm,m,n;
begin cout<<"m•"; cin>>m;
write ( ' m='); readln(m)l cout<<"n•" ; cin>>n;
write ('n='); readln(n) ; cm=cmmdc (m,n) 1
writeln(cmmdc(m,n)); cout<<cm<<" "<<m*n/cm;
writeln(m*n div cmmdc(m,n)); }
end .

~Exerciţii
1. Găs iţi două motive pentru care parametrii funcţiei cmmdc trebuie să fie transmişi
prin valoare şi nu prin referinţă.

2. Scrieţi
un subprogram cmmmo care să returneze cel mai mic multiplu comun a
două numere naturale. Stabiliţi locul unde trebuie integrat acesta in programul dat
şi modificaţi programul principal astfel fncât să se utilizeze subprogramul cerut.

3. Câte expresii diferite care au ca rezultat cel mai mare divizor comun al
numerelor naturale nenule a, b şi o există? ln orice expresie se utilizează numai
apeluri ale funcţiei ollDl\do şi variabilele a, b şi c. De exemplu, trei expresii sunt:
cmmdo(ommdc(a,b),c),
ommdc ( cmmdc (b, a), o) şi
cmmdc(o,cmmdc(a,b) ).

O Aplicaţia 2.3. Se citeşte un vector cu n componente numere intregi. Se cere să


se tipărească cel mai mare divizor comun al valorilor reţinute de vector.

~ Rezolvare. Dispunem deja de o funcţie care calculează cel mai mare divizor
comun a două numere şi am testat-o în programul anterior. Pentru varietate, de
data aceasta implementăm o funcţie cu algoritmul lui Euclid pentru calculul celui
mai mare divizor comun, algoritm prin împărţiri, nu prin scăderi repetate. Cu
ajutorul ei calculăm cel mai mare divizor comun pentru valorile reţinute de primele
două componente şi memorăm această valoare în variabila o. Apoi, pentru fiecare
dintre componentele următoare, se calculează cel mai mare divizor comun intre
valoare.a curentă memorată in o şi cea reţinută de componenta curentă a
vectorului. Cel mai mare divizor comun va fi reţinut din nou de c.
Manual de informatică pentru clasa a Xl-a 57

var v:array(1 •. 20]of integer; #include d :ostream. h>


n,i,csint eger; int v[21),n;
function emmdc(m ,n:integer ) int cmmdc(in t m, int n)
sinteger1 { int r;
var r:intege r; do { r=m%n;
begin m-n;
repeat n=-r;
r:•m mod n; ) while (ri=O)
ms•n; n:•r return m1
until r•O; }

end; void main()


{ int i , c,
begin cout<<"n •"; cin> >n;
write ('n='); readln(n ); cout<<"Co mponente vector:" ;
write( •compone nte vect.or: •); for(i•l ;i<=n ; i++)
for i:•1 ton do read(v[i ]); cin»v[i ];
c:•cmmd c(v[1],v[ 2])1 c•cmmdc (v[l],v[:ll ]);
for i:• 3 ton do for (i=3;i<•n ;i++)
c:•cmmd c(c,v[i]) ; c•cmmdc (c,v[i])l
writeln( c) cout<<c;
end. }

~Exerciţiu
In locul secvenţei
c ~ cmmdc(v[ l) ,v[:ll))
pentru i =3, n execută
c ~ cmmdc(c ,v[i))
sfârtit pentru
dorim să utilizăm, mal compact, secvenţa:

pentru i=l,n execută


c ~ cmmdc(c ,v[il)
sfârşit pentru

Care este valoarea pe care trebuie să o aibă variabila c inaintea acestei secven~:?

0 Aplicaţia 2.4. Triunghi special. Se citesc două numere naturale m şi n mal .


mici decât 10. Se cere ca programul dvs. să afişeze un triunghi, după regulile pe
care le deduceţi din exemplele următoare :

1. m•6 n•1 2. m•7 n•9

1 9
2 3 l :Il
4 5 6 3 4 5
7 8 9 1 6 7 8 9
2 3 4 5 6 1 2 3 4 5
7 8 9 1 2 3 6 7 8 9 1 :Il
3 4 5 6 7 8 9 Junior Division
58 Capitolul 2. Subprograme

0 Rezolvare. Se observă că dacă n=S trebuie să obţinem şirul s 1 61 7 1 a1


9, 1. , 2, 3, • • • Problema este de a putea num.ăra începând de la n, iar când
s-a depăşit valoarea 9, de a relua numărarea de la 1. Vom utiliza o funcţie (succ)
care primeşte ca parametru de intrare un număr k şi returnează valoarea
următoare, în logica prezentată mai sus.

Alt aspect al problemei este realizarea tipăririi acestor valori pe m linii, linia 1
având o singură valoare, linia a doua două valori etc. Algoritmul este implementat
în procedura tipar, care are ca parametri formali pe m şi n, transmişi prin valoare.
Valorile lor sunt citite în programul principal.

var m,n:integer; #include <iostream.h>


function suc(k:integer):integer; int suc(int k)
begin { if {k==9) return 11
if k=9 then succ:=l else return k+l;
else succzsk+l }
end;
void tipar(int m,int n)
procedura tipar(m,n:integer); ( int i,j;
var i,j : integer; for(i=1;i<=m;i++)
begin ( for (j=l;j<•i;j++)
for i : =1 to m do begin { cout<<n;
for j: • l t o i do begin n•suc(n);
write (n); }
n : =suc(n) cout<<endl;
end; }
writeln }
end
end; void main()
( int m,n;
begin
cout<<"m="; cin>>m;
write('m='); readln(m); cout<<"n•"; cin>>n;
write('n•'); readln(n); tipar(m,n);
tipar(m,n) }
end .

~ Exerciţii
1. După apelul tipar(mln) din programul principal, dacă se adaugă o instruc-
ţiune care afişează valoarea lui n, ce valoare estimaţi că se va tipări?
2. Cum trebuie să arate definiţia subprogramului suc, dacă în subprogramul
tipar, în loc de n~suc (n), se utilizează instrucţiunea suc (n)?
3. Cum putem defini subprogramul suc astfel încât secvenţa de afişare din
subprogramul tipar să fie:
pentru i=l,m execută
pentru j=1,i execută
scrie suc(n)
sfârşit pentru
scrie EOLN
sfârşit pentru
V anual de informatică pentru clasa a Xl-a 59

:J Aplicaţia 2.5. Se citesc două numere naturale m<n. Se cere să' se tipărească
· ~te
numerele palindrom aflate între m şi n. Un număr este paling;om dacă, citit de
r- stânga la dreapta
şi citit de la dreapta către stânga, rezultatul este acelaşi. De
~em plu, numărull2l este palindrom.

~ Rezolvare. Să analizăm problema. Este necesar să fie testate toate numerele


·-tre m şi n, pentru ca apoi să fie tipărite numai cele care îndeplinesc condiţia cerută .

.Pentru fiecare număr, avem subproblema verificării proprietăţii de număr


::;alindromic. Pentru aceasta, vom scrie o funcţie numită palin care are rolul de a
:=sta dacă un număr este sau nu palindrom. Analiza proprietăţii de număr
=alindromic o efectuăm construind răsturnatul (inversul) acelui număr şi verificăm
:acă cele două valori (numărul şi răsturnatul său) sunt egale.

Construirea "inversului" (nu în sens matematic) unui număr natural este şi ea


: su bproblemă a problemei verificării proprietăţii de palindrom. Vom scrie o funcţie
::..nvers, care va returna inversul unui număr natural transmis ca parametru .

Urmărind apelurile, vom constata că programul principal apelează funcţia


~.alin, iar funcţia palin apelează la rândul ej funcţia invers. Programul apelant
3 fu ncţiei palin este programul principal, în timp ce programul apelant al funcţiei
::..Dvers este subprogramul palin.

Care este avantajul scrierii fiecărei funcţii? Acesta este dat de faptul că,
s:unci când scriem funcţia, ne concentrăm exclusiv asupra ei, deci posibilitatea de
s g reşi este mai mică . Dacă dispunem de funcţie, algoritmul in continuare devine
1ult mai simplu. În plus, este posibil ca oricare dintre funcţii să poată fi folosită şi în
3 te cazuri, la rezolvarea altor probleme.

f Observaţii

•"' Ordinea definirii celor două subprograme devine importantă, deoarece funcţia
in~ers, de care are nevoie funcţia palin, trebuie declarată anterior acesteia.

../ Faptul că numărul al cărui invers se calculează este transmis subprogramului


prin valoare este iarăşi un avantaj. Cu toate că algoritmul ce construieşte
inversul lui n distruge de fapt valoarea acestuia, acest lucru nu este resimţit de
programul apelant.

function invers(n:1ongint):longint; #include <iostream.h>


var r:longint;
begin long invers(long n)
r:=O; { long r=O;
while n<>O do begin while(ni=O) {
r:=r*10+n mod 10; r=r*10+n%10;
n:=n div 10 n/=10;
}
end;
invers:=r return r;
}
end;
60 Capitolul 2. Subprograme

function palin(n:longint):boolean; int palin(long n)


begin { return invers(n)==n;
palin:=invers(n)=n; }
end;
void main()
var m,n,i:integer; { .:l.nt m,n,i;
begin cout<<"m="; cin>>m;
write('m='); readln(m); cout<<"n="; cin>>n;
write('n='); readln(n); for(i=m;i<=n;i++)
for i:~m to n do if (palin(i))
if palin(i) then writeln(i) ; cout<<i<<endl;
end. }

/ Exerciţii
1. Modificaţi antetul funcţiei invers, transformând parametrul n în parametru
transmis prin referinţă şi urmăriţi efectele modificării efectuate.

2. Modificaţi antetul funcţiei invers, transformând parametrul n în parametru


tr-ansmis prin referinţă şi testaţi proprietatea de palindrom folosind compararea
n=invers (n) în loc de invers (n) =n. Explicaţi de ce această modificare nu
reprezintă o corecţie a erorii apărute la cerinţa 1?

3. Un număr natural este superpallndrom dacă este palindrom atât el cât şi


pătratul său. Scrieţi, pe baza programului dat, un program care listează toate
numerele cu această proprietate aflate între doi întregi a şi b (a<b<32000).

O Aplicaţia 2.6. Suma cuburilor cifrelor. Se citeşte un număr natural


n~9999999. Se calculează suma cuburilor cifrelor sale. Exemplu: dacă se citeşte
25, se calculează 2 3 +5 3 =133. Cu numărul obţin ut procedăm la fel: 1 3 +3 3 +3 3 =55 .
Repetăm procedeul: 5 3 +5 3 =250 . Şi iar: 2 3 +5 3 +0 3 =133 . Repetăm procedeul până
ce obţinem un număr care este deja prezent în seria generată . Se cere să se
afişeze şirul de valori calculate prin acest procedeu.

Exemplu: pentru n=25, se afişează seria

25 133 55 250 133.

.Junior Divlslon

0 Rezolvare. Suma cuburilor cifrelor unui număr natural este calculată de funcţia
swna. Numărul n (citit) va ri memorat de prima compon entă a vectorului seria.
Apoi, suma cuburilor sale, de componenta a doua, suma cuburilor numărului reţinut
de a doua componentă va fi reţinută de a treia componentă, ş.a.m.d. După ce este
memorată o valoare, ea este comparată, pe rând, cu cele obţinute anterior. Dacă
este regăsită, se tipăreşte numărul de termeni ai seriei şi seria propriu-zisă
Funcţia swna calculează suma cuburilor cifrelor unui număr. Funcţia este verifică
dacă valoarea n există deja în şirul s format din ls componente.
l.lanual de informatică pentru clasa a Xl-a 61

type vec•array[1 . .100] of longint; #include <iostream. h>


var nzlongint; seria:vee;
ind,i:inte ger; long n;
int seria[100 ],ind,i;
function suma(nzlon gint):longi nt;
var s,i,c:inte ger; int suma(int n)
begin { int s•O,c;
s:•O; while (n) {
while n<>O do c•n%10;
begin s+•c•c*c;
c:•n mod 10; n/=10;
s:•s+c*c*c ; }
n:=n div 10 return s;
end; }
suma:•s int este(long n,int s[100],
end; int ls)
function este(n:lon gint;var s:vec; { int i;
ls:integer) :boolean; for(i•O;i< ls;i++)
var i:integer; if(s[il••n ) return O;
begin return O;
este:•true ; }
for i:•1 to ls do void main()
if s[i)•n then este:•fals e { cout<<"n•• ; cin>>n;
end; ind.. O;
begin seria[ind) •n;
write('n• '); readln(n); do {
seria[l):• n; n•suma(se ria[ind-1]) ;
ind:al; seria[++in d]•n;
repeat ) while(l este(n,ser ia,
n:• suma(seria [ind-1)); ind-1));
ind:•ind+1 ; for (i•1;i<•ind ;i++)
seria(ind] :•n; cout<<seri a[i)<<endl ;
until este(n,ser ia,ind-1); }
for i:•l to ind do
writeln(s eria[i]);
end.

0 Aplicaţia 2.7. Să se scrie o procedură care citeşte o matrice cu elemente


1umere tntregi din fişierul text MAT. orxor. Pe prima linie a fişierului sunt două valori:
m (numărul de linii ale matricei) şi n (numărul de coloane ale matricei). Următoarele m
nii ale fişierului conţin, rn ordine, elementele aflate pe fiecare linie a matricei.
Verificaţi subprogramul definit, integrându-1 într-un program de testare.

Exemplu: fişierul f • in conţine:

3 4
3 1 7 g
1 2 3 4
9 1 3 8
62 Capitolul 2. Subprograme

0 Rezolvare. Pentru a putea fi folosită pentru citirea oricărei matrice de numere


intregi, procedura va trebui să utilizeze ca parametri .de ieşire matricea, numărul de
linii şi numărul de coloane ale matricei. '

Tn programul următor se testează procedura, prin listarea matricei:

type mataarray[1 •• 100,1 •• 100] #include <fstream.h>


of integer;
void citesc(int mat[100] [100],
var m:mat;
int&: m, int&: n)
linii,coloane,i,j:intege r;
{ fstream f("fis.txt•,ios::in);
procedura citesc(var t:mat; f>>m>>n;
var m,n:integer); for (int i=1;i<=m;i++)
var i,j:integer; for (int j•1;j<=n;j++)
f:text; f»mat [il [j];
begin f .close() 1
assign(f, 1 mat.txt 1 )J }
reset(f);
void main()
readln(f,m,n) 1
{ int m,n, mat[100) [100];
for i:=1 to m do
citmat(m,n,mat);
for j:•1 ton do
for (int i • l ; i<-.m;i++)
read(f,t[i,j]);
close(f) { for (int :i • l;j<•n;j++)
cout<<mat [il [j] <<• ";
end;
cout<<endl;
begin
citesc(m,linii,coloane); }
for i :."' 1 to linii do
bagin
for j:•l to coloane do
write(m[i,j], 1 1 ) 1
writeln
end
end.

Probleme propuse

1. Scrieţi un program care calculează şi afişează valoarea fiecăreia dintre expresiile


de mai jos, pentru x citit de la tastatură:

a) G(x) =sin(x)+cos(x)•cos(2•x);
b) H(x) = 10•{x} (am notat prin {x} partea fracţionară a lui x).
'anual de informatică pentru clasa a Xl-a 63

2. Coifurile NFL. Există 28 de echipe de fotbal în NFL (Liga Naţională


:e Fotbal în SUA). Unele magazine au aparate care oferă _ coifuri în
""'niatură ale echipelor contra o. 25$. Când pui moneda în aparat, nu
ş:;i ce coif vei obţine, oricare din cele 28 de coifuri diferite îţi poate veni
: n aparat (sunt oferite aleator). Scrieţi un program care simulează punerea
-onezii într-un astfel de aparat până când se obţin toate cele 28 de coifuri.
:>rogramul trebuie să tipărească şi totalul cheltuielilor efectuate pentru obţinerea
:elor 28 de coifuri.
Junior Divislon

~ Indicaţie. Utilizaţi subprogramele predefinite Random şi Randomize.

3. Scrieţi o funcţie care primeşte ca parametru lungimea laturii unui pătrat şi


·eturnează aria sa.
~. Scrieţi un subprogram care primeşte ca parametru lungimea laturii unui pătrat şi
·eturnează atât lungimea diagonalei, cât şi perimetrul pătratului.
5. Scrieţi o funcţie care primeşte ca parametri de intrare lungimile celor două catete
ale unui triunghi dreptunghic ş i returnează lungimea ipotenuzei.
6. Scrieţi o funcţie care primeşte 3 parametri de tip real, cu semn ificaţia de lungimi
oentru 3 segmente. Funcţia va returna 1 dacă cele trei segmente pot forma un
triunghi şi o, in caz contrar.

7.Scrieţi o funcţie care returnează prima cifră a unui .,număr natural. De exemplu,
dacă parametrul efectiv este 127, funcţia va returna 1.

8. "Numai ultima cifră". Se citesc n (număr natural, n<100) ş i n numere naturale


nenule x1 x2 x3 •• • xn. numere mai mici ca 30000. Se cere:

• ultima cifră a numărulu i x 1 + x 2 +....+x ,.;


• ultima cifră a numărul ui x(1.r3 .•.•.r•.

• 8 pentru că ultima cifră a sumei 11+4+3 este 8;


• 1 pentru că ultima cifra a lui 1112 este 1.

Concursul Naţional al Elevilor, Sinaia

9. Să se citească două matrice şi să se efectueze suma lor. Programul se va


realiza astfel:

• se scrie un subprogram de citire a unei matrice cu m linii şi n coloane;


• se scrie un subprogram de tipări re a unei matrice cu m linii şi n coloane;
• se scrie un subprogram care adună două matrice;
" prv gramul principal rezultă din apelul acestor subprograme.
64 Capitolul 2. Subprograme

10. Să se calculeze coeficienţii polinomului P(x) = (x+a)n, în două variante.

a) Programul utilizează un subprogram care fnmulţeşte un polinom oarecare


de grad k (coeficienţii se găsesc într-un vector, transmis prin referinţă)
cu polinomul x+a.

b} Programul utilizează un subprogram care calculează coeficienţii, aşa cum


rezultă din formula (binomul lui Newton):

11

cx+a)" =Ic,~ak x"- k.


k =O

11. Să se tipărească toate numerele prime aflate între doi întregi citiţi. Programul
va folosi o funcţie care testează dacă un număr este prim sau nu.

12. Scrieţi un program care tipăreşte numerele întregi găsite între două valori citite,
numere. care se divid cu suma cifrelor lor. Programul va utiliza o funcţie care
returnează suma cifrelor unui număr întreg primit ca parametr.u .

13. Să se scrie o procedură care permută două linii date ale unei matrice pătratice.

14. Scrieţ! o procedură care interclasează doi vectori sortaţi ştiind că aceştia sunt
ordonaţi descrescător.

15. Se citesc două polinoame de grade m şi n (coeficienţii fiecărui polinom se citesc


într-un vector). Să se afişeze coeficienţii polinomului sumă şi cei ai polinomului
produs. Jn ambele cazuri se vor utiliza proceduri de citire, adunare, înmulţire.

16. Se citesc 3 valori a, b, n (a şi b reale, n întreg). Se cere să se calculeze valorile


unei funcţii F, definită pe intervalul [a, b] în n+l puncte rezultate din împărţirea
Intervalului [a,b] în n părţi egale şi să se afişeze cea mai mare dintre aceste
valori. Funcţia F va fi dată sub forma unui subprogram de tip funcţie.

17. Un număr raţional


este reţinut prin utilizarea unui vector cu două componente.
Scrieţi subprogramele care adună, scad, înmu lţesc şi împart două numere
raţionale . Subprogramele trebuie să returneze numărul simplificat.

18. Design triunghiular. Triunghiul de mai jos este generat de un algoritm.


Descoperiţi acest algoritm şi folosiţi-! pentru a scrie un program care citeşte
numărul n de linii şi generează triunghiul cu n linii corespunzător.

1
232
34543
4567654
567898765
67890109876
7890123210987

Junior Division
_-_a. de informatică pentru clasa a Xl-a 65

... :a afişează programul următor?


·, Var_ianta·Pasca.! ,. ,
T3..: z:integer; #include <iostream. h>
;=ocedure test(a:int eger); void test(int a)
~in { a++;
a:=a+l; write{a); cout<<a;
c.d; }

=egin void main()


z: =S; { int z=S;
t est(z); test(z);
write(z) cout<<z;
end . }

~ 6 6; b) 5 6; c) 6 6; d) 6 s.
:~ . Ce afi şează programul următor?
o!j'! ·• '., ' ' 1·"
.~ Varianta Pascal ' Varianta C++'·
function f(x:intege r):integer ; #include <iostream. h>
begin int f(int x)
f:=x+l { return x+1;
and; }
procedura t(a:intege r); void t(int a)
begin { a++; cout<<a;
a:=a+l; writeln(a) ; }
and;
void main()
begin { t(f(f(f(1) )));
t(f(f(f(l) ))) }
end.

a) 3; b) 4; d) eroare de sintaxă.

21. Ce afişează programul următor:


1
Varianta P.ascal
•IJ'i';•J,,I li,•,. .11,.
V~rianta C# ~~·!; 1J!I!'I!:I·~" '.
'
var a:integer; #include <iostream. h>
int a;
function f:integer;
var a:integer; int f()
begin { int a • 3;
a:=3; cout<<a;
write(a); return a;
f:=a; }
end; void main()
begin { a•2;
a:=2; cout<<a;
wri t eln(a,f); cout<<f();
end. }

a) 233; b) 23; c) 32; d) 232.


66 Capitolul 2. Subprograme

22. Ştiind că valorile citite ale variabilelor a şi b sunt numere naturale nenule, care
dintre secvenţe utilizează subprogramul increment...pentru a obţine produsul celor
două numere?

var a,b,i,j,t:integer; #include <iostream.h>


procedura increment(var x:integer); int a,b,i,j,t;
begin void increment(int& x)
x:=x+1 { X++; }
end; void main()
begin { cin>>a;
readln(a); readln(b); cin>>b;
ts=a; t=a;

writeln(t) cout<<t;
end. }

a) for i:=l to a do for(i=1;i<=a;i++)


for j:=1 tob do for(jal;j<=b;j++)
increment(t); increment(t);
b) for it•l to a do for(i=1;i<=a;i++)
for j:=1 to b-1 do for(jal;j<b;j++)
increment(t); increment(t);
c) for i:=l to b do for(i=l;i<=b;i++)
for j : =l t o t do for(j=l;j<=t;j++)
increment(t); increment(t);
d) for i:=l to a do for(i=l;i<=b;i++)
increment(t); increment(t);

23. Pentru varianta de limbaj preferată, stabiliţi care dintre următoarele antete de
subprogram sunt corecte:
a) procedura p(n:integer; var void p(int n, int& v[10))
v: array[l. .101 of integer);
b) function p(n:integer;var p(int n, float& v)
v:real);
c) procedura p(n:integer;v:real); void p(int n, float v)
d) functio~ p(n):integer; int p(int& n; float v)

24. Pentru funcţiile definite mai jos, care dintre afirmaţii este falsă?

function f(xsreal):real; float f(float x)


begin { if (x<3) return 3*x-1;
if x<3 then f:=3*x+l else return x-1;
else f t=x-1 }
end;
function g(x:real):real; float g(float x)
begin { if (x<lO) return 4*x;
if x<10 then g:=4*x else return x-1;
}
else g:=x-1
end;
, - i.Jal de informatică pentru clasa a Xl-a 67

a> Expresia f ( g ( 3) ) produce valoarea 11;


b) Expresia g ( f ( 3 ) ) produce valoarea a;
c > Expresia f ( g ( f ( 3 ) ) > produce valoarea 7;
d) Expresia (f+g) (3) produce valoarea 14.

~ Fiind dată funcţia următoare, care dintre afirmaţii sunt adevărate?

· varianta Pascal
~ction Cat(n1,n2:in teger): long Cat(int n1,int n2)
longint; { int inv=O;
Tar inv:integer; while (n21a0)
~gin { inv=inv*10+n2 %10;
inv:=O; n2=n2/10;
while n2<>0 do begin }
inv:•inv*10+ n2 mod 10; while (invi=O)
n2:=n2 div 10 { nl=nl*lO+inv% 10;
end; inv=inv/10;
while inv<>O do begin
n1:=n1*10+in v mod 10; return nl;
inv:•inv div 10
end;
Cat:=nl
end;

a) cat(12,10) returnează 1210;


b) Cat(10,12) returnează1012;
c) cat< o, 12) returnează 12;
d) Cat (12, O) returnează 120.

26. Fiind dată funcţia următoare, care dintre afirmaţii este falsă?

long Cat(int nl,int n2)


{ int n2s;
var n2s:integer; n2s=n2;
begin while (n21•0)
n2s:=n2; { n2/=10;
while n2 <>0 do begin n1*=10;
n2:=n2 div 10; }
n1:•n1*10 return n1+n2s;
end; }
Cat:=n1+n2s
end;

a) Cat(-12,13) returnează -1213;


b) cat ( 12, o> returnează 12;
c> Cat (o, 12) returnează 12;
d) Cat (13, 12) returnează 1312.
68 Capitolul 2. Subprograme

27. Se consideră declarările următoare:

1'',__. ;.,:
;, ,, ··
' "':·\ Varianta Pa9cal
. ·
type vect=array [1 •• 40] of byte; void Concat(int A[20], int 1
8(20], int m, int n, int C[40],
procedura Concat(A,B :vect; int&. p)
m,n:b.yte;v ar C1vect;var p:byte);
void Reun(int A[20], int 8[20],
procedura Reun(A,B:v ect; int m, int n, int C[40], int&
m,n:b.yte;v ar C:vect;var p:b.yte); p)

procedura rnters(A,B :vect; void rnters(int A[20], int


m,n:b.yte;v ar C1vect;var p1b.yte); B[20],int m, int n, int C[40],
int&. p)
procedura Dif(A,B:ve ct;m,n:byte ; void Dif(int A[20J, int B[20],
var C1vect;var p:byte); int m, int n , int C[40], int&
p)
procedura Elimin(var A1vect;
var nu byte) ; void Elimin (int A[20],int& m)

în care:

- Conoat are rolul de a returna vectorul c cu p componente, vector obţinut din


concatenarea componentelor vectorului A de m elemente cu ale vectorului B cu n
elemente. De exemplu, din Aa(1,2,3 , 7), m=4, B=(2,5,3) , n=3, se obţine
C=(1,2,3 ,7,2,5,3), pa7,
- Reun are rolul de a returna vectorul c cu p componente , vector obţinut dir
reuniunea mulţimilor de numere naturale din vectorii A cu m elemente şi B cu n
elemente (presupunand că A conţine valori distincte şi B conţine valori distincte)
De exemplu, din A•(1,2,3, 7), m=4, B=(2,5,3) , n=3, se obţine
c- c1 , 2 , 3 , 1 , s >, p• s.

- :rnters are rolul de a returna vectorul c cu p componente, vector obţinut dir


intersecţia mulţimilorde numere naturale din vectorii A cu m elemente şi B cu n
elemente (in aceleaşi condiţii ca şi in cazul reuniunii). De exemplu, <iir
A= (1, 2, 3, 7), m•4, B= (2, 5, 3), na3, se obţine C• (2, 3), p=2.
- c cu p componente, vector obţinut din
Dif are rolul de a returna vectorul
diferenţa mulţimilor
de numere naturale din vectorii A cu m elemente şi B cu n
elemente (in aceleaşi condiţii ca şi fn cazul reuniunii). De exemplu, dir
A•(1,2,3, 7), m=4,B•(2 ,5,3), n=3 , seobţineC=(1,7), p•2.
- Elimin are rolul de a returna vectorul care conţine toate elementele distincte
ale vectorului iniţial cu m componente, prin eliminarea valorilor multiple. De
exemplu, pentru A= ( 1, 1, 2, 3, 2), m=S, se returnează A= ( 1, 2, 3) , m=3 .

A. Se citesc doi vectori x cu a numere naturale şi Y cu b numere naturale


Elementele din fiecare vector nu sunt neapărat distincte. Care dintre secvenţele de
mai jos nu calculează reuniunea mulţimilor numerelor naturale reţinute de cei do
vectori? Rezultatul se va găsi in vectorul z.
2..- Jal de informatică pentru clasa a Xl-a 69

Concat(X,Y,a,b, Z , p);
Elimin(Z,p);
Elimin(X,a); Elimin(Y,b);
Concat(X,Y,a,b,Z,p);
Elimin(Concat(X,Y,a,b ,Z,p),p);
Elimin(X,a); Elimin(Y,b);
Reun(X,Y,a,b,Z,p);

Se citesc doi vectori x cu a numere naturale şi Y cu b numere naturale.


=::e11entele din fiecare vector sunt distincte. Care dintre secvenţele de mai jos
::=.culează intersecţia mulţimilor numerelor naturale reţinute de cei doi vectori?
::-::zultatul se va găsi in vectorul z. ·
Dif(X,Y,a,b,Z,p);
Dif(X,Z,a,p,Z,p);
; Dif(X,a,Y,b,Z,p);
Dif(Y,Z,b,p,Z,p);
: Intersect(X,a,Y,b,Z,p)
~ Intersect(X,Y,a,b,Z,p) ;

"' Se citesc doi vectori x cu a numere naturale şi Y cu b numere naturale.


=ementele din fiecare vector sunt distincte. Care dintre secvenţele de mai jos nu
::c:>cu lează nici x - Y, nici Y-x? Rezultatul se va găsi in vectorul z.

• Inters (X,Y,a,b,Z,p)l
Dif(X, Z,a,p,Z,p);
= Inters( X,Y,a,b,Z,p);
Dif(Y,Z,b,p,Z,p)l
: Inters(Y, X,a,b,Z,p);
Dif(X,Z,a,b,Z,p);
~ Dif(X,Y,a,b,Z,p);

28. Se conside ră subprogramul următor:

procedura Afis (n:integer); void Afis (int n)


var i,j,k:integer; { int i,j,k;
begin k•2*n-1J i =l;
kl•2*n-1;i:=1; while Ci<an)
while i<•n do {for(j=l;]<•(k-2*i+1)/2Jj+ +l
begin caut<<' ';
for j:=l to (k-2*i+l)div 2 do for(j • 1;]<=2*i-1J]++)
write (' '); caut<<'*';
fo.r j: =l to 2*i-l do i++;
write ('*'); caut<<endl;
i:=i+l; }
wr:i.te:n }
end
end;
70 Capitolul 2. Subprograme

A. Dacă n=8, câte caractere"*" se afişează pe ultimul rând tipărit?

a) 8; b) 16; c) 15; d) 17.

B. Dacă n•4, câte caractere"*" se afişează in total (pe toate randurile)?

a) 8; b) 16; C) 12; d) 20.

C. Dacă n•20, eate spaţii se scriu înaintea primului caracter"*", pe penultimul rând?

a) 10; b) 20; c) 19; d) 1.

29. Se consideră subprogramul următor:

type vect•array[l •• lO] of byte; void xx (i nt V[lO] )


{ int A[4) (4],i,j , ZI
procedure xx(var Vrvect);
for (i=O;i<4;i++)
var A:array[1 • . 4,1 .• 4] of byte;
for (j=3;j >=O;j--)
i,j,z:byte;
begin
{ A[i] [j]=V[i]%2;
V[i]/=2;
for ir•l to 4 do
}
for j:=4 dow.nto 1 do
for (i=O;i<4;i++)
begin
{ z=A [O] [ i) 1
A[i,j]r•V[i) mod 2;
for (j=llj<4;j++)
V[i] :=V[i] div 21
end;
z=z*2+A[j] [i) 1
V[i]=z;
for i : •l to 4 do
}
begi.n
}
z r=A[l,i];
for j:=2 to 4 do
zr•z*2+A [j , i] ;
v[i] r•z;
end;
end1

A. Care este conţinutul lui v după apel, dacă inainte este V=< 1, 2, 3, 4)?
a) V=(1 1 2,3 1 4); b) V=(4,3,2 1 1);
c) V=(0,1,6,10 ) ; d) V=(0,4 , 5 1 9) .

B. Care trebuie să fie iniţial conţi nutul lui v, dacă după apel este V= ( 1 1 6 1 10, o)?
a) V=(2,4,6,8); b) V=(1,6,10,0) ;
c) V=(0,10,6,1); d) V=(1,9,3,2) .

C. Pentru care dintre exemplele u rmătoare de configu raţii ale lui v la intrarea în
subprogram, conţi n utu l acestuia la i eşire coincide cu cel de la intrare?
a) V=(13,10,5, 1 1); b) V=(2,3 1 9 1 13);
c) V=(0,1,2,12); d) v .. (1,4,9,15 .
'.1anual de informatică pentru clasa a Xl- a 71

30. Se consideră subprogramul următor:

typevect=array[1 , . 101 of integer; int Cmp(int m, int n,


int A[10], int 8[10])
function Cmp(m,n:integer; { int i=O;
A,B:vect):byte; while (i<=m && i<=n &&
var i:integer; A[il==B[i]) i++;
begin if(i>n && i>m) return O;
i:=l; else if(i>n) return 1;
while (i<=m) and (i<=n) else if(i>m) return -1;
and (A[i]=B[i]) do i:=i+l;
else if(A(i]<B[i])
if (i>n) and (i>m) return -1;
then Clllp:=O el se
else if i>n then Cmp:=1 return 1;
else if i>m then cmp:=-1 }
else if A[i]<B[i]
then Cm;p:=-1
else Cmp:=1;
end;

A. Ce valoare returnează funcţia apelată pentru fiecare dintre cazurile următoare?

a) m=3, n=3, A= ( 1, 2, 3), B= ( 1, 2, 3);


b) m=2, n=3, A=(l,2), B= ( 1, 2, 3);
c) m=2, n=3, A=(l,3), B= ( l, 2, 3);
d) m=l, n=l, A= (1), B= ( 1).

B. Pentru m=S, n=3, A=(1,2 , 5 , 7 , 2), B=(1 , 2 , 0), stabiliţicevaloarepoatefi


a treia componentă a vectorului B astfel încât funcţia să returneze valoarea o.

a) 3; b) s;
c) nu există o astfel de valoare; d) 2 .

C. Pentru X=< 1, O. O,
s , 7), Y= c1, 2, 7), stabiliţi ce valori pot fi scrise în casete
astfel încât apelul CJl\P ( 4, 4, x, Y) să returneze aceeaşi valoare ca şi apelul
cmp ( 4 , 4 , Y, X) .

a) 2 şi respectiv 3; b) 2 şi respectiv s;
c) nu există astfel de valori; d) 3 şi respectiv 6.

O. Pentru care şir de vectori, funcţia Cmp, apelată pentru oricare doi vectori
consecutivi în şir, returnează aceeaşi valoare?

a) A= ( 1, 2, 3) , B= ( 1, 2, 3), C ( 1, 2, 2);
b) A=(1,2), B=(1,2), C=(1,3);
c) A=(1,3), B=(l,2), C=(1,3);
d) Ac ( 2), B= ( 1), c= (O).
72 Capitolul 2. Subprograme

31. Scrieţi subprograme pentru:

A. Calculul valorilor ce caracterizează o sferă (arie şi volum) pe baza valorii razei


sale r.
B. Calculul valorilor caracteristice ale unui con (raza bazei, generatoarea şi fnălţimea)
pe baza valorii volumului conului şi a ariei sale laterale.
C. Obţinerea descompunerii fn factori primi a unui număr natural n.
D. Ordonarea componentelor pozitive ale unui şir de valori, valorile negative
rămânând pe poziţiile lor iniţiale.
E. Determinarea unei subsecvenţe palindromice a unui şir de numere naturale de
cel mult două cifre.
F. Verificarea respectării limitei de viteză pe un sector de drum pentru un vehicul
care fi parcurge şi care este inregistrat la diferite momente de timp de o cameră
video. Se transmit: viteza maximă admisă v, numărul de camere video n, distanţele
faţă de punctul de inceput al sectorului de drum unde sunt amplasate camerele:
d 1 ,d2 , ... ,d,. şi momentele la care s-a inregistrat trecerea mobilului prin dreptul
fiecărei camere t1,t21- .. ,to.

Răspunsuri

19. d); 20. c); 21. a); 22. b); 23. c); 24. d); 25. b),c); 26.a) ;

27. A. b), c); B. a), d); C. c);

28. A. c); B. b); C. d).

29. Indicaţie: fiecare număr este scris in binar pe o linie a matricei. Se afişează
numerele zecimale obţinute din num·erele iri binar de pe fiecare coloană a matricei.
A. c) , B. a) , C. a) .

30. Indicaţie: funcţia realizează compararea lexicografică a doi vectori care reţin
numere naturale. A. a) o; b) -1; c) 1; d) o; B. c); C. b); O. d).
Capitolul 3
Şiruri de caractere

. 1. Generalităţi

Programele nu lucrează numai cu numere. Ele utilizează din plin şirurile de


ca.-actere. In absenţa lor ar fi foarte greu ca, de exemplu, un program să poată citi
SL afişa numele unei persoane sau numele unei localităţi. Ne putem imagina un
~ :1e caractere ca un vector în care fiecare componentă reţine un caracter. După
e...., se ştie, fiecare caracter este memorat prin utilizarea codului ASC:U. Şirurile de
c:.:actere pot fi citite, afişate şi prelucrate cu ajutorul unor subprograme puse la
-=:.poziţie de limbaj.

Şirurile de caractere au aplicaţii uriaşe în viaţa reală. Să vedem câteva dintre


~stea:

-t Cine n-a folosit un editor de texte? Chiar acum, când scriem programele
Pascal sau C++ utilizăm un editor de texte. Luaţi codul sursă al unui program
(fişiere cu extensii .pas sau • cpp). Observaţi că sursa este un fişier text în
care fiecare linie a ei, este o linie a fişierului text. Prin urmare, fiecare linie a
fişierului text conţine un şir de caractere. Ce operaţii putem face cu
editoarele? De exemplu, putem modifica o linie. O astfel de operaţie constă
în includerea sau ştergerea unui subşir (caractere consecutive din Interiorul
unul şir). Astfel de operaţii se fac, şi vom vedea cum, prin prelucrarea
şirurilor de caractere. Alte operaţii: a) Find- este o operaţie de Identificare
a unui subşir în cadrul unui şir de caractere; b) Replace este o operaţie de
înlocuire a unui subşir cu un altul; c) cut este o operaţie prin care, din cadrul
unul şir se extrage un subşir al său şi acesta este memorat în Clipboard.
Prin Paste se inserează un şir de caractere în cadrul altui şir de caractere.
Şirul exemplelor ar putea continua. Puteţi găsi altele?

-t Aţi învăţat, la "Tehnologia Informaţiei şi a Comunicaţiîlor", cum, de exemplu,


putem crea tabele (Excel, Word sau Access). Am văzut că liniile tabelului,
pot conţine valori numerice, dar şi alfabetlce, care sunt şiruri de caractere.
Putem sorta liniile unui tabel după o anumită coloană (de exemplu, după
numele persoanelor, în ordine alfabetică). De aici, rezultă că între şirurile qe
caractere există o relaţie de ordine, corezpunzătoare ordonării alfabetica. In
informatică, o astfel de relaţie se numeşte ordine lexicografică. Cum se
ordonează alfabetic (lexicografic) mai multe şiruri de caractere? Care sunt
principiile care stau la baza acestei ordonări? Despre toate acestea vom
învăţa în acest capitol.

+ Limbajele actuale permit numai introducerea datelor sub formă de şiruri de


caractere. Luaţi, de exemplu, o componentă de tip edit, componentă studiată
la 'Tehnologia Informaţiei şi a Comunicaţiilor". Chiar dacă vreţi, de exemplu,
74 Capitolul 3 -Şiruri de caractere

să introduceţi numărul 12, veţi introduce de fapt şirul de caractere • 12 • .


Programul va converti şi rul de caractere ' l l ' în număru l întreg 12. Nu
întotdeauna o astfel de operaţie reuşeşte, pentru că este posibil ca persoana
care introduce data respectivă să greşească. De exemplu, în loc de şirul'12' ,
să introducă şirul '1Gl'. Aceasta înseamnă că în timpul operaţiei de conversie,
se efectuează şi o anumită validare. În cazul de faţă, trebuie efectuată o
validare numerică. Componentele studiate la Tehnologia Informaţiei afişează
şiruri de caractere. Prin urmare, dacă realizăm un astfel de program, este
necesar să convertim valorile numerice în şiruri şi invers. Cum efectuăm astfel
de conversii? Conversiile sunt prezentate în acest capitol.
Întrucât există diferenţe semnificative între modul în care sunt reţinute şi
prelucrate şirurile de caractere în cele două limbaje (Pascal şi C++), prezentarea
acestora se va face separat.

3.2. Şiruri de caractere în Pascal

3.2.1. Noţiuni introductive

Poate aţi observat că •• până în acest moment, nu am memorat cuvinte.


Acestea au fost doar afişate. In Pascal se poate lucra cu uşurinţă cu ele, datorită
faptului că limbajul este înzestrat cu un tip de date special, numit string.

Definiţia 3.1. O succesiune de caractere cuprinse între două caractere


apostrof se numeşte şir de caractere.

Exemple: •un sir'; 'toamna se nwnara bobocii'.

O întrebare: între caracterele care alcătuiesc şirul se poate găsi şi


apostroful? De exemplu, şirul • Alt • sir • este scris corect?

Răspunsul comportă o anumită nuanţare. Pentru a include apostroful, se


foloseşte un artificiu: acesta este trecut de dou~ ori. Prin urmare, şirul • Alt
• sir • este incorect, corect este •Alt • • sir •. Atenţie! În memorie, şirul • Alt
• • sir • este reţinut ca • Alt • sir •, deci cu un singur apostrof. De ce este
nevoie să se procedeze astfel? Motivul este dat de faptul că, dacă am proceda
altfel, compilatorul nu ar putea decide unde se termină şirul, cu alte cuvinte, care
este apostroful final.
În Pascal, pentru a putea lucra cu şirurile de caractere se folosesc variabilele
de tip string. Tipul string este predefinit, adică este cunoscut, nu avem nevoie
să-I declarăm cu type.

var ta string; Programul alăturat are decl arată o


begin variabilă t, de tip string. Ea este
t 1• 'Iepuras•;
iniţializată cu şirul 'Iepuras •, apoi este
writeln(t);
end. afişată.
•.•anual de informatică pentru clasa a Xl-a 75

Pentru început, să analizăm modul în care o variabilă de tip string


îemoreatil un şir de caractere. Pentru aceasta, pornim de la exemplul dat în
::'ogramul anterior:

t[S]
1-----l
- ...- . . ·-·· -..- t[255]
t[O] t[l] t[2] t[3] t[4] t[S] t[6] t[7]

Ce observăm? Pentru o variabila de tip string se rezeNa automat un vector


~u 256 deocteţi, numerotaţi de la o la 255. Primul dintre ei are rolul de a reţine
1umarul de octeţi ocupaţi de şir.

În exemplul dat, şirul 'I epuras • este alcătuit din şapte caractere. Octeţii de
a 11a 7 memoreatil caracterele din care este alcătuit şiru l. Restul octeţilor, de la a
a 255 au un conţinut neprecizat. De altfel, nici nu ne interesează conţinutul lor.
faptul că afişarea s-a realizat, în ansamblu, prin precizarea numelui.
Observaţi

Întrucât o variabilă de tip string memorează cuvintele sub formă de vector


de caractere, exisM posibilitatea să accesăm direct conţinutul unui octet, aşa cum
suntem obişnuiţi. Astfel, t [1] reţine codul caracterului I , t [2] reţine codul
caracterului e, ş.a.m.d . Programul care urmează afişează acelaşi cuvânt, pe litere:
var t: string;
i: integer;
begin
t r= ':Iepuras'J
for i := 1 ton do write(t[i ]);
end.

Mai mult, putem modifica conţinutul unui singur octet, aşa cum rezultă din
programul următor.
var t: string;
begin
t 1• ':Iepuras•;
t[6] 1'11 ' i ' l
write(t)J
end.

in loc de 'a', t [6) reţine • i • . Prin urmare, programul afişează 'Iepuris'.


În cazul variabilelor de tip string există posibilitatea ca atribuirea sa se faca
direct, nu pe litere, aşa cum rezultă din programul următor:
var t, z: stringi
begin
t 1• • :Iepuras ' 1
% : = t;
write(z);
end.

in urma atribuirii, variabila z reţine cuvântu l" ~epuras" şi acesta este afişat.
76 Capitolul 3 - Şiruri de caractere

Am văzut că pentru o variabilă de tip string se reţin automat 256 de octeţi,


din care primul reţine lungimea. În multe cazuri, acest număr este prea mare - se
consumă memorie inutil. Din acest motiv exist~ posibilitatea ca o variabil~ de tip
string să fie declarată in aşa fel incât să ocupe un număr mai mic de octeţi.

Exemplu: v ar t: string[4];
Variabila t, ocupă 5 oc:teţi. Primul, cel de indice o, are rolul de a reţine
lungimea cuvântului memorat. In acest caz, variabila poate reţine cuvinte ce au cel
mult 4 caractere. Programul afişează 'mama'.
var tt string[4 ] ;
begin
t z• 'mama';
write(t);
end.

Ce facem în cazul în care cuvântul care va fi memorat, are un număr mai


mare de litere decât numărul de octeţi ai variabilei care îl memorează?

Exemplu:
var tz string[4];

t:= 'DANSATOR';

În astfel de cazuri se reţin numai primele caractere, atâtea câte pot fi


memorate. În exemplu, variabila t reţine cuvântul 'DANS'.

După cum am învăţat, caracterul blank se memorează ca oricare altul, prin


codul său. Prin urmare, o variabilă de tip string poate reţine mai multe cuvinte,
separate prin unul sau mai multe blank-uri.

Exemplu:
var numet string;

numez• 'Ion Zaharia•;

În concluzie, prin utilizarea variabilelor de tip string avem avantajul că


putem adresa şirul de caractere atât în ansamblu, prin utilizarea numelui variabilei,
cât şi pe caractere, prin utilizarea parantezelor drepte.

Acesta nu este singurul avantaj. Limbajul este înzestrat cu proceduri şi funcţi i


care uşurează mult lucrul cu şirurile de caractere.

3.2.2. Concatenarea şirurilor

Pentru a concatena două şiru ri de caractere se foloseşte operatorul '+ '.


Operatorul '+ • este binar (adică are doi operanzi) şi poate acţiona asupra datelor
de tip string. Ce înţe legem prin concatenare?
· Jal de informatică pentru clasa a Xl-a 77

Definiţia 3.2. Concatenarea este operaţia prin care din două şiruri de
caractere se obţine un al treilea, format astfel: p~imul şir (cel aflat în
stânga operatorului), urmat de al doilea şir (cel aflat in dreapta
operatorului).

:.amplu:
program st6;
var t, z: string;
begin
t := •acest';
z := ' exemplu';
t :• t+z;
writeln(t);
end.

- programul st6 se concatenează două şiruri de caractere (t: =t+z; ):

• variabila t reţine şirul 1 acest 1 ;


• variabila z reţine şirul 1 exemplu 1 ;
• prin operaţia (t+z;) se obţine şirul 1 acest exemplu';
• şirul obţinut este atribuit variabilei t.

J Observaţii

1. Dacă am fi scris t :mz+t; programul ar fi afişat • exempluacest 1 • De
aci tragem concluzia că operaţia de concatenare nu este comutativă (contează
:;dlnea în care sunt trecuţi cel doi operanzi).
2. Observăm cât este de important să ţinem cont de poziţia blank-ului (al
::oilea şir este precedat de blank). Observaţi că dacă am făcut concatenarea z+t,
am obţinut şirul 1 exempluacest 1 , care este precedat de blank.

3. Trgbuie să avem în vedere că şirul obţinut în urma concatenării să poată fi


-,emorat în întregime - variabila căreia i se atribuie să fie declarată cu un număr de
octeţi care să permită memorarea sa, altfel şirul va fi memorat trunchiat - adică vor
' memorate numai primele caractere, atâtea câte încap.

3.2.3. Compararea şirurilor

Oricât ar părea de curios, şirurile de caractere pot fi comparate. Astfel, două


şiruri de caractere (notate a şi b), se pot găsi în una din relaţiile:

• aab - cele două şiruri sunt egale;


• a>b - şirul a este mai mare decât şirul b;
• a<b - şirul a este mai mic decât şirul b.
78 Capitolul 3 -Şiruri de caractere

Dar cum se pot compara două şiruri? Să nu uităm că şi două caractere se


pot compara după codul lor. Am văzut modul în car~. au fost codificate caracterele,
după o anumită logică: da~ un caracter urmeazA altuia, În ordine alfabeti~, atunci
el are drept cod un număr mai mare decât al caracterului căruia Îi urmeam.
Exemplu: codul caracterului d este mai mare cu o unitate decât codul caracterului c.

Şirurile sunt succesiuni de caractere. Prin urmare, compararea se face pe


caractere. În continuare prezentăm algoritmul de comparare a două şiruri.

Fie două şiruri de caractere, notate cu a (cu m caractere) şi b (cu n


caractere).

~ Se compară codurile primelor două caractere (aflate în stânga şirurilor, de


indice 1). Avem 3 posibilităţi:

• dacă codul primului caracter al şirului a este mai mare decât codul
primului caracter al ş irului b , atunci a>b.

• dacă codul primului caracter al şirului b este mai mare decât codul
primului caracter al şirului a, atunci a<b (b>a ).

• în caz de egalitate se compară codurile caracterelor de indice 2.

~ Dacă m<n , şi în urma comparării primelor m caractere a rezultat egalitate, atunci


a<b (bare mai multe caractere).

~ Dacă m>n, şi în urma comparării primelor n caractere a rezultat egalitate,


atunci a>b (a are mai multe caractere).

~ Dacă m•n şi în urma comparării tuturor caracterelor a rezultat egalitate, atunci


cele două şiruri sunt egale (a=b).

Exemple:
• a=' abc' , b= 'bactr •. Atunci a<b, pentru că a [ 11 este a şi are codul mai
mic decât b [ 1 1 care este b .

• a= 1 abc 1 , b= • aba 1 • Atunci a>b, codul lui a [1] este egal cu codul lui b [1] ,
codul lui a [ 2 J este egal cu codul lui b [ 21 iar codul lui a [ 3 1 este mai mare
1

decât codul lui b [ 31 .

• a• • abc' 1 b= 1 abea •. Aici m=3, n=4. Atunci a<b, codul lui a [1] este egal cu
codul lui b [11. codul lui a [2 1 este egal cu codul lui b [2], codul lui a [3 ]
este egal cu codul lui b[3] şi n>m (şirul bare mai multe caractere).

Compararea şirurilor de caractere este extrem de utilă în sortarea alfabetică


a cuvintelor (ca în dicţionar). Ordinea astfel imp usă se mai numeşte şi ordine
lexicografică.
'.~anual de informatică pentru clasa a Xl-a 79

În programul următor se sortează alfabetic mai multe şiruri de caractere


;:....;.·.
_.,/\;. (cuvinte). Pentru aceasta, se declară un vector în care fiecare
componentă este de tip string.

Algoritmul de sortare este unul studiat (care?), motiv pentru care nu revenim
asupra lui. Programul este prezentat în continuare:

type cuvinte= array[1 •• 10] of string;


var v: cuvinte;
man: string;
n, i: byte;
inv: boolean;
begin
write( 'n= •);
readln(n);
for i := 1 to n do
readln(v[i]);
repeat
inv := false;
for i := 1 to n-1 do
if v[i] > v[i+1] then
begin
man ,. v[i];
v[i] ;a v[i+1] 1
v[i+ll := man;
inv :• true;
end
until not inv;
for i := 1 to n do
writeln(v[i]);
end.

3.2.4. Lungimea şirurilor de caractere

Definiţia 3.3. Prin lungimea unui şir de caractere înţelegem numărul de


caractere pe care acesta le conţine.

Exemple:
şiru l •mama • are lungimea 4;

şirul •mama • are lungimea s (şi blank-ul este caracter).

:::>entru aflarea lungimii unui şir avem două posibilităţi:

1. Prin utilizarea funcţiei length, care are forma generală:

function length(S: String): Integer;

Ş' întoarce lungimea şirului s - număr Tntreg.


80 Capitolul 3 - Şiruri de caraC"'

4 Exemple de utilizare

a) var a: string;
c: integer;

a := •un test ' ;


c := length(a);

În urma atribuirii, variabila c reţine 7.

b) În condiţiile de mai sus, writeln(length( a)) afişează 7.

2. După
cum am învăţat, octetul de indice o reţine lungimea şirului. Pentru c
obţinefolosim funcţia ord astfel: ord(a [01 >. De ce aşa? Atunci când scne-
a[OJ ne referim la un caracter. Folosind această expresie obţinem caracterul ca
are codul dat de număr (în binar). Deci, pentru a obţine codul său, utiliz~­
funcţia ord.

CJ În programul următor se afişează lungimea unui şir citit. Tipărirea Si


(4,-X: face în cele două moduri prezentate:

var a: string;
begin
write('a= '); readln(a);
writeln('lungime a sirului a este ' length(a))t
writeln('lungime a sirului a este • ord(a[O)));
end.

Un caz aparte de şir este şirul vid. Prin şir vid Înţelegem un şir f/!1ră nici ...
caracter. Evident, şirul vid are lungimea o.

Exemplu: a:= • •. Am iniţializat variabila a, de tip string, cu şirul vid.

3.2 .5. Subşiruri

Definiţia 3.4. Fiind dat un şir de caractere, prin subşir al său se înţelege
un şir de caractere consecutive care se regăsesc în şirul iniţial.

Exemple:

• Şirul • harnic • are ca subşir şirul • mi •. Acesta începe în poziţia a treia din
şirul iniţial (3 caractere);

• Şirul•mama • are ca subşir şirul •ma •. Observăm faptul că subşirul apare de


două ori - începând cu poziţiile 1 şi 3. Prin urmare, în cadrul unui şir, un subşir
poate apărea de mai m uite ori.

• Şirul • harnic • nu are ca subşir şirul • rnit •, chiar dacă primele trei
caractere (mi) se regăsesc în şirul iniţial.
'Mual de informatică pentru clasa a Xl-a 81

=Jn cţii ce acţ i onează asupra şiruri lo r de caractere


.-
Există mai multe proceduri şi funcţii care acţionează asupra şiruri lor de
:aractere. Acestea sunt prezent~te în continuare.

,. Funcţia copy are rolul de a extrage un subşir din cadrul unui şir dat:

function copy(s:string;inceput,lungime:integer):string;

variabila s (de tip string)- conţine şirul din care se face extragerea;
• inceput - reţine poziţia de început a subşi rului care se extrage;
• lungime - reţi ne numărul de caractere care se extrag.

f; În cazul în care prin parametrul lungime specificăm mal multe caractere


• decât are şirul, se extrag caracterele până la sfârşitul şirului.

=ie programul următor:

var a, b: string;
i, j: byte;
begin
write('a= ') ; readln(a);
write('i= '); readln(i);
write('j= '); readln(j);
b := copy(a, i, j);
writeln(b);
end.

Dacă · se citeşte şirul 'un text • , 4 pentru i şi 3 pentru j, variabîla b va reţine


su bşirul 1
tex 1 •

)- Funcţia pos are rolul de a verifica dacă un şir este subşir pentru altul:
function pos(subsir,sir:string):byte;

Se scriu, în ordine, următoriî parametri:

• subsir - subşirul căutat;


• sir - şirul în care se face căutarea.

F uncţia returnează:

• o, dacă nu a fost găsit subşirul cerut;


• poziţia de început a subşirului, în cazul în care acesta a fost găsit.

Exemple: 1. Fie şirul abcde 1 • Se caută subşirul 1 bcd 1 • Acesta este găsit, iar
1

f uncţia returnează 2 (poziţia de început a subşirului). Dacă se caută subş irul 1 cz 1 ,


f uncţia returnează o ( 1 cz 1 nu este subşir al şirului 1 abcde 1 ).
82 Capitolul 3 - Şiruri de caractere

2. Programul următor citeşte două şiruri a şi b şi verifică dacă b este subşir al lui
a. în ambele cazuri se dau mesaje. Dacă b nu a fost identificat ca subşir al lui a,
sau, în caz contrar, se afişează poziţia de început a sa.
var a, b: string;
n: integer1
begin
write('a= '); readln(a);
write('b• ')1 readln(b)J
n := poa(b,a);
if n • O then writeln('b nu este subsir al lui a')
else writeln('b este subsir al lui a si incepe in
pozitia • ,n) 1
end.

)o> Procedura insert are rolul de a insera un şir de caractere începând cu o


anumită poziţie, în alt şir. Pentru aceasta, ea primeşte ca parametri
următoarele:

procedura insert (sir de ins: string; var şir_unde_ins:


stringi poz: integer);
unde:

• sir_de_ins - şirul care urmează a fi inserat;


• sir_unde_ ins - şirul în care se face inserarea;
• poz - poziţia din care se face inserarea.

Exemplu:
var a, b: string;
begin
write(•a• '); raadln(a)t
write('b• '); readln(b)t
insert(b, a, 3);
writeln(a);
and .

În programul anterior am citit două variabile de tip string, a şi b. Şirul reţinut


de variabila b este inserat în şirul reţinut de variabila a începând cu poziţia 3 .

Exemplu: dacă a reţine şirul 1


abcd 1 şi b reţine şirul 1
123 1 , programul afişează
1
ab123cd 1 •

)o> Procedura delete are rolul de a şterge un subşir din cadrul unui şir dat.
Pentru aceasta, ea are ca parametri, în ordine:
procedure delete(var sir: string; indice, nr_car: integer);

unde:

• Sir - variabila care conţine şirul din care se face ştergerea;


• Indice - indicele primului caracter al subş irului care se şterge;
• nr_car - numărul de caractere pe care îl are subşirul.
..,.,ual de informatică pentru clasa a Xl-a 83

::Xemplu:
Programul următor citeşte un şir de caractere într-o variâbilă de tip string,
- -•ită a. Şirului cititi se şterge subşirul care începe în poziţia 3 şi are lungimea 2
::ouă caractere). De exemplu, dacă variabila a reţine şirul • abcde', se afişează
.aba'.

var a:string1
begin
write('a= ' ); readln(a)J
delete(a,3,2);
writel n(a);
end.

:J Aplicaţia 3.1. În programul următor se listează indicii tuturor apariţiilor


:aracterulul citit în şir:
var sir: string;
ch: string [l l ;
pozn, pozv: byte1
begin
writeln(•introduceti sirul'); readln(sir)1
writeln(•introduceti caracterul ')1 readln(ch)J
pozn :• pos(ch, sir)1
pozv :• O;
while pozn <> O do
begin
POZV := POZV+POZDI
writeln(pozv)J
sir :• copy(sir, pozn+l, 255)1
pozn := pos(ch, sir);
end
end.

O Aplicaţia 3.2. Ştergerea tuturor apariţiilor unui subşir din cadrul unui şir:
var sir., subsir: stringi
poz, lung: byte1
begin
writeln(•introduceti sirul')l readln(sir);
writeln('introduceti subsirul '); readln(subsir)J
lung:~ length(subsir);
poz :• pos(subsir, sir);
while poz <> O do
begin
delete(sir, poz, lung);
poz := pos(subsir, sir);
end;
writeln(sir)J
end.
84 Capitolul 3 - Şiruri de caractere

O Aplicaţia 3.3. Înlocuirea tuturor apariţiilor unui subşir cu alt subşir. Analizaţi
programul următor pentru a descoperi modul în care se realizează aceasta:
var sir, sir_ sters, sir_adaugat: string;
poz, lungt byte;
begin
writeln( 1 introduceti sirul 1 ) ; readln(sir);
writeln( 1 introduceti sirul care se sterge);
readln(sir_sters);
writeln( 1 introduceti sirul care se adauga)/
readln(sir_ adaugat)J
lung := length(sir_sters);
poz :• pos(sir_sters, sir);
while poz <> O do
begin
delete(sir,poz,lung);
insert(sir_adaugat, sir, poz);
poz t= pos(sir_ sters, sir);
end1
writeln(sir};
end.

3.2.6. Conversia de la şiruri la valori numerice şi invers


Fie şirul 1 l.23 1 • Evident, acesta este diferit de numărul123. După cum ştim ,
şirul ocupă 4 octeţi (nu uitaţi, primul octet reţine lungimea). Numărul 123, dacă
este memorat de o variabilă de tip integer, ocupă doi octeţi şi este reţinut în cod
complementar. limbajul dispune de două proceduri care realizează conversia de la
valori numerice la şiruri şi invers.
~ Procedura str are rolul de a transforma o valoare numerică în şir.

procedura Str(X [: Lg [: Zec )]1 var S:string)l

Pentru aceasta i se transmit, în ordine, doi parametri:


• x-variabila (valoarea) numerică- poate fi întreagă sau reală;

• s - variabila de tip string care reţine şirul convertit.

Exemplu:
var a: string1
n: integer;
begin
write ( 1 n• '); readln(n);
str(n, a);
writeln(a)
end.

În programul de mai sus se citeşte o valoare întreagă (n). Valoarea este


convertită către
tipul string - variabila a are acest tip. Rezultatul este afişat. De
exemplu, dacă se citeşte 123 se afişează 123. Până aici nimic spectaculos. Dacă
• ~ ual de informatică pentru clasa a Xl-a 85

::'1 fi afişat conţinutul variabilei n, am avea acelaşi rezultat. Atunci la ce folosesc


::stfel de conversii? Pentru a putea da răspunsul trebuie .să mai învăţăm ceva
:aspre modul în care putem apela procedura str. Am fi putut să scriem apelul şi
=şa: str(n:4,a) ;. Observaţi faptul că, după numele variabilei care se afişează
::l.) au fost puse caracterul ':' şi numărul 4 . Aceasta i'nseamnă că am cerut ca
::zultatul (de tip string) să ocupe s octeţi - unde primul reţine lungimea, deci 4
xteţi pentru memorare.

Să analizăm acum modul de efectuare a conversiei.

=> Dacă variabila n reţine numărul 1.234, variabila a va reţine şirul


1
1234 1 •

=> Dacă variabila n reţine numărull.23, atunci a va reţ ine şirul 1


123 1 •
Rezultă că şiru l , fără octetul de lungime, ocupă 4 octeţi. În astfel de
cazuri, şiru l este completat în stânga cu numărul de blank-uri necesar.

=> Dacă variabila n reţine num ărul12345 atunci ş irul va fi '12345 Cu 1


alte cuvinte nu se respectă cerinţa noastră, pentru că dacă ar fi fost


respectată, rezultatul ar fi fost eronat.

Acum putem răspunde la întrebarea: la ce folosesc astfel de conversii?


=>rogramatorul va avea grijă ca întotdeauna numărul d~ octeţi ai şi rul ui să fie mai
mare sau egal cu numărul de octeţi ai valorii convertite. In acest fel, la afişare, vom
şti care este spaţiul ocupat de şir şi putem să afişăm rezultatele aliniate (tabele). In
continuare, ne ocu păm de conversia valorilor reale către ş iruri de caractere.
Exemplu:
var a: string;
x: real;
begin
X :;:: -67.789;
str(x: 10: 2, a);
writeln(a)
end.

În programul de mai sus se converteşte numărul -67.789 către un şir.


Cerinţa este ca şiru l efectiv să ocupe 1.0 caractere, dintre care ultimele do'uă să fie
zecimale. Evident, două caractere vor fi semnul •- • şi punctul zecimal. în' exemplu,
se obţine şirul • -67 . 79 •. Ce observăm?

./ Întotdeauna numărul zecimalelor solicitat de programator este respectat. În


cazul în care numărul efectiv de zecimale este mai mare decât numărul
solicitat pentru conversie, înaintea conversiei n um ă ru l este rotunjit: Dacă
număru l solicitat de zecimale ar fi fost o, s-ar fi afişat -67 (deci punctul
zecimal nu ar fi afişat) .
./ În cazul în care numărul total de cifre al părţii întregi + numărul total de
zecimale + 2 (caracterele de semn şi punctul zecimal) ocupă mai mult
decât numărul total de octeţi solicitaţi pentru afişare, acesta din urmă nu este
86 Capitolul 3 -Şiruri de caractere

respectat. Cu alte cuvinte, se trunchiază numai zecimalele nu şi partea


întreagă a valorii reale. De exemplu, dacă am fi scris x: 1: 2, şirul obţinut ar
fi fost o -67.79 o •

./ în cazul Tn care numărul care se converteşte este pozitiv, semnul 0


+ 0 nu
este trecut.

Acum studiem conversia inversă de la tipul string către valori numerice


(întregi sau reale). De exemplu, şirul '123 • se poate converti către valoarea de tip
integer: 123.

De la început precizăm că nu întotdeauna conversia reuşeşte. De exemplu.


dacă încercăm să convertim şirul 0 1a2 o către o valoare de tip integer, conversia
nu reuşeşte, pentru că şirul conţine caracterul 1 a 1 •

};> Pentru realizarea conversiei utilizăm procedura val. Ea are trei parametri şi
anume:
proc:edu.re val (a: atring1 var variabila...JNIIIB%'ica; var cod._er: integer) ;

unde:
• a - conţine şirul ce urmează a fi convertit;

• variabila_numerica - variabila de tip întreg sau real care va


reţine rezultatul conversiei (valoarea numerică).

• eod_er - variabilă de tip întreg. După conversie, aceasta va reţine o


dacăconversia a reuşit sau o valoare diferită de o, în caz contrar.

Să analizăm programul următor:

var as stringi
x, ers integer;
begin
write('Sirul este ')1 readln( a)l
val(a, x, er)1
if er • O then writeln(' conversia a reusit •, x)
el se
begin
writeln ('conversia nu a reusit')/
writeln(x)
end
end.

Programul citeşte un şir de caractere care este reţinut în variabila de tip


atring a. Se încearcă conversia şirului către o variabilă de tip -integer. În cazU'
în care tentativa a reuşit, se afişează mesajul corespunzător şi conţinutul variabilei,
altfel se afişează numai un mesaj prin care se anunţă faptul că tentativa a eşuat.
...:- ~ al de informatică pentru clasa a Xl-a 87

Observaţii

Dacă şirul de caractere cifre este precedat de un număr de blank-uri,
conversia reuşeşte.

Exemplu: şirul 1
123 1 se poate converti către valoarea 123.

Dacă şirul de caractere cifre este urmat de un număr de blank-uri, conversia


nu reuşeşte.

Exemplu: şirul • 123 • nu poate fi convertit către o valoare numerică.

Dacă şirul conţine un singur caracter literă, el nu poate fi convertit către o


valoare numerică. De exemplu, şirul • l2i • nu poate fi convertit. Excepţie
fac şirurile de caractere care respectă sintaxa unei constante reale în formă
ştiinţifică (de exemplu, 'l.E-3')

Dacă variabila care reţine rezultatul este de tip întreg, iar şirul conţine punctul
zecimal, conversia nu reuşeşte.

Exemplu: şirul •1. 23 • nu poate fi convertit către o variabilă de tip Întreg,


dar poate fi convertit către o variabilă de tip real .

..~ Dacă în urma conversiei se obţine o valoare numerică care nu poate fi


memorată de variabila respectivă, programul se termină anormal, prin eroare
de executare.

:J Aplicaţia 3.4. Programul următor testează dacă o valoare introdusă este


1u merică şi dacă este cuprinsă în intervalul [ 10, 2 o]:
var sira stringi
eroarea integer1
valoarea real1
begin
writeln( •introdu ceti sirul ')1
readln(s ir) 1
~al(sir, valoare, eroare)l
if eroare<>O
then
writeln( •val. introdus a este eronata• )
el se
if (valoare < 10) or (valoare > ~O) then
writeln( •val. nu este in interval ul dorit ')
el se
writeln( •okl')
end.
88 Capitolul 3 -Şiruri de caractere

3.2. 7. Citirea şi scrierea datelor de tip String din şi


în fişiere text
"l
A} Citirea datelor de tip String

Aceste variabile se citesc începând din poziţia curentă a cursorului până este
citit numărul de caractere necesar tipului sau până s-a ajuns la sfârşitul de linie.

Programul de mai jos demonstrează acest fapt (dacă linia 1 a fişierului are 3
caractere, se vor afişa două pe un rând şi unul pe al doilea rând). După citirea unei
astfel de variabile, pointerul se află sau pe caracterul ce urmează după ultimul
caracter citit sau pe CR. În situaţia în care pointerul se află pe CR şi se forţează o
nouă citire de tip String, se returnează şirul vid.

var f: text;
a: string[2];
b: string;
begin
assign(f, 'fl.dat');
reset(f);
read(f, a);
writeln(a);
read(f,b);
writeln(b);
close(f);
end.

B) Scrierea datelor de tip String

În general, conţinutul unei date de tip string se scrie în fişier în totalitate.


Opţional, numele variabilei poate fi urmat de ' :' şi de un parametru, m. Pentru
datele de acest tip, se iau în considerare lungimea efectivă a şirului şi valoarea
parametrului m ce specifică numărul de poziţii pe care se face scrierea. În cazul în
care valoarea lui m este mai mică decât lungimea efectivă a şirului, aceasta se
ignoră, şirul va fi scris în întregime. Dacă valoarea lui m este mai mare decât
lungimea efectivă a şirului, acesta este scris pe m poziţii aliniat dreapta, iar în faţă
se pun blank-uri.
Exemplu: se scrie şirul • Marian'.
var f:text;
sir:string[6] ;
begin
assign(f,'Fl.txt');
rewrite (f);
sir:= 'Marian';
write(f,sir:10);
close(f);
end .
\~anual de informatică pentru clasa a Xl-a 89

3.3. Şiruri de caractere în C++

3.3.1. Generalităţi

Am învăţat faptul că o constantă de tip şir de caractere se declară intre dou~


caractere ", de exemplu: "calculator". Dar cum se reţine in memoria internă?
Aceasta este reţinută sub forma unui vector de caractere. Primul element, cel de
"ldice o, reţine codul ASCXI al caracterului 'c', al doilea reţine codul ASC:U al
caracterului 'a', ... , ş.a.m.d. Convenţia este ca ultimul octet să reţină o (codul
caracterului nul). Prin urmare, pentru a reţine şirul "calculator" trebuie să fie
--ezervate cel puţin 11 elemente de tip char ·(10 litere plus caracterul nul) -adică
11 octeţi. Menţionăm că pentru fiecare caracter este reţinut codul Asc:n.

1 c 1 a 1 1 1 c 1u , ........................................................................................, J
O

a[OJ a[l] a[2J ••••••• •••••••••••••••••• •• •••• • •• • a[lO]

Vectorii de caractere pot fi iniţializaţi la declarare, caracterul nul fiind


'Tiemorat automat.

Exemple:
• char vect(ll]="calculator".
char vect {] •"calculator". În acest caz, compilatorul face calculul
numărului de octeţi necesari.
char vect [100]a"calculator". Am rezervat mai mulţi octeţi decât era
necesar.

3. 3.2. Citirea şi scrierea şirurilor de caractere

Se propune următoarea problem ă: să se citească şi să se afişeze cuv~ntul


·calculator". Pentru aceasta, ar trebui să procedăm astfel:

• reţinem un vector cu cel puţin 11 componente de tip char - în exemplu 20;


• citim cuvântul, caracter cu caracter;
• il afişăm .
orogramul următor realizează toate acestea:
#include <iostream.h>
main()
{ char a[lOJ;
int i;
for(i~O;i<lO ii ++) cin>>a [i] ;
a[10]•0;
for(i • O;i<lO;i++) cout<<a[i];
}
90 Capitolul 3 -Şiruri de caractere

Să recunoaştem că o astfel de modalitate de lucru este deosebit de greoaie.


Dar dacă citim un cuvânt cu patru litere? S-ar putea- scrie o secvenţă care să
citească cuvântul până la întâlnirea caracterului EN'l'ER, însă şi această modalitate
este greoaie.

Limbajul C++ permite ca lucrul cu şiruri de caractere să fie cu mult mai


simplu. Refacem programul anterior:

#include <iostream.h>
main()
{
char a[20];
cin>>a;
cout<<a;
}

·J~ Observaţii
./ Caracterul nul este adăugat automat.

./ Procedând ca mai sus, putem citi orice cuvânt cu un număr de până la 19


caractere - excluzând caracterul nul.
./ Putem să rezervăm, în limita memoriei pe care o are la dispoziţie programul ,
un număr mai mare de octeţi.

Exemplu: char a[1000] .

./ Un vector poate fi adresat pe componente.

Exemplu: a [O]= 1 c 1
, a [1) =•a 1
, ş.a.m.d.

Din păcate, prin metoda de mai sus nu poate fi citit un şir care conţine mai
multe cuvinte separate prin spaţii. De exemplu, dacă la rularea programului anterior
tastăm şirul" un om", se va afişa "un". Aceasta înseamnă că citirea se face astfel:

• Se sar toate caracterele albe. În exemplu, s-au sărit blank-urile.

• Se citeşte şirul care începe cu primul caracter care nu este alb şi se


sfârşeşte la întâlnirea primului caracter alb (în exemplu, blank).

Din acest motiv, pentru citirea şirurilor de caractere vom utiliza o funcţie de
un tip special, pe care o prezentăm în continuare.

)> Funcţia:

cin . get(vector_de_caractere, int nr, char='\n')


citeşte un şir de caractere până când este îndeplinită una dintre condiţiile de mai jos:

• au fost citite nr-1 caractere;

• a fost întâlnit caracterul transmis ca ultim parametru (implicit, "\n").


clasa a Xl-a 91
de informatică

Observaţii

Sunt citite şi caracterele albe.


!:ste inserat caracterul nul.
şir.
Caracterul transmis ca ultim parametru nu este inserat in
Al treilea parametru este trecut în mod facultativ. Dacă nu este trecut, se
-=-u une că este 1 \n • .

Priviţi următoarele exemple:

Se citeşte un şir de maximum 2 caractere:


char a[10) ;
cin.ge t(a,3);
cout<< a;
va fi afişat.
:.: exemplu, dacă tastăm 1 mama şi Enter se citeşte şirul "ma", care
1

sau când
2.. _a fel ca mai sus, dar citirea se întrerupe la întâlnirea caracterului
1
1 g

a.. fost citite 9 caractere ale şirului.


char a[10);
cin.ge t(a,10 ,'g')l
cout<<a 1

În C++ pot exista mal multe funcţii cu acelaşi nume, dar care diferă
prin
::.arametrii primiţi. Astfel, există şi funcţia:

ain.ge t()

~ră parametri. Ea are rolul de a citi un caracter (fie că este alb, fie că nu).

f Observaţie. ln cazul utilizării repetate a funcţiei


problemă . Analizaţi programul
ain.ge t ()
următor :
cu trei
• parametri, apare o
linclud e <iostre am.h>
linclud e <string .h>
main()
{ char sir1[10 00], sir2[25 ]1
cout<< •sir 1 •1
cin.ge t(sir1, 1000);
cin.ge t{) 1
cout<< •sir 2 •1
cin.ge t(sir2 ,25)1
}

Dacă după prima citire nu am fi folosit funcţia cin.ge t () fără parametri, a


a fost
doua citire nu nr mai fi fost efectuată. De ce? Sfârşitul primului şir introdus
făcut ca in memori e (buffer ) să fie păstrat
marcat prin tastarea Enter. Aceasta a
funcţiei
caracterul '\n 1 • La a doua citire, noul şir va începe cu acesta. Prin logica
92 Capitolul 3 - Şiruri de caractere

cu trei parametri, citirea se face până la Tntâlnirea lui. Tn concluzie, se citeşte şirul
vid. Utilizatorul nu mai apucă să îşi tasteze textul. - Prin utilizarea funcţiei fără
parametri, acel caracter se citeşte şi noua citire se poate efectua fără probleme.

3.3.3. Tipul char*


Vectorii de caractere au o proprietate deosebită. Priviţi programul următor:

#include <iostream.h>
main()
{
char a[]a"masa";
cout<<a+l<<" "<<a+2<<" "<<a+3;

Tn urma executării, se tipăreşte: "asa sa a".

·'a De ce? Limbajul C++ permite ca un vector de caractere să fie adresat


• "' începând de la un anumit octet al său:
~ când scriem a, adresăm vectorul - în ansamblul lui - începând cu
primul său octet (cel care reţine primul caracter al şirului de caractere);
echivalent, puteam scrie a+O;
~ când scriem a+l, adresăm vectorul Tncepând cu al doilea octet;
~ când scriem a+2, adresăm vectorul incepând cu al treilea octet;

Din acest motiv, programul ti păreşte pentru a+l şirul "asa" , pentru a+2
şirul "sa", ş.a . m . d . Mai mult, vectorii astfel adresaţi pot fi accesa ţi aşa cum
suntem deja obişnuiţi. Astfel, pentru exemplul anterior, (a+l) [OJ reţine caracterul
'a' , (a+l) [1] reţinecaracterul •s •,etc.

După cum observaţi, a+l, a+2, ... sunt expresii. Orice expresie are un
anumit tip. Care este tipul lor? Tipul acestor expresii este char*. Ce semnificaţie
are? Semnificaţia este cea de adresă.

Definiţia 3.5. Numărul de ordine al unui octet in memoria internă se



::J:_) numeşte adresa octetului respectiv.

Definiţia 3.6. Adresa unui vector de caractere este adresa primului


său octet.

•!• O variabilă de tipul char* poate reţine adresa unui vector de caractere.

•!• În C++, numele unui vector de caractere este o adresă constantă de vector
şi poate fi atribuit unei variabile de tip char*.
~ _aJ de informatică pentru clasa a Xl- a 93

~: Considerăm următorul exemplu:

#include <iostream.h>
main()
( char a[]•"Exemplu", *p;
paa; cout<<p<<endl;
p++; cout<<p<<endl;
p++l cout<<p<<endl;
cout<<p[ll<<endl;
cout<<p-a;
}

Am declarat un vector de caractere numit a, pe care l-am iniţializat cu un şir.


:-:asemenea, am declarat şi o variabilă de tip char* numită p . Tn aceste condiţii,
:_·em afirma că:
• p•a; este o atribuire corectă . Variabila p va reţine adresa vectorului de
caractere a, adică adresa primului său octet. ln schimb, atribuirea a=p este
incorectă, pentru că a este o constantă ce reprezintă o adresă .

dupăatribuire, putem utiliza variabila p in aceleaşi condiţii ca variabila v. De


exemplu, putem afişa şirul reţinut prin "cout«p«endl; " .
dacă la conţinutul variabilei p se adaugă 1 , aceasta va reţine adresa
vectorului al cărui prim octet coincide cu al doilea octet al vectorului v. De
exemplu , dacă tipăresc vectorul se va afişa "xemplu". Noul vector se poate
adresa şi pe octeţi. Exemplu: p[ll.
se pot face şi scăderi între adrese. ln acest caz, rezultatul este întreg. De
exer:nplu, prin p-a se obţine indicele în v, al primului octet al vectorului
reţinut dep. Testaţi programul!

3.3 .4. Lungimea unui şir de caractere

Pentru a putea fi folosite funcţiile de prelucrare a şirurilor de caractere,


trebuie să fie inclus fişierul antet "string.h", tot aşa cum includem fişierul
"iostream.h":
#include <iostream.h>

~ Funcţia strlen are rolul de a returna lungimea efectivă a unui şir (în calculul
lungimii nu intră caracterul nul). Forma generală este:
size_ t strlen(char*);
unde:
• size_ t este un tip întreg, utilizat în adresarea memoriei, definit în "string.h"
(îl putem privi ca pe tipul unsigned int);
• argumentul este de tip char* (adică o adresă către un şir).
94 Capitolul 3- Şiruri de caractere

Programul de mai jos citeşte un şir şi afişează numărul de caractere pe care


le are şirul
citit (exclusiv caracterul nul):

#include <iostream.h>
#include <string.h>
main()
{ char a[lOOJ;
cin . get(a,lOO) ;
cout<<"Sirul citit are "<<strlen (a)<<" caractere";
}

Aşa cum ştim din lucrul cu tablouri, atribuirile de forma a•b, unde a şi b sunt
vectori de caractere, nu sunt permise. Tot aşa, o atribuire de forma a:::"un sir"
nu este permisă. Astfel de operaţii ca şi multe altele se fac cu anumite funcţii, puse
la dispoziţie de limbaj. Pentru ca acestea să poată fi folosite, trebuie să fie inclus
fişierul antet "string. h", tot aşa cum includem fişierul "iostream. h". Ordinea
de includere nu are importanţă. fn continuare, vom prezenta cele mai uzuale funcţii
şi modul fn care acestea se folosesc.

3.3 .5. Copierea şi concatenarea şirurilor de caractere

);> Funcţia strcpy are forma generală:

char *strcpy(char* dest, char* sursa);

şiare rolul de a copia şirul de adresă sursa la adresa dest. Copierea se termină
după ce a fost copiat caracterul nul. Se returnează adresa dest. Analizaţi
codul următor:
#include <iostream.h>
#include <string . h>
main()
{ char a[lOO]="un sir", b[lOO]•"alt sir"t
strcpy (a,b);
cout<<at
}

Tn programul de mai sus se copiază în vectorul de caractere a şirul de


caractere reţinut de b. Programul va afişa "alt sir". Această copiere simulează
atribuirea: a=b. ·

);> Funcţia standard strcat are forma generală:

char~ strcat(char* dest, char* sursa);

şi rolul de a adăuga şirului de adresă dest şirul de adresă sursa. Şirul de adresă
sursa rămâne nemodificat. Această operaţie se numeşte concatenare şi nu este
comutativă. Rezultatul este adresa şirului sursa, iar şirul va avea ca lungime,
suma lungimilor celor două şiruri care au fost concatenate.
.,... Jal de informatică pentru clasa a Xl-a 95

Programul următor tipăreşte "mama merge":


#include <iostream. h>
#include <string.h>
main()
{
char a[20) • •mama", b(100]=" marge";
strcat (a,b);
cout<<a;
}

, Funcţia strncat are forma generală:

char *strncat(char *dest , const char *sursa, size_t nr);

r ace)aşl
ro) ca strcat cu deosebirea că adaugă şlru)u'l destmaţ'le prlm'll nr octeţi al
s rului sursă. Adăugarea caracterelor se face Tnaintea caracterului nul. Funcţia
-etu rnează adresa de început a şirului destinaţie.

3. 3.6. Căutarea unui caracter într-un şir

;. Funcţia strchr are forma generală:

char* strchr(char *s, int c);

şi rolul de a căuta caracterul • c • in şirul s. Căutarea se face de la stânga la


dreapta. ln cazul in care caracterul este găsit, funcţia întoarce adresa subşirului
care începe cu prima apariţie a caracterului citit şi se termină cu caracterul nul al
şirului în care se face căutarea. Altfel, întoarce o expresie de tip char* cu valoarea
o (adică o adresă vidă de şir) . Exemplele care urmează ne vor lămuri.

ln programul următor se caută in şirul a caracterul • t 1 • Acesta este găsit, iar


programul tipăreşte şirul "ta este". Evident, acesta este subşirul care incepe cu
caracterul t • . 1

#include <iostream.h>
#include <string . h>
main()
{
char a[20]•"Acesta este"1
cout<<strchr (a,'t 1 )1
}

D Aplicaţia 3.5. in unele cazuri ne interesează indicele în cadrul vectorului al


caracterului căutat. Acesta se obţine ca diferenţă între două valori de tipul char* .
Descăzutul este adresa returnată de funcţie, iar scăzătorul este adresa vectorului în
care se face căutarea.
96 Capitolul 3 -Şiruri de caractere

Programul de mai jos tipăreşte indicele primei apariţii a caracterului 't', şi anume 4:
#include <iostream.h>
#include <string.h>
main()
{
char a[201="Acesta este";
cout<<strchr (a, •t•)-a;;
}

O Aplic.aţia 3.6. în programul următor se citeşte un şir şi un caracter. Dacă


acesta este găsit în şi r, se tipăreşte indicele primei apariţii a caracterului în şirul
solicitat, altfel programul semnalează faptul că acest caracter nu există în şir.
#include <string.h>
main()
{
char a[100], *t,c;
cout<< "introduceti sirul "; cin.get(a,100);
cout<< "caracterul cutat "; cin>>c;
t=strchr(a,c);
if (t) cout<<"Indicele este "<<t-a;
else cout<< " sirul nu contine acest caracter ";
}

,Jw Observaţii
../ Variabila t este de tipul char* .

../ Testul de apartenenţă a caracterului la şir s-a făcut prin a vedea dacă
variabila t reţine sau nu o (adică o adresă nulă de şir).

O Aplicaţia 3.7. In programul următor se listează indicii tuturor apariţiilor


caracterului citit în şir:

#include <iostream.h>
#include <string.h>
main()
{
char a[lOO], *t,c;
cout<< "introduceti sirul "; cin.get(a, lOO);
cout<< "caracterul cutat "; cin>>c;
t=a-1;
do
(
t++;
t=strchr(t,c); .
if (t) cout<<"Indicele este "<<t-a<<endl;
} while (t);
}

Fiecare adresă de subşir, care are ca prim caracter cel reţinut de c, intră în
calculul indicelui acelui caracter - din ea se scade adresa de început.
97

=_'1Cţia strrchr are forma generală:


char *strrchr(const char *s, int' c);

::ela şi rol cu strchr, deosebirea fiind că întoarce adresa ultimei apariţii a


::J~'"""=C~erului (căutarea se face de la dreapta către stânga). Ea este uti l izată în
~ .=za în care se caută ultima apariţie a caracterului Tn cadrul şiru l u i.

- 3. 7. Compararea şirurilor

.... =uncţia strcmp are forma generală:

int strcmp(const char *sl, const char*s2);

-o ul de a compara două şiruri de caractere Valoarea returnată este:

• <0, dacă sl<s2;

• =O, dacă sl=s2;

• >0, dacă sl>s2.

? Dar care este mecanismul prin care se compară două şiruri de caractere?

Fie m numărul de caractere al şirului sl şi n numărul de caractere al şirului s2.
5ă presupunem că primele i caractere ale lui sl coincid cu primele i ale lui s2.

:J În cazul în care codul caracterului i+l al şiru l ui sl este mai mare decât codul
caracterului corespunzător şirului s2, avem sl>s2.

:l În cazul' în care codul caracterului i+l al şirului sl este mai mic decât codul
caracterului corespunzător şirului s2, avem sl<s2. ln cazul in care şi la
această comparaţie avem egalitate, avem patru posibilităţi:

• ambele şiruri au un număr strict mai mare de caractere decât i+l, caz în
care se compară ca înainte caracterele de pe poziţia i+2;

• . sl are i+l caractere, iar s2 are un număr de caractere mai mare decât
i+l, în acest caz sl<s2;

• s2 are i+l caractere, iar sl are un număr de caractere mai mare decat
i+l, în acest caz sl>s2;

• atât sl cât şi s2 au i+l caractere, caz în care sl=s2.

Pe scurt, un şir sl este mai mic ca altul s2, dacă Tn dicţionar sl ar figura
înaintea lui s2.

Exemple: "soare">s;
"tata">mama;
98 Capitolul 3 -Şiruri de caractere

Probaţi relaţiade ordine dintre două cuvinte (şiruri care nu conţin


caractere albe) prin utilizarea programului următor:
#include <iostream.h>
#include <string.h>
main()
(
char a[100],b[100];
int semnal;
cout<< •introduceti sirul a "; cin>>a;
cout<< "introduceti sirul b "; cin>>b;
semnal=strcmp(a,b);
if (semnal<O) cout<<"a<b";
el se
if (semnal>O) cout<<"a>b";
else cout<<"a• b";
}

(;, Funcţia strcmp face distincţie Între literele mari şi miel ale a/fabetului.

> Funcţia stricmp are forma generală:
int stricmp(char *s1,char *s2);

şi acelaşi rol ca strcmp. Diferenţa este că nu face distincţie Între literele mari şi
miel.
Vectori de cuvinte. Există aplicaţii in care este necesar să se lucreze cu n
cuvinte - fnţelegând prin cuvânt o succesiune de caractere care nu sunt albe. în
acest caz avem posibilitatea să declarăm vectori de cuvinte. Acestea sunt, de fapt,
matrice cu elemente de bază de tip char.

Exemplu: char a [10] [25] 1

Fiecare linie din cele 10 ale matricei poate reţine un şir de caractere. Acesta poate
avea cel mult 25 de caractere (inclusiv caracterul nul). Cuvintele pot fi adresate prin
a [O] (primul cuvânt), a [1) cuvântul al doilea, ş . a . m . d.

D Aplicaţia 3.8. Tn programul următor se citesc n cuvinte. Acestea sunt sortate


alfabetic:
#include <iostream.h>
#include <string.h>
main()
(
char cuvinte[lO] [25] , man[25];
int i,n,gasit ;
cout<<"n=";
cin>>n;
for (i=O;i<n;i++)
( cout<<"cuvant ";
cin>>cuvinte[i];
}
-<:.. de informatică pentru clasa a Xl-a 99

do
{ gasit=O;
f or (i=O;i<n-l;i++)
if {strcmp(cuvinte[ i],cuvinte[i+l])>O )
{ strcpy(man,cuvin te[i]);
strcpy(cuvinte[i ],cuvinte(i+l]);
strcpy(cuvinte[i+ l],man);
gasit=l;
}
}
while (gasit);
for (i=O;i<n;i++) cout<<cuvinte[i ] <<endl;

Dacă pot compara două cuvinte, atunci pot să le sortez. Am folosit sortarea
nterschimbare. lată că, algoritmii, cu precădere cei fundamentali, pot fi folosiţi
"'tr-un context diferit de cel în care au fost prezentaţi.

3.8. Subşiruri

" Funcţia strstr are forma generală :

char *etrstr(const char *el, conet char *s2)1

are rolul de a identifica dacă şirul s2 este subşi r (caractere succesive) al şi ru lu i


Jl Dacă acesta este identificat, funcţia returnează adresa de inceput in cadrul
: rului sl, altfel returnează adresa nulă {O). Căutarea se face de la stânga la
:·eapta. În cazul în care s2 apare de mal multe ori în cadrul lui el, se returnează
:dresa de început a primei apariţii.

Exemplu: fie char sl[l="xyzt", s2[]="yz", atunci strstr(el,s2)1


~eturnează sl+l (adresa caracterului y în sl).

:J Aplicaţia 3.9. Tn programul următor se citesc două şiruri de caractere şi se


·estează dacă al doilea este subşir al primului. In caz afirmativ, programul afişează
11dicele caracterului de inceput al subşirului.

#include <iostream.h>
#incl ude <string.h>
m.ain()
{
char sir(l000],subsir[ 25], *t;
cout<<"introduce ti textul ";
cin.get(sir,lOOO );
cin.get();
cout<<"introduce ti subsirul cautat ";
cin.get(subsir,2 5);
t=strstr(sir,sub sir);
if (t) cout<<"este subsir si are indicele "<<t-sir;
else cout<<"nu este subsir";
}
100 Capitolul 3- Şiruri de caracterE

O Aplicaţia 3.10. Ştergerea tuturor apariţiilor unui subşir din cadrul unui şir
Imediat ce am identificat adresa de inceput a subşiruloi, restul şirului (fără subşir)
este copiat pe poziţia de inceput a subşirului.
#include <iostream.h>
.# include <string . h>
main()
{ char sir[1000],subsir[ 25],*p;
int lung_subsir;
cout<<"introduce ti textul ";
cin.get(sir,1000 );
cin.get();
cout<<"introduce ti subsirul ";
cin.aetlBUbB~r,~~)i
lung_ subsir=strlen(su bsir);
p•strstr(sir,sub sir);
while (p)
{ strcpy(p,p+lung_ subsir);
p • strstr(p,subsir) ;
)
cout<<sir;
)

O Aplicaţia 3.11. fnlocuirea tuturor apariţiilor unui subşir cu alt subşir. Vă las pe
dvs. să descoperiţi modul in care programul următor realizează aceasta:
#include <iostream.h>
#include <string.h>
main()
( char sir[100], man[100], sterg[25], ada ug[25], *p;
int lung_ sterg, lung_ adaug;
cout<<"introduce ti textul ";
cin.get(sir,lOO) ;
cin.get();
cout<<"inlocuim subsirul ";
cin.get(sterg,25 );
cin . get();
cout<<"cu subsirul "1
cin.get(ad&ug,25 );
lung_ sterg• strlen(sterg);
lung_ adaug•strlen(ada ug);
p•strstr(sir,ste rg);
while (p)
(
man[O] • O;//subsir vid;
strncat(man,sir, p-sir);
strcat(man,adaug );
strcat(man,p+lun g_ sterg);
atrcpy(sir,man);
p•strstr(p+lung_ adaug,sterg);
)
cout<<sir;
. )
a.~ual de Informatică pentru clasa a Xl-a 101

- =· ~ l te funcţii utile în prelucrarea şirurilor


· _-,::,a strtok are forma generală:
char *strtok(char *sl,.const char *s2)1

-c: ul de executare este următorul:


ş1rulsl este considerat ca fiind alcătuit din o, 1 , .. ., n entităţi separate prin
unul sau mai multe caractere cu rol de separator, iar şirul s2 ca fiind alcătuit
din unul sau mai multe caractere cu rol de separator;

Exemplu: şirul sl. este " mama, tata si bunicul". Şirul s2 este:
" , ". Întrucât caracterele separatoare sunt blank-ul şi virgula, rezultă că
entităţile sunt: ":mama", "tata" "si" "bunicul".

• la prima apelare care trebuie să fie de forma strtok ( sl, s2) 1, funcţia
intoarce adresa primului caracter al primei entităţi (în exemplu "mama"). in
plus, după prima entitate separatorul este înlocuit automat prin caracterul
nul (cod O);

• următoarele apeluri ale funcţiei sunt de forma strtok (NULL, s2);, iar
funcţia intoarce de fiecare dată adresa primului caracter al următoarei entităţi
şi , după ea, este adăugat caracterul nul - aceasta permite ca entitatea să
poată fi extrasă cu uşurinţă.

în momentul în care şi rul rămas nu mai conţine entităţi , funcţia Tntoarce


adresa nulă .

:J Aplicaţia 3.12. In programul următor se citeşte un şir de caractere. Entităţile se


:onsideră a fi cuvinte - şiruri de caractere care nu sunt albe - separate prin blank-uri
ş.lsau virgule. Programullistează entităţile depistate, fiecare pe un rand.
#incl.ude <iostream.h>
#include <string.h>
main()
{ char sir[lOOOJ,separator[]=" ,
" 1 *p;
cin.get(sir,1000);
p=strtok(sir,separator);
while (p)
{
cout< <p< <endl;
p =strtok(NULL, separator);
}
}

Variabila p reţi ne adresa de început a entităţii. Dacă entitatea este găsită, p


va o valoare d ife rită de o, caz în care entitatea este afişată şi se va căuta
reţine
u rmătoarea entitate.
102 Capitolul 3- Şiruri de caractere

LI Aplicatia 3.13. Programul următor citeşte un şir de caractere şi tipăreşte şirul


obţinut pri~ eliminarea blank-urilor. În fapt, se separă entităţile, care sunt afişate
una după alta.
#include <iostream.h>
#include <string.h>
main()
{ char sir [1000] , separator[]=" , 11 , '*p;
cin.get(sir,lOOO) ;
p=strtok(sir,separator);
while (p)
< cout<<p;
p=strtok(NULL, separator); }

)> Funcţia strcspn are forma generală:


size_t strcspn(const char *sl, const char *s2);
şiare rolul de a returna numărul de caractere al şirului sl - caractere consecutive
care încep obligatoriu cu primul caracter- care nu se găsesc in şirul s2.

)> Funcţia standard strspn are forma generală:

size_t strspn(char *al, char *s2);


şi are rolul de a returna numărul de caractere al şirului al - caractere consecutive
care încep obligatoriu cu primul caracter- care se găsesc in şirul s2.
Exemple
1. Fie char* sl="AB2def"; *s2="123";. Atunci: strcspn(sl , s2)
returnează 2 (pentru că primele două caractere • A' şi 'B ' din sl nu se găsesc in
s2 ), iar strspn ( sl, s2) returnează o (primUl caracter din sl nu se găseşte in s2 ).
2. Fie char* sl="AB2def"; *s2="16A32BF";. Atunci: strospn(sl,s2)
returneazăo (pentru că primul caracter • A' din al se găseşte in s2), iar
atrspn (al, a2) returnează 3 (caracterele • A •, 'B', '2' din sl se găsesc in sa).

LI Aplicaţia 3.14. Se citeşte un şir de caractere care nu conţine caractere albe.


Să se verifice dacă şirul este alcătuit exclusiv din caractere numerice.
Vom reţine un vector numit cifre. Componentele lui reţin toate caracterele
numerice 0-9. Şirul de caractere citit are toate caracterele numerice dacă toate
caracterele sale - numărul lor este dat de strlen - se regăsesc printre
caracterele vectorului cifre.
#include <iostream.h>
#include <string.h>
main()
{ char c::uvant[lOO], cifre[]="0123456789";
cout<<"Introduceti cuvantul "; c::in>>cuvant;
if (strspn(c::uvant,cifre)==strlen(cuvant)) cout<<"numeric";
else cout<<"nenumeric";
}
clasa a Xl-a 103

••c;ra 3.15. Se citeşte un de caractere care nu conţine caractere albe.


şir
,.."...~ ::iacă şirul e alcătuit exclusiv din caractere nenumerlce .
:r- ::ne un vector numit cifre. Componentele lui reţin toate caracterele
lm!ll!:!U::E: :-9. Cuvântul citit are toate caracterele nenumerice dacă toate
.ectorului cifre - numărul lor este 10 - nu se regăsesc printre

k~ude <iostream. h>


t~:ude <string.h>
-.::..= \)
:.:.ar cuvant[lOO ], cifre[]="O l23456789 ";
cout<<"Int roduceti cuvantul "; cin>>cuvan t;
1f (strcspn(ci fre,cuvant )==lO) cout<<"co rect ";
else cout<<"inc orect";

=.'"cţia strlwr are forma generală:


char *strlwr(c har *s);

.:::---. erteşte toate literele mari ( 1 A 1 • z •) în litere mici ( 1 a


• • •
1
•• ,
1
z • ).
s:_ ~racterelor rămân neschimbate. Funcţia întoarce adresa s .

=-~cţia strupr are forma generală :


char *strupr(c har *s);

-: _, de a converti toate lîterele mici ( 1 a 1 • • •


1
z 1 ) în litere mari ( 1 A • • • z 1 ).
rs-- caracterelor rămân neschimbate. Funcţia intoarce adresa s. În programul
-a:or se citeşte un cuvânt. Cuvântul citit se tipăreşte cu litere mari.
#include <iostream. h>
#include <string.h>
main()
{ char a[20];
cout<<" J:ntroduce ti cuvantul "; cin>>a;
oout<<stru pr(a);
}

Funcţia atrpbrk are forma generală :

char *atrpbrk( char *sl, char *s2);

s acţionează în felul următor:

Caută primul caracter al şirului al în s2. Dacă este găsit, returneaza adresa
sa din cadrul şiru lui sl şi execuţia se termină, altfel trece la pasul următor.
• Caută al doilea caracter al şirului sl în s2. Dacă este găsit, returnează adresa
sa din cadrul şirului sl şi execuţia se termină, altfel trece la pasul următor.

• dacă nici un caracter al şirului al m:. aparţine şirului s2, funcţia returnează
adresa nulă.
104 Capitolul 3 - Şiruri de caractef"!

Exemplu: fie char *sl="abcdefghi jklmnopqrstuvwxyz", *s2 • "<::12" ;


Atunci strpbrk(sl, s2) returnează adresa sl+2 (adresa caracterului c, pentr.
că acesta este primul caracter din sl găsit in s2).

CJ Aplicaţia 3.16. Se citesc două cuvinte. Se cere să se afişeze toate caracterele


primului cuvant care se regăsesc in al doilea.
#include <iostream.h>
#include <string.h>
main()
{<::har cuvant1[10], cuvant2[10],*p;
cout<<"Xntroduceti primul cuvant •; cin>>cuvantl;
cout<<"Xntroduceti al doilea cuvant •; cin>>cuvant2;
pastrpbrk(cuvantl,cuvant 2);
while(p)
{ cout<<p[O]<<endl;
p++;
p=strpbrk(p,cuvant2);
}
}

Caracterele primului cuvant, Tncepand de la primul, sunt căutate in al doilea


cuvant. Orice caracter găsit se tipăreşte. Aceasta se face pornind de la adresa
şirului, returnată de strpbrk. Apoi, variabila care conţine această adresă se
incrementează. Aceasta pentru ca, la următoarea căutare, să nu fie returnată
aceeaşi adresă de şir.

3.3.1 O. Conversia şirurilor în valori numerice şi invers

Următoarele au prototipul Tn "stdlib. h"


funcţii şi folosesc pentru
conversia valorilor numerice Tn şir şi invers.
~ Funcţia atof un şir către tipul double. Dacă conversia eşuează
converteşte
(se intalneşte un caracter nenumeric) valoarea Tntoarsă este O. Dacă primele
caractere ale şirului sunt albe, acestea sunt ignorate.
double atof(const char *s);

Observaţiile sunt valabile şi pentru următoarele 3 funcţii:


~ Funcţia _atold converteşte un şir către tipullong double:
long double _atold(const char *s);

~ Funcţia atoi converteşte un şir către tipul int:


int atoi(const char *s);

~ Funcţia a tol converteşte un şir către tipullong:


long atol(const char *s);
tru clasa a Xl-a 1os

Se citeşte un text alcătuit din mai multe cuvinte. Se cere să se


:J.;J:::C:..:oorilor numerice întâlnite in text. Se presupune că valorile
u;.;~... r:-oouse corect.

E ~ră în entităţi. Dacă o entitate este alcătuită exclusiv din • + •,


~. atunci ea este convertită în double şi adunată la o variabilă s,

~~ <iostream.h>
~:de <string.h>
~~e <stdlib.h>
IIIC..:::.
:.:.ar sir[lOOO],separator[]=" ",cifre[]="0123456789.+-", *p;
!.cx!b~ e s=O;
~ .get(sir,lOOO);
~st rtok(sir,separator);
~le (p)
i f (strspn(p,cifre)==strlen(p)) s+=atof(p);
p=strtok(NOLL, separator);

oout<<"suma numerelor intalnite in sir este "<<s ;

:::_... :":'<3 ecvt are rolul de a converti o valoare de tip double către un şir.
:..~e rul nul este adăugat automat şirului obţinut. Forma generală este:
~· ecvt(double valoare, int poz, int* zec, int* semn);

• valoare - valoarea de convertit;


• poz - numărul de poziţii pe care trebuie să le ocupe şirul obţinut în urma
con versiei;
• zec -adresa unei variabile, de tip int, care reţine, după apel, numărul
zecimalelor pe care le are numărul;
• semn - adresa unei variabile, de tip int, care are rolul de a memora,
după apel, 1, dacă numărul este negativ, sau o, în caz contrar.

Până în prezent nu am studiat modul in care putem obţine adresa unei


ahile. Reţineţi că dacă a este o variabilă, &:a este adresa ei. Funcţia utilizează
.-:est mecanism de transmitere a parametrilor pentru ca să poată returna anumite
"F_:::ultate pe care le găsim în variabilele respective. in cazul ei, variabila zec va
~,.,e după apel numărul intregilor, iar variabila semn semnul rezultatului.

Funcţia nu inserează în şir nici punctul zecimal, nici semnul numărului. Şirul
:oţi nut
începe cu cifra cea mai semnificativă a numărului. Pentru ca şirul obţinut să
re corect, este necesar să fie prelucrat, fn continuare, de către programator. Dacă
- Jmăru l poziţiilor ocupate de şir (poz) este mai mare decât ocupă data, şirul este
completat în stânga cu un număr de o necesar. in cazul în care numărul poziţiilor
~ste mai mic decât cel ocupat de număr, rezultatul este rotunjit.

Exemple: ecvt (val, 3, &:zec, &:semn) ;


106 Capitolul 3 - Şiruri de caractere

dacă val reţine 1234, atunci se returnează şirul "123", zec reţine 4 şi
semn reţine o.
dacă val reţine 1239, atunci se returnează şirul "124", zec reţine 4 şi
semn reţine o.

dacă val reţine 1, atunci se returnează şirul "100" , zec reţine 1 şi semn
reţine o.
dacă val reţine -0.001, atunci se returnează şirul "100", zec reţine -3
şi semn reţine 1.

Pentru a vă convinge, rulaţi programul următor:


#include <iostream.h>
#include <stdlib.h>
#include <string.h>
main()
{ double numar;
int zec,semn;
char numar_sir[20]="", numar_prel[20]="";
cout<<"n="; cin>>numar;
//convertesc numarul si tiparesc rezultatul
11 asa cum este furnizat de functie
strcpy(numar_sir,ecvt(numar,19,&zec , &semn)) ;
cout<<"Sirul este "<<numar_ sir<<" "<<zec<< " "<<semn<<endl;
11 prelucrez sirul pentru a tipari rezultatul asa cum
11 este asteptat de utilizator.
if (semn) strcat(numar_prel,"-");
strncat(numar_prel,numar_ sir,zec);
strcat(numar_prel,".");
strncat(numar_prel,numar_ sir+zec,3);
cout<<numar_prel;
}

)> Funcţia itoa are rolul de a converti o valoare de tip int într-un şir, a cărui
adresă este memorată in variabila sir. Valoarea baza reţine baza de numeraţie
către care să se facă conversia (de obicei baza este 10). fn cazul bazei 10, şirul
obţinut reţine şi, eventual, semnul"-". Funcţia întoarce adresa şirului obţinut.

char* itoa(int valoare, char *sir, int baza);

)> Funcţia 1 toa are acelaşi efect ca i toa, deosebirea fiind dată de faptul că se
converteşte către şir o valoare de tip long int.

char* ltoa(long value , char *sir., int baza);

)> Funcţia ultoa are acelaşi efect ca itoa, deosebirea fiind dată de faptul că
se converteşte către şir o valoare de tip unsigned long.

char* ultoa(unsigned long value, char *sir, int baza);

Pentru conversii, se mai pot utiliza următoarele funcţii:


vanual de informatică pentru clasa a Xl-a
107

Funcţia

long strto l(co nst char *s, char **en


dptr, int baza );
::.re rolul de a converti un şir către long . Tn afara
şirului care trebuie convertit,
:.J'lcţia primeşte ca parametru de intrare adresa unei variabile de
tip char *. După
::pel, această vari abilă va reţine poziţia primului
caracter din şir care nu poate fi
:oovertit. De exemplu, dacă şirul este "12m 21 ",
după apel, variabila a cărei adresă
; fost transmisă ca parametru reţine adresa carac
terului 1 m 1 • Dar care este rostul
:xistenţei acestei informaţii? Cu ajuto
rul ei se pot depista eventualele erori apărute
~tunel când se introduc datele. Priviţi
şirul dat anterior ca exemplu: e clar
~"'Lllizatorul a dorit să introducă un număr că
, dar in locul unei cifre a tastat m
1 1

:J Aplicaţia 3.18. Programul următor testează dacă o


valoare introdusă este
- u merică şi dacă este cuprinsă în intervalul [10, 20]. Testul propr
a:unci cand comparăm numărul caracterelor iu-zis se face
convertite cu lungimea şirului.
::galitatea are semnificaţia că intreg şirul este nume
ric. Variabila radi x trebuie să
:onţină s , 10 sau 16, adică baza in care
este considerat nu măru l sub formă de şir.
#inc lude <iost ream .h>
#inc lude <std lib . h>
#inc lude <stri ng.h >
main (void )
{ char numa r[20] , *adr esa;
long v;
cin> >num ar 1
v=st rtol( num ar,& adre sa,lO );
if(ad resa- num arl•s trlen (num ar))
cout< <"Da ta cont ine cara ctere nenu meri
ce";
el se
if (v<lO 11 v>20 ) cout «"da ta nume rica
in afar a limi tei ";
else cout <<v< <end l;
}

, Funcţia

doub le strto d(co nst char *s, char


**en dptr )l
:onverteşte un şir către doub le.

, Funcţia

long doub le _ strto ld(c onst char *(s)


, char **en dptr );
~nverteşte un şir către long doub le.
:;. Funcţia

unsi gned long strto ul(c onst char *s,c


har **en dptr ,int baza )1
converteşte un şir către unsi gned long
.
Jn caz de depăşire - adică numărul nu poate fi
memorat deoarece este în
afara tipului - ultimele patru funcţii returnează
cea mai mare sau cea mai mică
1aloare care poate fi memorată de tipul în
care se face conversia, după
cum valoarea este pozitivă sau negativă .
108 Capitolul 3 - Şiruri de caractere

3 .3.11. Citirea şi scrierea şirurilor de c;.aractere din şi în


fişiere text

3.3.11.1. Operaţia de citire

Pentru a citi o linie a unui fişier text, se poate folosi funcţia următoare:

getline(char* Adresa_sir, int Nr_car, char='\n').

Funcţia citeşte un şir de caractere, pân ă când una dintre condiţiile următoare
este îndeplinită:

a) au fost citite Nr_car-1 caractere;


b) a fost detectat sfârşitul de linie: • \n'.

Evident, în acest fel, la o citire pot fi aduse în memorie cel mult 32766
caractere (vezi limitele tipului int).

l~ Observaţii

../ Caracterul care marchează sfârşitul liniei ' \n' nu este inserat în şir, în
schimb este inserat caracterul nul (march ează sfârşitul şiru lui) .
../ Este suficient să apelăm funcţia cu primii doi parametri, ultimul fiind implicit.
../ Dacă se doreşte, ultimul parametru poate fi apelat explicit cu o valoare
convenabilă, caz Tn care citirea se face până la intâlnirea ei sau până când
au fost citite Nr_car-1 caractere.

· ..11. Programulurmător citeşte linie cu linie un fişier text ale cărui linii nu au
~. mai mult de soo de caractere şi afişează liniile pe monitor. Nu se
• cunoaşte lungimea fiecărei linii.
#include <fstream.h>
main()
{ fstream f("f.dat",iosz:in );
char Linie_citita[501 );
while (f.getline(Linie_ ci tita,501))
cout<<Linie_citit a<<endl;
}

Observaţie foarte
(;. efectuează
importantă! Prin f»Linie_citito. , citirea se
• astfel:
a) începând cu poziţia curentă a pointerului se sar toate caracterele albe;
b) se citesc toate caracterele până la întâlnirea unui caracter alb.
De exemplu, dacă o linie a fişierului conţine şirul " Afara ploua", se
citeşte şirul"Afara". Testaţ.i!
iX~ ~al de informatică pentru clasa a Xl-a 1os

3.3.11.2 . Operaţia de scriere

Scrierea se face prin comanda f « sir;. Prin această instrucţiune se


:-e intreg şirul, începând cu poziţia curentă a pointerului, inclusiv caracterele albe.
~ -a.;zaţi exemplul următor:

#include <fstream.h>
main()
{ fstream f("f.dat",ios::out);
f<<" Afara este"<<endl;
f<<"primavaral";
f . close() 1
}

~ 3. 12. O modalitate de conversie de la şir la alt tip

Există şialte posibilităţi de conversie de şiruri la alte tipuri de date. Un tip


:::-ecial, numit istrstream, permite chiar declararea stream~urilor (fluxurilor) de
::şi ruri către variabile. "Citirea" se efectuează cu ajutorul operatorului "»" la fel ca
:. - fişier.

Un şir x
reţine "1 2 3 4 5". O funcţie specială ( numită constructor)
.i:aşează şirului x , un stream (flux), numit ins. Ea are doi parametri: şirul x şi
.-gimea lui: istrstream ins (X, strlen (X)) 1.

"Citirea" se efectuează cu conversie către tipul respectiv. Programul va afişa


- ...merele 1,2,3,4,5 câte unul pe liniei
#include <iostream.h>
#include <strstrea.h>
#include <string.h>
main()
( ohar X[1•"1 2 3 4 5"1
istrstream ins(X, strlen(X));
int nr1
while (ins>>nr) oout<<nr<<endll
}

f. Observaţi cât de simplu se detectează sfârşitul şirului.



Tot aşa, se pot "citi" mai multe cuvinte dintr-un şir. Programul următor citeşte
cuvintele şi le afişează. De această dată se consideră că este şir valid şi cel de
ungime o, fapt care conduce la o nouă citire şi deci la ciclare. Pentru aceasta,
de fiecare dată, se testează ca lungimea şirului citit să fie nevidă.
11 o Capitolul 3 -Şiruri de caractere

Programul este prezentat în continuare:

#include <iostream.h>
#include <strstrea.h>
#include <string.h>
main()
{
char X[)="l mama tata 4 bunica";
char cuvant [20);
istrstream ins{X, strlen{X));
while (ins>>cuvant && strlen(cuvant))
cout<<cuvant<<en dl;
}

Probleme propuse
1. Se de la tastatură un text. Cuvintele se
citeşte consideră separate prin virgulă
spaţiu sau punct. eate cuvinte are textul citit?

2. Se citeşte de la tastatură un text şi o succesiune de caractere. De câte on


întalnim această succesiune in cadrul textului?

3. Se citeşte un text. Două cuvinte pot fi separate printr-unul sau mai multe spaţi i
Se cere să se elimine spaţiile inutile.
4. Se un fişier text care conţine o singură propoziţie, pe o linie. Programul
citeşte
rea ranjează literele în fiecare propoziţie, in ordine alfabetică, păstrand locul
cuvintelor. Numai literele de la 'A' la 'Z' sunt afectate. Celelalte caractere rămân
neschimbate. Ieşirea va fi tot un fişier text. Numele fişierelor este ales de dvs.

Exemple:

THE PRrCE OF BREAD rs $1.25 PER POUND.


ABC DDEEE EF HrrXNO OP $1.25 PPR RRSTU.
THE LrCENSE PLATE READ G76-ZA3.
AAA CDEEEEE GHrLL NPRS T76- TZ3 .
Junior Divlslon

5. Creştere automată. Scrieţi un program care să mărească toate numerele care


apar într-un document (citit dintr-un fişier text) după un procent citit de la tastatură.
De exemplu, dacă se introduce 12, procentul este: 12%. Toate numerele trebuie
afişate cu două zecimale. Testaţi programul dvs. pe fraza:

"Batranul Mc Donald's avea 7 vaci care dadeau 120 de litri de


lapte pe zi. Ele veneau acasa la 4 P.M"

Junior Divislon
1
::.,ual de informatică pentru clasa a Xl- a

ea micşorată a cuvâ
ntului "MISSISSIPPI",
ve rsi un
:.. Eli mi na re. "MSSSS
PP " es te micşorată şi ea :
uro r lite rel or "1". O frază poate fi
:~n ută prin elimi
na rea tut RSR C FAT", prin
RA TE " are ve rsiunea micşorată ''TRV
~AVERSAREA CA
II FE ne literele dintr-o
"E" . Se ce re ca programul dvs. să elimi se
= 11inarea literelor "A ", "1", ă linie. Literele care
t np ut . tx t" şi scrisă pe o singur
ul "i
-ază citită din fişier
tex t .tx t" .
este în fişierul "o ut pu
:m ină se citesc de
la tastatură, iar ieşirea
Ju nio r Di vls ion

ză lim ba jul
co lec tiv - nu ma i pe ntr u ele vii care studia
în
- Problemă de luc ru
l. Şir uri ge ne ral iza te.
:~asca re
ţum iţi de faptul că,
in Pascal, şirurile cu ca
sun teţ i mul
Presupunem că nu
caractere.
~e poate lucra po t
avea cel mult 25 5 de
caractere.
luc răm cu şiruri care
au cel mu lt 30 00 de
De exemplu , do rim să
iar sfârşitul unui şir
reţ inu t ca un ve cto r de 30 00 de caractere,
Jn astfel de şir va fi
valorii o (in binar).
:a fi marcat prin memorarea
subprogramele următoare:
Scrieţi
şir ;
ină lungimea unui
• subprogram care determ
şiruri ;
ite concatenarea a două
• subprogram ca re perm
şir pentru altul;
şir este sau nu sub
bp rog ram ca re identifică dacă un
• su
teşte un şir către
un int reg;
• subprogram care conver
re un şir;
teşte un intreg căt
• subprogram care conver
teşte un şir către
un număr rea l;
• subprogram care conver
teşte un real către
un şir;
• subprogram care conver
anumită poziţie a
altui şir ;
rog ram ca re ins erează un şir pe o
• subp
aşte poziţia
re şterge un subşir
al unui şi r da t (se cuno
• su bp rog ram ca
;
de inc ep ut a subşirului)
intr-un fişier tex t;
su bp rog ram ca re permite sc rierea unui şir

n fişier text.
ite citirea unui şir dintr-u
• subprogram care perm
Capitolul 4
Structuri de date neomogene

4.1. Noţiuni introductive

La "Tehnologia Informaţiei şi a Comunicaţiilor" am învăţat să lucrăm cu


tabele (Excel sau Access}. Să considerăm o linie a unui tabel. De exemplu, ea
conţine numele unei persoane (30 caractere alfabetice), vârsta (un întreg} ş i
salariul brut (un număr real}. Liniile, în totalitatea lor, alcătuiesc tabelul. Făcând
abstracţie de faptul că la "Tehnologia Informaţiei şi a Comunicaţiilor" se folosesc
programe gata făcute, specializate, destinate unor utilizatori cărora nu li se pretinde
să fie experţi în informatică, se pot pune mai multe întrebări, cum ar fi:

!' cum se poate reţine în memorie o linie a unui tabel?


!' cum se poate reţine un tabel pe suport extern?

Răspunsul la prima întrebare este dat de existenţa unor variabile speciale,


care au o structură neomogenă. O astfel de variabilă este împărţită, la rândul ei, în
mal multe subvariabile numite, uneori, câmpurl. Pentru exemplul considerat, o
variabilă, s-o numim Pers, va conţine o subvar\abilă num\\ă Nume, care es\e un şir
de caractere; o alta, numită varsta, pentru care se alege un tip întreg şi o alta
numită Salariu, de un tip real. Astfel, observăm că variabila Pers es:f
neomogenă din punct de vedere al structurii, spre deosebire, de exemplu, de ~~
vector care are toate componentele de acelaşi tip. Cum se declară şi cum ~
utifizeaztJ o astfel de varfabiftJ vom ÎnvtJţa În acest capitol.

în ceea ce priveşte răspunsul la a doua întrebare, o posibilitate de a reţi ne ...-


tabel este dată de crearea unui fişier cu tip.
fr· Crearea şi exploatarea fişierelor cu tip nu face parte din programa dvs., c.?'
·~ .: este recomandabil să le studiaţi în mod individual.

4.2. Structuri neomogene în Pascal

4.2.1. Tipul Record


în practică, apar situaţii în care toate tipurile de date învăţate până în pre:::r'
nu ne sunt de mare folos. Să anal izăm un exemplu.

~.'!î· Presupunem că dorim să prelucrăm anumite date referitoare la mai -:JII


q.~ : elevi. Pentru fiecare elev, cunoaştem:
Vanual de informatică pentru clasa a Xl-a 113

1. Numele şi prenumele- se reţine în string[20J;


2. Nota la matematică- variabilă de tip rea'l;
3. Nota la fizică - variabilă de tip real;
4. Vârsta· variabilă de tip byte;
5. Dacă este băiat sau fată - o variabilă de tip char reţine • B • sau 1 F 1 •

Observaţi faptul că informaţiile referitoare 1~ un elev sunt eterogene (de la


1umele de tip string, până vârsta de tip byte). In Pascal, există posibilitatea ca
ioate aceste informaţii să se regăsească într-un singur tip de înregistrare,
1Umit RECORD.
În programul următor, observăm cum se declară (în acest caz, elev), cum
se citeşte şi cum se afişează o variabilă (numită e) de acest tip:
type elev = record
nume: string[20 ];
n_mat, n _ fiz1 real;
varsta : byte;
sex : char;
end;
var e: elev;
begin
write('nume elev ' ); readln(e.nume);
write('nota matematica '); readln(e.n~t)l
write(•nota fizica ')1 readln (e.n_ fiz};
write(•varsta '); readln(e.varsta);
write ('sexul ') ; readln(e,sex);
writeln(•nume •, e . nume);
writeln(•nota matematica •, e.n_mat);
writeln('nota fizica • , e.n_ fiz) ;
writeln(•varsta 1 , e.varsta);
writeln('sexul •, e.sex);
end.

f Observaţii

v" Pentru a adresa un anumit câmp al variabilei de tip RECORD, se foloseşte
numele ei, urmat de '.', apoi de numele câmpului.

Exemplu: e.nwne reprezintă câmpul nwne al variabilei e.


v" În cazul în care avem mai multe câmpuri adiacente (care urmează unul după
altul) şi de acelaşi tip, le putem declara deodată, separându-le prin virgulă.

Exemplu: câmpurile: "Nota la matematică" şi "Nota la fizică" sunt adiacente


şide tip real, deci declaraţia este:
n _mat, n _fiz: real;
114 Capitolul 4. Structuri de date neomogene \4anual de Informatică~

+ lată cum arată în memorie o înregistrare de acest tip- ocupă 35 de octeţi :


4.2.3. Înregistră ri
2 1 octeti 6 octeti 6 octeti 1 octet f octet
'----v------' '----v------' '---v---' .....__"_...., '----y---J
nume tr.mlll n. fiz virsta .re.t în general, câmp
·"ltrebare: dar RECORD? 1
Câmpurile unei variabile de acest tip pot fi citite sau afişate individual. De
asemenea, li se pot atribui valori, pot intra în calcule, tot individual. program r3;
type elev = rac
Dacă a şi b sunt două variabile de acelaşi tip RECORD se pot face fără DUJI

probleme atribuiri de genul a: •b;. da~

4.2.2 . Acces ul simplificat la câmp uri n_:


v a.:
S e:J
Aşa cum a fost prezentat, modul de acces la câmpurile unei variabile de tip e.n(
RECORD este deosebit de greoi - întotdeauna punem numele variabilei în faţă. În var e1 elev1
realitate, accesul la câmpurile unei astfel de variabile se poate face mult mai uşor, begin
prin utilizarea instrucţiunii w:t:TH, cu forma generală : with e do
begin
with var 1 , var 2 , ••• , varn do instrucţiune write( •n x
with da ta
şi are rolul ca, în cadrul instrucţiunii subordonate, adresarea să se facă simplificat, begil
adică prin utilizarea exclusivă a numelui câmpului. wri t
writ
Rel uăm programul anterior, dar de această dată am utilizat instrucţiunea with: writ
end1
type elev = record end
n ume1 string[~011 end.
n_mat, n_ fizl real ;
varsta1 byte1 Pentru un elev, s
sex1 char1 tipul RECORD (include 2
end1 câmpurilor care o alcătL
var e1 elev; fi putut face şi aşa: e ••
astfel de adresare este 1
begin
with e do Putem avea arie<
begin RECORD, care inc
wri te('nume elev ')1 readln(nume)l
write( ' nota matematica ')1 readln(~t)l
write( •nota fizica '); readln(n_fiz)l 4.2.4. Vectori de
write(•varsta ')1 readln(varsta)1
write ('sexul ')1 readln(sex)l Aşa cum s-a ar~
writeln( •nume ',nume) 1
de tip RECORD.
writeln('nota matematica • ,n~t)1
writeln('nota fizica •,n_fiz)1 Adresarea câmp1
writeln('varsta •,varsta)l paranteze drepte întn
writeln('sexul •,sex)l propri u-zisă se face aşc
end
end. Dacă veste '
82 ·
a-~:
'
următor) ,atur
prin v[i] . nu

.-·· "
~ual de informatică pentru clasa a Xl-a
115

.2.3. Înregistrări imbr icate

În general, câmpurile care alcatuiesc un tip RECORD pot


avea orice tip. O
trebare: dar RECOR D? Raspunsul este afirmativ. Analizaţi progra
mul urmator:
progra m r3;
type elev = recor d
numes string [20J;
data_ n: recor d
zi, lunaa byte;
an: integ er;
end;
n_mat , n_fiza real;
varst aa byte;
sexa char;
end1
var ea elev;
bagin
with e do
begin
write( •num e elev '); readln (nume );
with data_ n do
bagin
write (•ziu a naste rii '); readl n(zi) ;
write ('luna naste rii '); readl n(lun a);
write (•anu l naste rii •); readl n(an) ;
end;
end
end.

Pentru un elev, se citesc numele şi data naşterii. Aceasta


din urma este de
lUI RECORD (Include ziua, luna şi anul naşterii). Pentru adresa
rea simplificata a
m purilor care o alcatuiesc s-a folosit, din nou, instrucţiune
a WITH. Adresarea s-ar
putut face şi aşa: e.da ta_n .zi, e.dat a_n.l una, e.da
ta_n. an. Evident, o
ltfel de adresare este greoaie şi nerecomandata ...
Putem avea oricâte niveluri de imbricare (un tip RECORD
include un alt tip
RECORD, care include un altul, ş.a.m .d.).

2.4. Vectori de înregistrări

Aşa cum s-a arătat, elementele unui vector pot fi de orice tip, deci inclusiv
tip RECORD.
Adresarea câmpurilor se face prin numele vectorului, urmat
de perechea de
ranteze drepte între care este trecut indicele compo
nentei, apoi selecţia
~ ri u-zisă se face aşa cum am învăţat.

Daca veste vectorul, iar înregistrarea este de tip elev (ca


;:i: ur~ător}, atunci numele se selectează prin v[il
în programul
.nume, iar ziua naşter i i
pnn v [il .nume . zi.
116 Capitolul 4. Structuri de date neomogene

Şi de această dată, pentru selecţia simplificată, se poate utiliza cu succes


instrucţiunea WITH, aşa cum rezultă din programul urmă.tor, unde se citeşte un
vector cu n înregistrări de tip elev:

type elev = record


nume: string[20 ] ;
data_n: record
zi, luna:byte;
an:integer;
end;
end;
vector_inregistrari • array[1 •• 9] of elev;
var v: vector_ inregistrari;
n, i: integer;
begin
write('n='); readln(n);
for i := l to n do
with v[i] do
begin
write(•nume elev '); readln(nume);
with data_ n do
begin
write('ziua nasterii '); readln(zi);
write('luna nasterii '); readln(luna);
write(•anul nasterii '); readln(an);
end;
end
end.

4.2.5. Înregistrare cu variante


Nu toate înregistrările au o structură fixă (acelaşi număr de câmpuri) aşa
cum au fost cele prezentate. Sunt cazuri când un tip inregistrare are o parte fixă
urmată de o parte variabilă.

Să presupunem că ne Interesează o situaţie referitoare la studiile unei


persoane. O astfel de înregistrare are o parte fixă dată de câmpurile care reţin
numele, vârsta şi tipul de studii. O persoană poate să nu aibă studii, caz în care nu
mai este necesar să avem alte informaţii, poate să fi făcut câteva clase de şcoală
generală (ne-ar putea interesa câte clase), să fi terminat liceul (caz în care dorim
să ştim anul terminării şi oraşul) sau să aibă studii superioare (şi atunci ne
interesează ~ numele facultăţii şi numărul de ani de studiu în cadrul facultăţii
respective). In concluzie, în funcţie de tipul de studii, înregistrarea arată altfel.

1• Limbajul permite ca
realizează aceasta?
înregistrările să aibă o structură variabilă. Cum se

În primul rând, trebuie reţinut că partea variabilă este plasată in cadrul


inreglstrărll după partea fixă. O parte variabilă se dezvoltă in cadrul
inreglstrării după valorile pe care
le la un câmp situat in cadrul părţii fixe.
..{anual de informatică pentru clasa a Xl-a 117

în programul care urmează, se exemplifică descrierea unui tip de înregistrare


-ariabilă, selecţia părţii
variabile făcându-se în funcţie de yalorile pe care le ia
:âmpul studii. Pentru selectare, se foloseşte o clauză specială numită CASE.
::âmpul după care se face selecţia apare descris în această clauză.
Câmpul selector trebuie să fie de tip ordinar cu un număr finit de
elemente. În funcţie de _valorile pe care le poate lua câmpul selector, se va
:ezvolta partea variabilă. In esentă, se scriu pe rând valorile posibile ale câmpului
~lector. În dreptul fiecărei valori se trece partea pe care trebuie să o conţină
·71registrarea în acest caz. Aceasta se încadrează între paranteze rotunde şi poate
- chiar vidă. Câmpurile prezente între paranteze se scriu separate prin •; •.
!."lalizaţi programul următor (care citeşte o singură înregistrare):
type persoana = record
nume: string[JO];
varsta: byte;
case studii: char of
•f 1
1 ( ) ;
(nr_ cl: integer);
•g•:
'1': (an_t: integer;
oras: string);
•s•: (fac: record
nume_f: string[20];
an_s: byte
end)
end;
var p: persoana;
begin
write( 'nume '); readln(p.nume);
write(•varsta '); readln(p.varsta);
write(•studii '); readln{p.studii);
case p . studii of
'g': begin
wri te ( 'numar clase •); readln(p.nr_ cl)J
end;
'1': begin
write(•anul terminarii liceului ')J readln(p.an_ t);
write('orasul '); readln(p.oras);
end;
's': begin
write('numele facultatii '); readln(p.fac.nume_ f);
writeln(' ani de studii facultate ');
readln{p.fac.an_ s);
end
end {case}
end.

f Observaţii

/ Pentru fiecare inregistrare de acest tip compilatorul rezervă numărul de
octeţinecesari celei mai lungi variante.
/ Este preferabil ca citirea unei variabile de tip înregistrare cu variante să se
facăprin utilizarea instrucţiunii CASE.
118 Capitolul 4. Structuri de date neomogene

4.3. Structuri neomogene în C++

4.3.1. Tipul struct

În practică, apar situaţii în care toate tipurile de date învăţate până în prezent
nu ne sunt de mare folos. Pentru a înţelege aceasta, vom porni de la un exemplu .

..... ;~ Presupunem că dorim să prelucrăm date referitoare la mai mulţi elevi.


...~·
~.
Astfel, pentru fiecare elev cunoaştem:
' ..,,

1. Numele- char [20) ;


2. Prenumele- char (20] ; ;
2. Nota matematică - float;
3. Nota informatică - float;
4. Vârsta - int;
Observaţi faptul că informaţiile referitoare la un elev sunt eterogene: şiruri de
caractere, numere reale sau întregi. Cum am putea rezolva problema prin utilizarea
cunoştinţelor de care dispunem? Ar fi necesari s vectori, câte unul pentru fiecare
Informaţie. Astfel am avea doi vectori cu elemente de bază şiruri de caractere
pentru nume şi prenume, doi vectori cu elemente de bază de tip float pentru note
ş i unul cu elemente de bază de tip int pentru vârstă. Fiecărui elevi , îi corespund
componentele i ale fiecărui vector. O astfel de abordare este greoaie, nenaturală.
Ar fi cu mult mai bine dacă limbajul ar dispune de un mecanism prin care fiecărui
elev să-i corespundă o singură înregistrare.

În C++ există un tip de date, numit atruot, care ne permite acest lucru.
Forma generală este (ce este trecut între paranteze drepte este considerat facultativ):
struct [nume structura]
{
[<tip> <nume variabila[, nume variabila, •• • ]>]
[<tip> <nume variabila[, nume variabila, •• • ]>] 1

} [lista de variabile] 1

Pentru exemplul dat, structura este:


struct elev
{
char nume[20], prenume[20];
float nota~te,nota_info;
int varsta1
};

şi se numeşte elev.
IJan ual de informatică pentru clasa a Xl-a
119
Exi stă două posibilităţi de dec lara re a vari abil elor care _alc
ătuiesc structura.
1. Aşa cum rezultă din form a generală, scriind la sfârşit numele variabilelor:
stru ct ele v
{ cha r num e[20 ], pren ume [20]
;
floa t nota _ma te,n ota_ info ;
int var sta1
}in r1,i nr2 ;

Aici, inr l şi inr 2 sunt două variabile de tipul ele v.


2. Clasic, declarând variabilele aşa
cum suntem obişnuiţi:
ele v inr 1, inr2 1

Definiţia structurii poate fi~ura


atât în cadrul funcţiei mai n c) cât şi
după includerile de fişiere ante în faţa ei,
t. In ce ne priveşte , vom prefera a dou
a variantă .
Se pune următoarea întrebare: fiind
dată o variabilă de un tip stru
este modalitatea de acces la câmpuri ct, care
le ei? Pentru aceasta, se foloseşte
de selecţie directă, notat cu ' .', ope operatorul
rator cu prioritate maximă .
Fie inr o variabilă de tipul elev . Atunci:
• inr . num e - reprezintă şirul num
e al variabilei inr ;
• inr. num e [O] - reprezintă prim
ul caracter al şirului nume;
• inr. not a_m ate - reprezintă câm
pul not a_m ate al variabilei inr .
În programul următor, se citeşte şi
se tipăreşte o variabilă de tipul ele v:
#in clude <io stre am. h>
stru ot ele v
{ cha r num e[20 ], pren ume [20]
1
floa t nota~te,nota_infol
int var sta1
}1

mai n()
{ ele v inr1
cout <<"N Ume "1 cin> >inr .num
e;
cou t<<• Pren ume "1 cin> >inr .pre
num e1
oou t<< "No ta mat ema tica ";
cin>>inr.nota~te;
cou t<< •No ta info rma tica "1
cin> >in r.no ta_i nfo ;
oou t<< "Va rsta "1
cin> >in r.va rsta 1
cout <<" Am oit it t •<< end l
<<in r.nu me< <" "<< inr. pren ume
<<e ndl
<<inr.nota~te~<endl
<<i nr.n ota_ info <<e ndl
<<i nr . var sta;
}
120 Capitolul 4. Structuri de date neomogene

Între două variabile de acelaşi tip struct se poate folosi atribuirea. Astfel,
dacă inrl, inr2 sunt două variabile de tip elev, prin atribuirea inrl:::inr2,
variabila inrl ia aceeaşi valoare ca variabila inr2. În C++, o astfel de atribuire se
mai numeşte copiere bit cu bit.

4.3.2. Înregistrări imbricate


Există situaţii când un tip structurat conţine in interiorul său un alt tip
structurat. Priviţi tipul următor:

struct elev
( char nume(20], prenume[20];
struct
( int clasa;
float note(20] ;
}situatie;
int varsta;
} ;

Structura de bază este elev. În interiorul său se găseşte o altă structură, de


această dată fără nume, dar pentru care există declarată "o variabilă" numită
situatie. În realitate nu este vorba de o variabilă, ci de un nume prin intermediul
căruia poate fi accesat un element al structurii. Am văzut că, in general, elementele
structurii se accesează prin numele variabilei de tipul structurii respective.

l't· .,. Fie inr o inregistrare de tipul elev. În aceste condiţii, accesarea
Q %: 1
elementelor situate în interiorul substructurll se face ca mai jos:

• inr. situatie.clasa - se accesează câmpul clasa al


substructuril;
• inr. situatie. note [o J - se accesează prima notă a
vectorului inclus in substructură.

Exemplul următor prezintă o altă posibilitate de declarare a structurilor:

struct elevl
( char nume[20], prenume[20);
struct
( int clasa;
float note[20);
}situatie_ l, situatie_2;
int varsta;
};

Tipul structurat elevl, subordonează, pe lângă alte tipuri, două structuri


situatie_ l şi situatie_2 . Forma este echivalentă cu cea în care cele două
structuri sunt descrise una după alta.

În practică, se foloseşte termenul "imbricate" pentru una sau mai multe


structuri incluse una in alta, ca mai sus.
\4anual de informatică pentru clasa a Xl- a 121

4.3.3. Înregistrări cu structură variabilă


Pentru început, vom studia un tip aparte de dată structurată, numit union.
Analizaţiprogramul următor:
#include <iostream.h>
union test
{
int a;
char b[lOlt
double CI
};
main()
{
test var;int i;
cin>>var.c; cout<<var.c<<ond lt
c i n>>var.bt cout<<var.bt
}

Variabila var este de tipul union. Ea conţine un întreg, un vector de


caractere şi o variabilă reală. Cele trei câmpuri subordonate ocupă respectiv 2
octeţi, 10 octeţi şi e octeţi. În realitate, pentru toate variabilele s-au reţinut 10 octeţi
- adică octeţii necesari pentru a memora câmpul cel ma/lung. Aceasta înseamnă
că, la un moment dat, se poate memora doar un singur câmp dintre cele
subordonate. În exemplu, am utilizat la început variabila întreagă, apoi şirul
de caractere.
lată forma generală a unei uniuni:
union [<numele uniunii>]
(
<tip> <nume variabila> 1

} [lista de variabile] ;

Cu excepţia faptului că numai un câmp poate fi ocupat la un moment dat,


toate celelalte reguli sunt identice cu cele de la structuri. Bine, veţi intreba, dar la
ce folosesc "uniunile"? Sunt situaţii în care înregistrările nu au format fix, ci ve ~;<lbil.
Ca să fiu clar, voi da un exemplu.
S~ presupunem clj ne intereseaztl o situaţie referitoare la studiile unei
persoane. O astfel de înregistrare are o parte fixă dată de câmpurile care reţin
numele, vârsta şi tipul de studii. O persoană poate să nu aibă studii, caz în care nu
mai este necesar să avem alte informaţii, poate să fi făcut câteva clase de şcoală
generală (ne-ar putea interesa câte clase), să fi terminat liceul (caz în care dorim
să ştim anul terminării şi oraşul) sau să aibă studii superioare (şi atiJnci ne
interesează • numele facultăţii şi numărul de ani de studiu în cadrul facultăţii
respective). In concluzie, în funcţie de tipul de studii, înregistrarea arată altfel.

?• Limbajul perm ite ca


realizează aceasta?
înregistrările să aibă o structură variabilă . Cum se
122 Capitolul 4. Structuri de date neomogene

Uniunile pot fi incluse în structuri. La rândul lor, structurile. pot fi incluse în


uniuni. Pentru exemplul nostru, înregistrarea are o parte fixă care este alcătuită din
numele persoanei respective şi o variabilă de tip char, numită studii, care reţine
tipul studiilor pe care le are persoana respectivă. Astfel, dacă persoana respectivă
nu are studii, reţine • f •, dacă are câteva clase de generală, reţine 1 g • , dacă are
liceul, reţine 1 1 • , iar dacă are studii superioare, 1 s 1 •

Apoi urmează partea variabilă care este o uniune. Aceasta conţine o


variabilă nr_clase - pentru cazul în care studii reţine 1 g 1 , o structură pentru
cazul în care studii, reţine • 1 •, ş.a.m.d. În rest, citirea şi afişarea înregistrării se
face, de fiecare dată, sub swi tch, în funcţie de datele citite sau conţinute. În
concluzie, Înregistrarea are structura variabilfJ, dar ocupă un număr fix de octeţi.

Uniunea poate figura oriunde în interiorul structurii - nu este obligatoriu ca


aceasta să fie scrisă la sfârşitul structurii. Analizaţi programul:
#include <iostream.h>
struct persoana
{ char nume[30], studii;
un ion
{
int nr_clase;
struct
{int an_t;
char oras(20];
}liceu;
struct
{char nume_f[30);
int nr_ani;
}facultate;
}std;
};

main()
{ persoana pers;
cout<<"Nume persoana " ;cin.get(pers . nume,30);
cout<<"Studii f-fara g-generala,l-liceu;s-superioare) ";
cin>>pers.studii;
switch (pers.studii)
{
case •g •: cout<<" numar clase" ;
cin>>pers.std . nr_clase;
break;
case '1': cout<<"anul terminarii liceului" ;
cin>>pers.std . liceu.an_t;
cout<<"orasul ";
cin>>pers.std.liceu.oras;
break;
case •s•: cout <<"nwnele facultatii ";cin.get();
cin.get(pers.std.facultate.nume_f,30);
cout<<"nr ani de studiu ";
cin>>pers.std . facultate.nr_ani;
}
~ anual de informatică pentru clasa a Xl-a 123

//afisez inregistrarea
cout<<pers.nume<<endl;
switch (pers . studii)
{
case 'f': cout<<•n-are saracu• studii"; break;
case 'g': cout<<" numar clase "<<pers.std.nr_clase; break;
case '1': cout<<"a terminat liceul in"
<<pers.std.liceu.an_t
<<" in orasul "<<pers . std.liceu.oras; break;
case •s•: cout<<"numele facultatii"
<<pers.std.facultate.nume_f
<<" nr ani de studiu "
<< pers.std.facultate.nr_ani;
}
}

Probleme propuse
1. Citiţi o variabilă cu următoarea structură:

• nwne_elev: 30 caractere;
• data_nasterii:
zi : intreg;
luna : intreg;
an : intreg;
• nota matematica - real;
• nota informatica - real;
• nota engleza - real;
• media - real;

(media se calculează, nu se citeşte).

Testaţi dacă datele au fost introduse corect. Citirea se va verifica prin afişarea
rezultatului.
2. Citiţi n înregistrări de tipul celei de mai sus şi afişaţi-le în ordinea alfabetică a
numelui.

3. Aceeaşi problemă ca cea anterioară, numai că afişarea se va face în ordinea


descrescătoare a mediilor.

4. Presupunând că înregistrările se referă la un examen de admitere dat, să se


afişeze în ordine descrescătoare a mediilor, în limita unui număr de locuri sau până
când se epuizează toate înregistrările elevilor cu medii mai mari sau egale cu s. În
cazul în care pe ultimul loc avem mai mulţi elevi cu aceeaşi medie, toţi aceştia sunt
consideraţi admişi. Programul va afişa numărul de locuri în plus.
Capitolul 5
Structuri de date

5.1. Conceptul de structură de date

Orice algoritm primeşte date de intrare, le prelucrează şi obţine date de


ieşire . Yn fiecare caz, datele de intrare, datele intermediare - cele create în timpul
prelucrării - şi datele de ieş ire sunt structurate (organizate) într-un anumit fel care
corespunde Intrării, necesităţilor de prelucrare sau a celor de utilizare ulterioară.

Pentru a veni în sprijinul programatorilor, limbajele de programare evoluate


(de exemplu, C++ sau Pascal) pun la dispoziţia acestora posibilitatea organizării
datelor în anumite "şabloane" , numite tipuri de date. Mal precis, prin tip de date
se înţelege:

• o mulţime de valori;
• o regulă de codificare a acestora; 1
• o mulţime de operaţii definite pe mulţimea datelor.

La rândullor, tipurile de date pot fi:

• simple - descriu date care aparţin unor mulţimi care nu sunt


rezultate ca produs cartezian al altor mu lţimi. Exemplu: int .

• structurate - descriu date care aparţin unor mulţimi rezultate ca


produs cartezian al altor mulţimi.

~~: 1. Priviţi declaraţia de mai jos:


. ,. ';· :j
1W''il'ii..,, 1;rii~!':Jy~rlanta· ··r Vari~hţa C!t-+ :r',...S'. ,.: ',
~ ~
1 '1 .11.- 1
~( .
1.,111. 1 ,,, ' ' ' 'P ilsca't ·.:,-,' . " ·' ,,11 t• ' ,,

type rational..record struct r a tional


p,q:integer; { int p,q;
end };

Prin tipul de mai sus se descrie structura unei variabile capabilă să reţină
numere raţionale. Fie A 1 mulţimea valorilor care pot fi memorate prin utilizarea
tipului întreg. Fie A2 =A1 ··{0}. Atunci, o variabilă de tip ratio nal poate memora
valori care aparţin mulţimii A 1 XA2 • Evident, este sarcina programatorului ca valoarea
reţinută de variabila corespunzătoare lui q să fie diferită de o.

în unele lucrări veţi întâlni definiţii ale tipului de date care exclud regula de codificare. Aici
1

se porneşte de la ideea că nu întotdeauna este necesar, pentru a lucra cu tipul respectiv,


să cunoaştem regula de codificare.
Manual de informatică pentru clasa a Xl-a 125

2. Mai jos, este prezentat un alt exemplu:

type vector=arr~ [1 • • 100] of real; double a[lOO];


var a:vector;

Fie B mu lţimea valorilor care pot fi reţinute de tipul real. Atunci, variabila a
poate reţine la un moment dat un element al mulţimii :

BxBx ... x B.
~
de 11 an'

Practica impune utilizarea unor structuri ale datelor de o mare varietate, care
nu se suprapun întotdeauna peste tipurile care pot fi descrise prin limbaj .

.~ ,. Definiţia 5.1. Prin structură de date vom înţelege un ansamblu de date


J,~~·,,...'· caracterizat prin relaţiile existente între ele şi prin operaţiile care pot fi
efectuate cu datele respective.

Vom numi nod, o variabilă de un tip oarecare. De obicei, acest tip este
structurat. După caz, termenul nod poate fi înlocuit cu articol, înregistrare sau
entitate.

fn cele mai multe cazuri, "ansamblul de date" care formează structura este
alcătuit dintr-o mu lţime cu un număr variabil de noduri.

Relaţiile existente Tntre noduri şi operaţiile care pot fi efectuate cu ele vor fi
prezentate prin exemple.

IH: De reţinut!
fJÎ

./ Tipul variabilei care alcătuieşte nodul nu caracterizează structura de date .

./ Structura de date este un concept abstract. Mai precis, conceptul în sine nu


precizează locul unde structura respectivă va fi memorată (clasa de
memorare) şi nici detaliile de implementare (cele care ţin de limbajul folosit) .

./ Tn facultăţile de profil (calculatoare, informatică) se studiază disciplina numită


"Structuri de date". Ea este una dintre disciplinele care se includ în cadrul
mai larg al informaticii, tot aşa cum, de exemplu, algebra se studiază ca
disciplină aparte a matematicii.

./ ln practică s-au impus anumite structuri. Acest lucru este datorat faptului că
există mulţi algoritmi care le utilizează. Ele vor fi tratate, pe scurt, în
paragrafele următoare.
126 Capitolul 5. Structuri de date

5.2. Structura de tip listă liniară

5.2.1. Prezentarea structurii

Definiţia 5.2. O listă liniară este o colecţie de n~O noduri, x1, xl, ... , Xn
aflate într-o relaţie de ordine. Astfel, X 1 este primul nod al listei, X 2
este al doilea nod al listei, ..., Xn este ultimul nod.

Operaţiile permise sunt:

• accesul la oricare nod al listei în scopul citirii sau modificării informaţiei


conţinute de acesta;

• adăugarea unui nod, indiferent de poziţia pe care o ocupă in listă;

• ştergerea unui nod, indiferent de poziţia pe care o ocupă in listă.

• schimbarea poziţiei unui nod în cadrul listei.

Faptul că structura este liniară înseamnă că fiecare nod, cu excepţia


ultimului, are un singur nod succesor (care ii urmează in listă) şi, cu excepţia
primului nod, are un singur predecesor (care se află imediat înaintea lui
in listă).

Dacă vorbim la modul general de o structură de date, nu ne intereseazli,


pentru moment, modul În care aceasta va fi implementatli (adică unde este
memorată şi cum se· efectuează operaţiile permise asupra ei). Pur şi simplu, ne
imaginăm lista ca mai jos, unde fiecare nod i memorează informaţia inf 1 :

in:E 1 ~~ in:E 2 1·•••••••••••••••••••• ••••••••••+! in:E .a


noda nod,.

Exemple de aplicaţii care utilizează liste liniare:

a) Evidenţa situaţiei şcolare a elevilor unei clase. Fie n numărul elevilor. Aici, un
nod reţine numele unui elev şi notele la diversele materii. Vom avea deci, n noduri.
Nodurile vor fi memorate in ordinea alfabetică a numelor elevilor. În clasa
respectivă pot fi transferaţi elevi din alte clase, caz in care se adaugă noduri. Din
clasă, unii elevi pot pleca în alte clase, caz in care se şterg noduri.

b) Se doreşte să se reţină un şir de numere naturale, în ordinea in care au fost


citite de la tastatură. Aici, un nod reţine un număr natural.

Nu toate aplicaţiile utilizează liste liniare. Exemple de structuri care nu sunt


liste liniare:
• se dau n oraşe şi şoselele care unesc unele dintre aceste oraşe;
• arborele genealogie al unei persoane.
\Aanual de informatică pentru clasa a Xl-a 127

5.2.2. Liste alocate secvenţial

Din acest moment ne punem problema să vedem modul in care se poate


implementa o listă liniară.

O primă formă de alocare este cea secvenţială. in cazul acestei alocări,


nodurile listei ocupă poziţii succesive in memorie. Acest tip de alocare se întâlneşte
des, de eate ori utilizăm vectorii. Altfel spus, primul nod al listei va fi reţinut de
primul element al vectorului, al doilea nod al listei de al doilea element al vectorului,
ş . a . m.d.

În continuare, urmărim modul in care putem efectua operaţiile permise cu o


listă liniară .

a) Accesul la oricare nod al listei se poate face cu mare uşurinţă . Dacă dorim să
adresăm nodul k, atunci scriem v [kl (presupunand că vectorul care reţine nodul
se numeşte v).

b) Ştergerea unui nod, Indiferent de poziţia pe care o ocupă in listă

Fie lista alocată secvenţial:

Eliminăm al doilea nod - conţinut 3 (ştergem conţinutul nodului 2):

1 71 1 1 12 1 8 1 9 15 1 8 13 12 1 6 +. . . . .f...........
f...........l.......... I........... +. . . . . +. . . J
J......... I..........

Pentru a păstra structura şi pentru a nu pierde informaţii, este obligatoriu ca


i nformaţiile reţinute de nodurile care urmează nodului şters să fie deplasate către
stânga:

~ Practic, de această dată, nodul 2 (elementul de indice 2 al vectorului) va


.~ reţine informaţia celui de-al treilea nod; nodul 3, va reţine informaţia celui
de-al patrulea nod; ş. a . m . d . ln multe aplicaţi i , această modificare nu prezintă
importanţă.

c) Adăugarea unui nod, indiferent de poziţia pe care o ocupă in listă

Fie lista alocată secvenţial:

1 71 3 1 1 1 2 1 8 19 1 5 18 1 3 1 2 1 6 1
128 Capitolul 5. Structuri de date

Nodul 2 va conţine 5 (practic, adăugăm un nod). Începând cu nodul 2,


deplasăm toate info rmaţiile asociate nodurilor către dreapta:

111 13 11 12 la 19 15 la 13 12 16 1
Acum se completează valoarea reţinută de nodul 2:

171 5 1 3 11 12 1a 19 15 18 13 12 16 1
Observaţii

Şi de această dată, nodurile vor reţine informaţii diferite. De exemplu, nodul


3 va reţine ce anterior reţinea nodul 2, ş.a . m.d.

ln concluzie, la alocarea secvenţială, accesul la nod este foarte rapid, dar


adăugarea sau ştergerea unui nod se fac cu efort de calcul, pentru că
necesită deplasări ale conţinuturilor nodurilor.

5.2.3. Liste alocate înlănţuit

Există două feluri de alocare înlănţuită: alocare simplu înlăntuită şi alocare


dublu înlănţuită. În acest paragraf prezentăm principiile alocării inlăn(uite, urm~md ca
în paragraful următor să arătăm modul in care implementăm listele alocate înlănţuit.

1. O listă lin iară simplu înlănţuită este o structură de forma:

adr1
.J.I..:..ad.::.:.r:...dt---~11!
Ll..:.in:.:..
1
adr2
1 in2 1adrd .. nil

Semnificaţia notaţiilor folosite este următoarea :

• adr1, adr2, adr3, ... , adrn reprezintă adresele ce1or n inregistrări;

• in1, in:l, ... , inn reprezintă informaţiile conţinute de noduri, de altă natură
decât cele de adresă.

• nil - are semnificaţia "nici o adresă" - elementul este ultimul in listă.

După cum observăm, fiecare nod, cu excepţia ultimului, reţine adresa


nodului următor.

2. Alocarea dublu Înlănţuită. Alocarea simp~u înlănţuită permite parcurgerea listei


într-un singur sens (de la stânga la dreapta). In cazul în care se doreşte ca lista să
poată fi parcursă în ambele sensuri, se utilizează alocarea dublu înlănţuită. Aici,
•Aanual de informatică pentru clasa a Xl-a 129

"ecare nod reţine adresele predecesorului şi succesorului său, aşa cum se vede i n
"gura următoare:

1 nil 1in 1 1 adr2 1 • • 1 adr1 1 in2 1 adr3 C: ::::J adrn·11 inn 1 nil
adG ad~ adrn

5.2.4. Implementarea alocării înlănţuite prin utilizarea


vectorilor

Aşa cum am invăţat, lista liniară este alcătuită din mai multe noduri intre care
există o relaţie de ordine. Tn cazul alocării Tnlănţuite, informaţia memorată de
• ecare nod va cuprinde şi un câmp de adresă -in cazul alocării simplu inlănţuită­
sau două câmpuri de adresă -in cazul alocării dublu Tn lănţuită. in acest paragraf
vom studia implementarea alocării simplu in lănţuită.
lată cum se descrie informaţia dintr-un nod, în cazul listelor alocate simplu
înlănţuit, atunci când acesta reţine un nu[llăr intreg.

1 info 1 adresa nodului următor 1


"' Varianta Pascal ·· ' Varlanta ' C~+
type Adresa=Intoger; typedef int adresa;
nod= record struct nod
info:integor; { int info;
adr_urm:AdreQa; adresa adr_ urm;
end; ) 1

• Pentru memorarea listei folosim un vector care are componentele de tip Nod,
descris mai jos:

·Varianta C++
Lista=array[l •• lOOO) of Nod; nod L[1000];
var L:lista~

Din descriere rezultă că lista poate avea cel mult 1000 de noduri. Acesta
este spaţiul disponibil.

Priviţi exemplul de mai jos:

L ~~~-7~-3--~--~--_.-5~~4--~~--5--~4~--o--~~~~
1 2 3 4 5 6
130 Capitolul 5. Structuri de date

Dacă facem abstracţie de implementare, lista este:

1~~.~ J • I o
.~rJ. Ce observăm?

a) Fiecare nod trebuie să reţină şi adresa nodului următor. Adresa este, de fapt,
indicele componentei din vector care reţine informaţia asociată nodului următor.
Prin urmare, necesarul de memorie este mai mare.

b) Nodurile nu ocupă adrese succesive În memorie. De exemplu, deşi primul nod


al listei este reţinut de prima componentă a vectorului (de indice 1), al doilea nod al
listei este reţinut de componenta de indice 3 a vectorului. Din acest motiv, vom face
distincţie între număr.ul unui nod (acesta este în cadrul listei) şi indicele vectorului
unde este memorat. In exemplul de mai sus, nodul 2 reţine 5, iar el este memorat
de componenta de indice 3 a vectorului.

Pentru realizarea practică a implementării apar o serie de probleme. Acestea


vor fi rezolvate în cele ce urmează .

IRl Problema 1. Gestiunea memoriei. Conceptul de listă nu precizează numărul


de noduri pe care ea le poate avea. în practică, numărul de noduri este limitat.
Aceasta înseamnă că, la un moment dat, numărul de noduri poate fi depăşit. Prin
urmare, programul care lucrează cu o astfel de listă trebuie să gestioneze
spaţiul disponibil.

În aceste condiţii, vectorul care reţine nodurile, va fi dublat de un altul, ale


cărui componente reţin 1 sau o, după cum componenta de acelaşi indice a
vectorului L reţine sau nu un nod . Eventual, se poate utiliza o variabilă care
memorează numărul de componente ale vectorului care reţine nodurile.

L ----.1 3 15 1 11
4 1 5 14
1 2 3 4 5 6

ocupat-.! o o
1 2 3 4 5 6

!Rl Problema 2. Accesul la un nod al listei. Spre deosebire de alocarea


secvenţială, unde accesul este imediat, la alocarea înlănţuită accesul se face
începând cu primul nod al listei. Dacă nu acesta este nodul căutat, se trece la nodul
următor (orice nod conţine adresa nodului următor, ş . a.m.d.) .

!Rl Problema 3. Adăugarea unui nod. Să presupunem că în lista de mai jos


dorim să adăugăm, după al treilea nod, un nod cu informaţia 9 .
"'anual de informatică pentru clasa a Xl-a 131

L ... 1 7 13 15 14 11 15 4 1o
1 2 3 4 5 6

ocupat---.1 o o
1 2 3 4 5 6

Prin testarea vectorului ocupat, se observă că primul element liber al


.. ectorului L este cel de indice 2.

a) Marcăm nodul ca ocupat (ocupat [2] va reţine 1).

b) Memorăm informaţia: 9.

L~1713 Q 15 14 11 5 14 1 o
1 2 3 4 5 6

ocupat---.1 o
1 2 3 4 5 6

c) Noul nod va reţine ca adresă următoare, adresa următoare reţinută de al


treilea nod (pentru că introducerea noului nod se face după acesta):

L ... 1 7 1 3 Q !1 5 14 1 1 15 14 1 o
1 2 3 4 5 6

ocupat---.1 o
1 2 3 4 5 6

d) Al treilea nod va reţine ca adresă următoare, adresa nodului nou introdus,


pentru că acesta s-a introdus după el:

L ... 1 7 1 3 Q fi 1 5 14 1 1 12 14 1o
1 2 3 4 5 6

ocupat---.1 o
1 2 3 4 5 6

După această modificare, lista va fi:

17 13 ;J 15 14~ 11 l~l 19 15~ 14 1 o


1 ~3 '-44 5 ~5
132 Capitolul 5. Structuri de date

[8] Problema 4. Ştergerea unui nod. sa presupunem că in lista de mai jos dorim
să ştergem al doilea nod.

L ---+1 1 7 3 15 14 11 1 5 14 1o
1 2 3 4 5 6

ocupat--+-1 o o
1 2 3 4 5 6

Al doilea nod se găseşte in elementul de indice 3. Prin urmare, ocupat [3]


va reţine o. în continuare, primul nod va reţine ca adresă următoare adresa nodului
3 (pentru că acesta urmează nodului 2). Această adresă se ia din cămpul de
adresă următoare a nodului care urmează să fie şters.

L •1 7 14 15 1 4 11 1 5 14 1o
1 2 3 4 5 6

ocupat--+-1 o o o
1 2 3 4 5 G

l Observaţi faptul că, deşi nu am şters informaţia asociată nodului, acesta


• rf devine inaccesibil prin parcurgerea listei începând cu primul nod.

Lista va deveni:

-+ Dezavantajele alocării înlănţuite sunt:

1. Accesul la un nod al listei se face prin parcurgerea nodurilor care îl


preced. Aceasta necesită un efort de calcul.

2. Informaţiile de adresă , prezente în cadrul fiecărui nod ocupă memorie.

-+ Avantajele alocării înlănţuite sunt date de faptul că operaţiile de adăugare


sau eliminare a unui nod se fac rapid.

Exemplele sunt date pentru lista liniară simplu înlănţuită, dar bine înţelese,
ne permit să deducem singuri modul de efectuare a operaţiilor respective pentru
liste dublu înlănţuite .
"'an ual de informatică pentru clasa a Xl-a 133

5.3. Structura de tip stivă

Definiţia 5.3. Stiva este o listă pentru care singurele operaţii permise
sunt:

• adăugarea unui element în stivă;

• eliminarea, consultarea, sau modificarea ultimului element introdus


in stivă.

Stiva funcţionează pe principiul LIFO (Last In First Out) - Hultimul


ntrat primul ieşit".

Pentru a înţelege modul de lucru cu stiva, ne imagin ăm un număr n de


farfurii identice, aşezate una peste alta (o "stivă" de farfurii). Adăugarea sau
scoaterea unei farfurii se face, cu uşurinţă, numai în vârful stivei. După ce am scos
toate farfuriile din stivă, spunem că aceasta este vidă. Oricât ar părea de simplu
principiul stivei, el are consecinţe uriaşe în programare.

-+ Stivele se pot aloca secvenţial (ca vectorii). Fie ST [il un vector. ST (11,
ST (2 1, ..., ST (n) pot reţine numai litere sau numai cifre. O variabilă k indică
in permanenţă vârful stivei, adică ultimul element Introdus.

Exemplificăm, în continuare, modul de lucru cu stiva:

G ln stiva iniţial vidă se Introduce litera A, vârful stivei va fi la nivelul 1 (k=l).

0AB Introducem in stivă litera B, deci k va lua valoarea 2.

0
D Scoatem din stivă pe B (A nu poate fi scos deocamdată); k=l.

0
D Scoatem din stivă pe A; stiva rămâne vidă k=O.

~ Observaţii

./ ln mod practic, la scoaterea unei variabile din stivă, valoarea variabilei ce
indică vârful stivei scade cu 1, iar atunci când scriem ceva în stivă, o
eventuală valoare reziduală se pierde.
134 Capitolul 5. Structuri de date

./ Pe un anumit nivel se reţine, de regulă, o singură informaţie {literă sau cifră) ,


Tnsă este posibil să avem mai multe informaţii.

./ În cazul stivei, alocarea secvenţială nu prezintă mari dezavantaje, ca în cazul


mai general al listelor, pentru că nu se fac operaţii de inserare sau ştergere
în interiorul stivei, ci numai în vârful ei.
Singurul dezavantaj, in comparaţie cu alocarea dinamică înlănţu ită este dat
de faptul că numărul de noduri care pot fi memorate la un moment dat este
mai mic - depinde de gradul de ocupare al segmentului de date.,

./ in literatura de specialitate veţi întâlni termenul PUSH pentru operaţia de


adăugare în stivă a unei înregistrări şi POP, pentru extragere.

'
./ Este posibil să vă întrebaţi : de ce nu putem accesa un element al stivei, chiar
dacă nu este ultimul introdus? Nimeni nu ne opreşte~ doar că, în acest caz,
nu respectăm pri'ncipiul stivei.

a
C'X: Exemple

1. Funcţia Manna-Pnuen. Se citeşte xez. Se cere programul pentru calculul


funcţiei:

x-1, x~12
F(x) = {
F(F(x + 2)),x < 1.2

Vom incepe prin a studia modul de calcul al funcţiei pentru x=lS şi x=S:

f(15)=14;
f (8 )=f(f(10))=f(f(f(12))) =f(f(ll ))=f(f(f(13)))=f(f(12)) =f(11)
af(f( 1 3))=f(12)=11.

Algoritmul va folosi o stivă ST şi o varişbilă numită k, ce indică în


permanenţă vârful stivei.

Algoritmul se bazează pe următoarel e considerente:

• la o nouă autoapelare a funcţiei f, se urcă în stivă {k se incrementează


cu 1) şi se pune noua valoare;

• in situaţia in care, pentru valoarea aflată pe nivelul k, se poate calcula


funcţia, se coboară Tn stivă, punându-se pe acest nivel noua valoare;

• algoritmul se încheie când se ajunge Tn stivă la nivelul o..


~ ual de inform ati că pentru clasa a Xl-a 135

Pentru exemplul dat, prezentăm schematic funcţionarea sa:

12 13

10 10 11 11
8 '1 8 8 8 8

12 13

8 11 11 12
f=11

Programul este prezentat în continuare:

var st:array [1 •• 100] of iinclude <iostream.h>


integer; int st{100],n, k;
n,k : integer; main()
begin { cout<<"n=" ;
write('n='); readln(n); cin>>n;
k:=1t st [ 1]:=nt k=1;
while k>O do st[l]=n;
if st [k ] <12 then begin while (k>O)
k:•k+1 ; if (st[k]<12)
st[k]:=st[k-1]+2 { k++ ;
end st[k]=st(k-1]+2;
else begin }
k:=k-1; el se
if k>O { k- - J
then st [k]:=st[k+1]-1 if (k>O) st[k]=st[k+1l-1 t
end; }
writeln('f=•,st[1]-1) cout<<"n="<<st[l]-1 ;
end. }

~ Se poate demonstra uşor că pentru valori mai mici decat 12, funcţia ia
i -' valoarea 11. Observaţia simplifică mult programul, dar exemplul a fost dat în
alt scop.

2. Fu ncţia lui Ackermann. Se dă funcţia de mai jos, defin ită pe produsul cartezian
NXN. Se citesc m şi n . Să se calculeze Ack (m, n >.

l
n+1, m=0
Ack(m,n)= Ack(m-1 ,1 ), n= O
Ack(m - 1,Ack(m,n -1)),altfel
136 Capitolul 5. Structuri de date

Pentru a elabora algoritmul, studiem un exemplu numeric:


ack(2, 1) •ack(l,ack(2,0))=ack(l,ac k(1,1))=ack(1,ack(O,ack( 1,0)))=
• ack( l ,ack(O,ack(O,l)))•ack(l,a ck(0,2))=ack(l,3)=ack(O ,ack(l,2)) =
aack(O,ack(O,ack(l,l)))~ack(O,ack(O,ack(O,ack(l,O))))=
•ack(O,ack(O,ack(O,ack(O,l))))~ack(O,ack(O,ack(0,2)))=

ack(O,,ack(0,3))=ack(0,4 )•5.

Pentru calculul acestei funcţii, folosim o stivă dublă , ST. Iniţial, valorile m şi n
se reţin la nivetul 1. Pe nivelul k al stivei se reţin valorile curente m şi n . ln funcţie de
valorile acestora se procedează astfel:

• pentru a şi n diferite de o, este necesar un nou calcul de funcţie, caz in care


se urcă in stivă şi pe noul nivel se pun argumentele m şi n-1;

• pentru cazul n=O, se rămâne pe acelaşi nivel in stivă, punând in locul lui m
valoaream-1, iar în locul lui n, valoarea 1;

• in situaţia in care m•O , funcţia se poate calcula; se coboară in stivă şi se


inlocu ieşte valoarea lui m cu m-1, valoarea lui n cu valoarea calculată
anterior.

ln continuare, prezentăm grafic modul de funcţionare a algoritmului pentru


exemplul ack ( 2, 1):

1 o o1
2 o 1 1 1 1 1 1
2 1 2 1 2 1 2 1 2 1

1 o
1 1 1 1
o2 1 3 1 2 1 2
g1 1 3 1 2 1 3 1 3

o1
1 1 o2
1 2 1 2 o3
1 3 1 3 1 3 o4 ack(2,1 )=5.
137
lll.anual de Informatică pent ru clasa a Xl-a

·Var ianta C++

t ype stiva =arr ay [1 • • 1000 0,1 • • 2] int st[10 000 l [2lr


of inte ger; main ()
{ int m, n,k;
var st:s tiva ;
cout < <" m••; cin>> m;
m,n, k : inte ger;
cout << "n=" ; cin> >n;
begi n k=1;
writ e( 'm=' ); st[k ) [O]=m ;
read ln(m ) ; st(k ] [1]= n;
writ e( •n::• ); whil e (k>O )
read ln(n ) ; if (st[k ] [0] && st[k ] [1])
k:=l ; { k++;
st[k ,1] :=m; st(k ] (O]• st[k -1)[ 0);
st[k ,2):= n; st(k ] [1]= st[k- 1] (1]- 1;
whil e k>O do }
if (st[k ,1l< >0) and el se
(st[k ,21< >0) if ( 1 st [k] [1])
then { st [k) [O) •at [k) [0) -1;
begi n st(k ] (1]= 1;
k:=k +l; }
at[k ,1]l= Bt[k -1,1 ]; el se
st[k ,2l:= st[k -1,2 )-1 { k--;
end if (k>O )
el se { at[k ] (O] • st[k ] [0]- 1;
if at[k ,2l= O t [kl [1] =st [k+1 l [1] +1;
then }
begi n }
st(k ,1] :•st[ k,1] - 1; cout <<"a c("< <m< <', '<<
st [k, 2]:= 1 n<<" )•"< <st[1 ] [1)+ 1;
end }
else
begi n
kl=k -1;
if k>O
then
begi n
st(k ,l]:• st[k ,l]-1 ;
st[k ,2] Jast( k+1, 2)+1
end
end;
writ eln( •ac( • ,m, •, • ,n, • )=',
st[1 ,2]+ 1)
end.

u valori mici ale lui m şi


Funcţia lui Acke rman n ia valori extrem de mari pentr
( 4, 4) . lncercaţi ...
n. De exemplu, nu veţi reuşi să calculaţi Ack
138 Capitolul 5. Structuri de date

5.4. Structura de tip coadă

Definiţia 5.4. O coadă este o listă pentru care toate inserările sunt făcute
la unul din capete, toate ştergerile (consultările, modificările) la celălalt
capăt.

Coada funcţionează pe principiul FI:FO (First :rn First OUt)


"primul intrat primul ieşit".

Este cu totul nerecomandabilă alocarea secvenţială a cozii, deoarece in


această situaţie , are loc un fenomen de migraţie a datelor către ultimele
componente ale vectorului (cele de indice mare).

: .. Să presupunem că simulăm o coadă cu ajutorul unui vector cu zece


/:!~·: componente, care reţin numere intregi. Introducem in coad~. pe rand,
• ·! numerele :1, 2, 3, 4.

1 2 3 4

Dacă scoatem din coadă pe 1 şi introducem in coadă pe 5, coada va arăta


in felul următor:

2 3 4 5

Scoatem din coadă pe 2 şi introducem pe 6:

3 4 5 6

l, Se observă acest fenomen de "migraţie".

Probleme propuse
1. Care dintre structurile de mai jos nu este liniară?

a) b) C) d)

2. Un vector reţine pe poziţiile de la 1 la k nodurile unei liste liniare. Fiecare element


al vectorului (nod) reţine un num ăr natural. Se cere să se scrie un subprog ram care
inserează in listă, pe poziţia p , un număr natural citit de la tastatură.
anual de informatică pentru clasa a Xl-a 139

cemplu: k=3; V= ( 1, 2, 3); p=2. Numărul citit este 5. După rulare trebuie să
rem:
:4;V=(l,S,2 ,3).

Un vector reţine pe poziţiile de la 1 la k nodurile unei liste liniare. Fiecare


:!ment al vectorului (nod) reţine un număr natural. Se cere să se scrie un
oprogram care şterge din listă nodul aflat pe poziţia p, l~pS"k..

cemplu: k=3; V= ( 1, 2, 3); p=2. După rulare trebuie să avem: k=2; V== ( 1, 3).

Pentru problema anterioară, care dintre afirmaţiile de mai jos este falsă?

Pentru a şterge un nod aflat pe poziţia p sunt necesare k-p deplasări spre
inga ale conţinuturilor celorlalte noduri.
Subprogramul va avea parametrul k transmis prin valoare.
Subprogramul va avea parametrul k transmis prin referinţă.
Dacă k=p, nu se efectuează deplasări către stânga.
1

Lucrare in colectiv. Implementare a listelor alocate simplu inlănţult. Scrieţi


set de subprograme care creează şi gestionează o listă liniară simplu înlănţuită,
>eată secvenţial (prin utilizarea unui vector).

Lucrare în colectiv. Implementare a listelor alocate simplu inlănţuit. Scrieţi


set de subprograme care creează şi gestionează o listă liniară simplu înlănţuită,
tea tă înlănţuit.

Sortaţi n numere naturale utilizând algoritmul de sortare prin inserţie. Programul


utiliza o listă liniară a loca tă înlănţuit. Care sunt avantajele utilizării listei alocate
anţuit in cazul algoritmului de sortare prin inserţie?

Creaţi o listă liniară cu n noduri alocată secvenţial. Nodul 1 va conţine numărul


nodul 2 numărul 2, ş.a.m.d. Se generează aleator un număr natural k, mai mare
1 şi mai mic decât n. Nodurile de la k la n vor fi, in această ordine, primele in
ă , urmate de nodurile de la 1 la k-1, in această ordine. Afişaţi lista după p astfel
mversări. Observaţie: aceasta este o modalitate de a genera aleator o
rmutare a primelor n numere naturale.

În cazul în care, pentru o listă liniară alocată înlănţuit, câmpul de adresă al

t
mului nod reţine adresa primului nod, se obţine o listă circulară:

1 ~--1--M~r 1 1 ·1 1 ,. . . . . . . . . . . . . ....... 1 1 1
.. 1
Creaţi o listă circulară in care fiecare nod reţine un număr natural. De
: menea, scrieţisubprograme de inserare şi ştergere a unui nod al listei create.
140 Capitolul 5. Structuri de date

10. În jurul arbitrului sunt aşezaţi N jucători numerotaţi în sens orar. Arbitrul,
începând de la un jucător K num ără până la M. Per.soana la care s-a oprit
numă rătoarea este elimin ată din cerc. Arbitrul repetă procedeul începând cu
persoana următoare celei eliminate. Procedeul se repetă până când rămâne un
jucător L. Să se scrie un program care:

• citeşte M, N, K şi-1 determină pe L;


• citeşte M, N, L şi-1 determină pe K.
11. Urmă riţi secvenţa următoare, care se referă la o stivă, unde Push a este
operaţi a prin care se pune în stivă valoarea a, iar Pop este operaţia prin care se
extrage din stivă.

Push 1; Push 2; Pop; Push 3; Push 4; Pop; Pop

Care din afirmaţiile de mai jos nu este adevărată după executarea secvenţei
de mai sus?
a) Din stivă au fost extrase, in această ordine, valorile: 2, 4, 3;
b) stiva este vidă; c) stiva conţine valoarea 1; d) stiva are un singur nivel.

12. Scrieţi subprogramele care implementează o stivă.

13. Se citesc n valori numere naturale. Se cere ca, prin


utilizarea unei stive, vedeţi problema anterioară, valorile citite
să se afişeze in ordine i nversă.

14. În figura alăturată avem 4 vagoane, numerotate cu


1,2,3,4. Se presupune că pe linia c încap toate cele 4
vagoane şi că un vagon aflat pe linia c poate fi mutat numai
pe linia B. Se cere ca, prin mutări succesive de vagoane, să
avem pe linia B vagoanele într-o anumită ordine, in care c
primul vagon este cel aflat la ieşirea de pe linia B. Care
Figura 5.1.
dintre şirurile de vagoane de mai jos nu poate fi obţinut?
a) 1 2 3 4; b) 4 3 2 1; c) 3 4 2 1; d) 3 4 1 2.

15. Să se scrie un program care, pentru problema de mai sus, citeşte ca date de
intrare şirul vagoanelor aflate pe linia A şi şirul vagoanelor care trebuie obţinut pe
linia B. Se cere să se afişeze şirul mutărilor de tip Push nr_ vagon şi Pop
nr_vagon (aţi recunoscut, desigur, o structură de tip stivă) prin care se poate
obţine şiru l vagoanelor de pe lina B. Dacă acest şir nu se poate obţine, in momentul
in care se ajunge în situaţia unei mutări imposibile, să se afişeze mesajul
'Imposibil'.

16. Scrieţi un set de subprograme care gestionează o coadă . Coada va fi


implementată prin utilizarea alocării înlănţuite.

Răspunsuri

1. d) 4. b) 11. b) 14. d)
Capitolul 6

Introducere În recursivitate

5. 1. Prezentare generală

Recurslvltatea este una din noţiunile fundamentale ale informaticii.


..r.ilizarea frecventă a recursivităţii s-a făcut după anii 80. Multe din limbajele de
;~og ramare evoluate şi mult utilizate - Fortran, Cobol - nu permiteau scrierea
:~ogramelor recursive.

Definiţia 6.1. Recursivltatea este un mecanism general de elaborare a


programelor. Ea const~ În posibilitatea ca un subprogram ~ se
autoapeleze.

Recurslvitatea a apărut din necesităţi practice date de transcrierea directă a


'ormulelor matematice recursive. În timp, acest mecanism a fost extins, fiind utilizat
·1'1 elaborarea multor algoritmi.

6.2. Modul În care se realizează autoapelul


În acest paragraf vom învăţa modul în care subprogramele se autoapelează.
'Aecanismul recursivităţii şi modul cum se gândeşte un algoritm recursiv vor fi
prezentate în paragrafele următoare.

6.2.1. Realizarea autoapelului în limbajul Pascal


După cum ştim, în limbajul Pascal subprogramele sunt de două feluri:
proceduri şi funcţii. Oricare ar fi tipul subprogramului, acesta se poate autoapela,
însă modul în care se realizează autotransferul diferă.

-+ În cazul procedurilor, autoapelul se realizează prin apelul procedurii


respective, din interiorul ei. Apelul se face la fel ca în cazul în care procedura
este apelată din exterior.

~ .·, • Procedura prezentată în conti~uare este, recursivă şi afişează, pe rânduri


GţX. separate, numerele 7, 6, ... , 1 .
.~>
142 Capitolul 6. Introducere în recursivitate

procedure exemplu(n:integer);
begin
if n<>O then
begin
writeln (n) 1
exemplu(n-1);
end
end;
begin
exemplu(?) ;
end.

-+ În cazul funcţiilor, autoapelul se realizează printr-o operaţie de atribuire,


operaţie prin care numele funcţiei trebuie s~ figureze în partea dreaptă a
operatorului de atribuire.

~~: Funcţia următoare calculează suma 1+2+ •• • +7:

function suma(n1integer)1integer;
begin
suma:=O;
if n<>O then
suma:=n+suma(n-1);
end;
begin
writeln(suma(7));
end .

6.2 .2. Realizarea autoapelului în limbajul C++


După cum ştim, în C++ funcţiile pot fi de tipul void sau de un alt tip. În acest
din urmă caz, funcţiile returnează o anumită valoare. Oricare ar fi tipul funcţiei ,
aceasta se poate autoapela, însă modul în care se realizează autotransferul, diferă.

-+ În cazul funcţiilor de tip void, autoapelul se realizează prin apelul funcţiei


respective, din interiorul ei. Apelul se face la fel ca în cazul în care funcţia
este apelată din exterior.

i,j?~
.· :~), . Funcţia de mai jos este recursivă. Ea afişează, pe rânduri separate,
~?f.' · numerele 7, 6, ... , 1:
linclude <iostre am.h>
void exernplu(int n)
{ if (ni=O)
{ cout<<n<<endl;
exemplu (n- 1) ;
}
}
main()
( exernplu(7);
}
lanual de informatică pentru clasa a Xl-a 143

~ În cazul funcţiilor care nu sunt de tipul void, autoapelul se realizează prin


instrucţiunea return. Ea este de iorma return . expresie, dar în
expresia respectivă trebuie să intre şi funcţia care se autoapelează.

yJ.. Funcţia următoare calculează suma 1+2+ ••• +7:


- """'· #include <iostream. h>
int suma (int n)
{ if (nt=O) return n+suma(n-l);
}

main()
{ cout<<suma(7);
}

6.3. Mecanismul recursivităţii

:J Problemă. Să se calculeze recursiv n!.

A) Pentru a scrie o funcţie recursivă care efectuează acelaşi calcul, vom porni de
a o definiţie recursivă a lui n 1. Aceasta este:

1, n= O
n! = fact(n) = { n· fact(n-,•) , altfel cu nE N

De exemplu, pentru a calcula 3 t, procedărT) astfel:

3 ! =fact(3)=3Xfact(2)=3X2Xfact(l)=3X2X1Xfact(0)=3X2X1Xl=6.

Funcţia recursivă fact nu face altceva decât să transcrle definiţia recursivă


orezentată anterior:

var n : integer; #include <iostream.h>


int fact(int n)
function fact(n:integer): { if ( In) return 1;
integer; else return n*fact(n-1);
b egin }
if n=O then fact :=l
else fac t : =n*fact(n- 1) main()
end; { int n;
cout<<"n=";
begin cin>>n;
write ( • n= •) 1 cout<<fact (n);
readln(n); }
writeln(fact(n))
end.
144 Capitolul 6. Introducere în recursivitate

2 Care este mecanismul prin care subprogramele se pot autoapela? Să ne


~~i:' amintim modul în care subprogramele memorează parametrii transmişi.

);> Pentru memorarea parametrilor, subprogramele folosesc o zonă de


memorie numită stivă (mai exact, această zonă se numeşte segment
de stivă).
);> Memorarea parametrilor transmişi se face în ordinea în care aceştia
figurează
în antet: de la stânga la dreapta.
J;> Pentru parametrii transmişi prin valoare, se memorează valoarea
transmisă, iar pentru cei transmişi prin referinţă se memorează adresa
variabilei.
);> În cadrul subprogramului, parametrii transmişi şi memoraţi în stivă
sunt variabile. Numele lor este cel din lista parametrilor formali.

În capitolul anterior am studiat proprietăţile structurii numită stivă. Exact


aceleaşi proprietăţi le are şi segmentul de stivă. Singura diferenţă este dată de
faptul că gestiunea segmentului de stivă este făcută automat. Mai exact, codul în
limbaj maşină, obţinut în urma compilării, conţine secvenţe prin care se
gestionează segmentul de stivă.

~ la apelul subprogramului se depun în stivă, în ordine, parametrii transmişi.


De asemenea, tot în stivă se rezervă spaţiu pentru variabilele locale (cele
declarate în subprogram). Acesta este un prim nivel al stivei.

~ În cazul în care subprogramul se autoapelează pe un al doilea nivel, se


depun din nou parametrii transmişi şi se rezervă un nou spaţiu pentru
variabilele locale.

În continuare, prezentăm grafic modul în care funcţionează recursivitatea în


cazul funcţiei fact a programului anterior. Această funcţie nu are variabile locale,
deci în stivă se depune doar parametrul n.

La primul apel al funcţiei fact se creează în


segmentul de stivă o variabilă numită n, care
reţine 3 - valoarea parametrului formal.

Funcţia se autoapelează. De această dată


parametrul n ia valoarea 2. ln stivă se creează un
nou nivel, care reţine n cu valoarea 2.

Funcţia se autoapelează. Parametrul n ia valoarea


1. fn stivă se creează un nou nivel, care retine
1
n
cu valoarea 1.
vanual de informatică pentru clasa a Xl-a 145

Funcţia se autoapelează .
Parametrul n ia valoarea
fact=l
o. în stivă se creează un nou nivel, care reţine n
cu valoarea o. Funcţia ia valoarea 1, autoapelul
nu mai are loc. Se revine pe nivelul 3.

-+1
• fact=l
Pe nivelul 3 se efectuează calculul1*1=1.
n .-- Se revine apoi pe nivelul 2.

Pe nivelul 2 se efectuează calculul 2*1 .. 2.


fact=2
Se revine apoi pe nivelull.

n-+[2]_. Pe nivelull se efectuează calculul 3*2 •6.

..J facl=6
Se revine apoi în ma in ( ) .

Aici se afişează 6 . adică s-a calculat 3 t .

B) Mai puţin eficient, n! se poate calcula şi printr-o funcţie ca în exemplul următor:

·:~~ ·· · varl~mta Pascal 1 ':t· 1!


J!';•''
var val,n,p:integer; #include <iostream.h>
procedura fact(val,n:intege r; void fact(int val,int n,int&
var prod:integer); prod)
begin ( if (val<-n)
if val<=n then { prod*aval;
begin fact(val+l,n,prod );
prod:=prod*val;
fact(val+l,n,prod ) }
end;
end; main()
{ int val,n,p• l;
begin cout<<"n•"; cin>>n;
write (•n='); readln(n); fact(l,n,p);
p:=l; c:out<<p;
fact( l,n,p); }
writeln(p);
end.
146 Capitolul 6. Introducere în recursivîtate

Să analizăm şi pentru acest exemplu modul în care se efectuează calculul,


pentru n=3. Funcţia are trei parametri: doi transmişi prin valoare, unul prin referinţă.

lată conţinutul variabilelor după primul apel:

Il 1 3 Referinţă către p 1
p
val n prod

Pentru că val este mai mic sau egal ca n se efectuează: prod=prod*val;.


Întrucât variabila p a fost transmisă prin referinţă, programul lucrează cu această
variabilă, în loc de prod. În concluzie, se efectuează p=p*val ; . Apoi, funcţia se
autoapelează:

2 3. Referinţă către p

1 3 Referinţă către p

p
val n prod

Pentru că val este 2 - mai mic ca 3 - se efectuează prod=prod*val, după


care funcţia se autoapelează.

3 3 Referinţă către p

2 3 Referinţă către p

1 3 Referinţă către p

p val n prod

Pentru că val este 3- mai mic sau egal cu 3- se efectuează prod-Prod*val, deci p
ia valoarea 6, după care se revine pe nivelul2, apoi 1, apoi în programul principal.

~'
.' ,1' Observaţii

După cum rezultă din aceste exemple, subprogramullucrează cu datele aflate


pe un anumit nivel al stivei pentru variabilele transmise prin valoare, variabilele
locale şi variabilele transmise prin referinţă.

Există posibilitatea ca subprogramul să lucreze direct cu variabilele globale fără


ca acestea să fie transm îse prin r!3ferinţă. După cum ştim variabilele globale pot fi
accesate din orice subprogram. In exemplu, am preferat varianta de a trece ca
parametru variabila transmisă prin referinţă, atât din motive didactice cât şi pentru
a asigura independenţa subprogramului. Dezavantajul este dat de faptul că stiva
va fi încărcată suplimentar cu adresa acestei variabile.
Manual de informatică pentru clasa a Xl-a 147

./ În cazul unui număr mare de autoapelări, există posibilitatea ca segmentul


de stivă să depăşească spaţiul alocat, caz în care programul se va termina
cu eroare .

./ Recursivitatea presupune mai multă memorie in comparaţie cu iterativitatea.

6.4. Cum gândim un algoritm recursiv?

.. Pentru a ne familiariza cu
exemple intuitive.
raţionamentul recursiv, vom porni de la câteva

1. Wlrth. O cameră de luat vederi are în obiectiv un televizor care transmite


imaginile primite de la cameră. Evident, în televizor se va vedea un televizor,
iar în acesta un televizor... , ş.a.m.d.
Pe scurt, în orice televizor se vede un televizor.
2. Anunţ. În anumite restaurante am întâlnit anunţul: "Azi nu se fumeaza!".
3. Constatare. povestea cu săracul care a prins un peştişor de aur. Acesta
Ştiţi
ii spune omului că dacăîi da drumul îl îndeplineşte 3 dorinţe, oricare ar fi ele.
Omul îi dă drumul, îi spune prima dorinţă, peştişorul i-o îndeplineşte, îi spune
a doua dorinţă, peştişorul i-o îndeplineşte. A treia dorinţă a omului este să i
se indeplineasca 3 dorinţe ...

Lăsând gluma la o parte, constatăm ca, în general, o g~ndire recursiv~


exprim~ concentrat o anumitll stare, care se repeta la Infinit. Această g~ndire se
aplică in elaborarea algf!rltmllor recurs/vi, dar cu o modificare esenţială: adăugarea
cond~lel de terminare. In absenţa acestei condiţii, nu poate fi vorba de algoritm,
întrucât algoritmul trebuie să fie finit.

~ În elaborarea algoritmilor recursiv! se aplică raţionamentul: ce se intâmplă


la un nivel, se intâmplă la orice nivel.
Subprogramul care se autoapelează trebuie să conţină instrucţiunile
p corespunzătoare unui nivel.

~ Starea tratată de subprogram se găseşte pe un anumit nivel al stivei


(variabilele rezultate în urma transmiterii parametrilor şi variabilele locale ale
subprogram ului).

~e
e
~~ · Observaţii

./ Un algoritm recursiv se elaborează utilizând acest tip de gândire, nu o


râ gândire precum cea folosită până acum, când am elaborat algoritmi iterativi.
; ./ Pentru orice algoritm recursiv există unul iterativ care rezolvă aceeaşi
;a problemă.
'\J
ra ./ Nu întotdeauna alegerea unui algoritm recursiv reprezintă un avantaj.
./ Numeroasele exemple care urmează vă vor lămuri asupra celor afirmate.
148 Capitolul 6. Introducere în recursivitate

6.5. Aplicaţii recursive

6.5.1. Aplicaţii la care se transcrie o formulă recursivă

D Aplicaţia 6.1. Se citeşte xe z. Se cere programul pentru calculul funcţiei:

x -1 , x ;?:12
F(x) ={ F(F(x + 2)),x < 12

0 Rezolvare. Această aplicaţie a fost tratată iterativ, prin utilizarea stivei. În cazul
tratării recursive, nu facem altceva decât să transcriem definiţia recursivă a funcţiei.

•'•,,' ' i~,~;i,~~~ry:~


·d, 1 ,·'t; .vatlantâPasc~i
. • '
i.!'';r:::~;~ t~ti:!:··· ·<'
,;,.. '· ti ••• ,, '
. ,..
•:·: 1' i
1
··i:':~~li··:l:,,.·
"'' ,,.
"l-W':''rh
varlânta'' c-tilt=iî;,' ;!i· ·::~···
' 1 ,,)' "c: •.' r 1 •· •• , ~ . t}
·;·;· ' · ·.
·'•( .· · ·~~

var x:integer1 #include <iostream.h>


int XI
function manna
(x:integer):integer; int manna (int X)
begin { if (x>•12} return x-11
if x>a12 el se
then manna:=x-1 re turn manna(manna(x+2)};
el se manna:=manna(manna(x+2)) }
end1 main()
begin { cout<<"X"'"I
write('x= ' }l cin>>XI
readln(x) ; cout<<manna(x) ;
writeln(manna(x)} }
end .

În comparaţie cu abordarea iterativă, abordarea recursivă prezintă avantajul


scrierii concentrate.

O Aplicaţia 6.2. Se dă funcţia de mai jos, definită pe NXN. Se citesc numerele


m şi n. Să se calculeze Ack (m, n).

n+1, m=O
Ack(m,n)= Ack(m-1,1), n=O
{
Ack(m -1,Ack(m,n -1)), altfel

0 Rezolvare. Şi această aplicaţie a fost tratată iterativ, prin utilizarea stivei. în


cazul tratării recursive, nu facem altceva decât să transcriem definiţia recursivă
a funcţiei.
Algoritmul recursiv nu necesită comentarii.
'~!anual de informatică pentru clasa a Xl-a 149

Varianta .~ascal ·
var m,n:integer; #include <iostream.h>
int m,n;
function
ack(m,n:integer) :integer; int Ack(int m,int n)
begin { if (lm) return n+1;
if m=O el se
then ack:=n+1 if (In)
el se return Ack(m-1, 1);
if n=O then ack:=ack(m-1,1) el se
el se re turn
ack:=ack(m-l,ack {m,n-1)) Ack(m-1,Ack(m,n -1));
end; }

begin main()
write('m='); readln(m); { cout<<"m•"; cin>>m;
write('n='); readln(n); cout<<"n="; cin>>n;
writeln(ack(m,n) ) cout<<Ack(m,n);
end.

În comparaţie cu abordarea iterativa, abordarea recursivă prezintă avantajul



' scrierii concentrate şi acela al scutirii programatorului de un efort suplimentar
în elaborarea algoritmului.

:J Aplicaţia 6.3. Şirul lui Fibonaccl. Se consideră şirul definit astfel:


o. n=O
U" = 1, n=l
{ altfel
U"_l +U"_z
Se citeşte n, număr natural. Să se calculeze Un.

0 Rezolvare. Funcţia fib transcrie definiţia recursivă:

r Varianta Pascal .. ,.; ' Varianta.C++


var n:integer; #include <iostream.h>
int n;
function
fib (n:integer):integ er; int fib (int n)
begin { if (In) return O;
if n=O then fib: =O el se
el se if (n==1) return 1;
if n=1 then fib:=1 el se
else fib: =fib(n-1)+fib(n-2 ) return fib(n-1)+fib{n-2 );
end;
begin main()
write('N='); readln(n); { cout<<•n•"; cin>>n;
writeln ( fib (n) ) cout<<fib(n);
end. }
150 Capitolul 6. Introducere în recursivitate

În această situaţie, este corect să se folosească un program care calculează


Un iterativ. Să ne imaginăm cum funcţionează această funcţie.

Pentru calculul lui fib(n) este necesar să se cunoască fib(n-1) şi


fib (n-2). Parametrii acestor funcţii sunt depuşi în stivă. Procedeul continuă până
când este calculat fib(n-1), apoi se reia calculul pentru fib(n-2). Acest lucru
este extrem de ineficient pentru valori mari ale lui n (ex. n=lOO). Se calculează
u 100 ca sumă între u 99 şi u 98 . Pentru calculul lui u 99 se calculează u 9 a şi u 97 • După
ce calculăm u 99 , reluăm calculele pentru uga.

O astfel de recursivitate se numeşte recursivitate În cascadă. Rulaţi


programul pentru o valoare mai mare a lui n şi ... aşteptaţi. Cât de simplu se
rezolvă problema iterativ şi cât este de rapidă această metodă...

Prezentăm varianta iterativă, care nu necesită comentarii.

var n,fO,fl,f2,i:integer; #include <iostream.h>


begin
main()
write('n•'); readln(n);
{ int n , fO• O,f1•1,f2;
fO:=O;
cout<<"n•"l cin>>n;
fl:=l;
if (In) cout<<fO;
if n=O then writeln(fO)
e l se
el se
if (n==l) cout<<fll
if n=l then writeln(fl)
el se
el se
{ for (int i =2;i<=n;i++)
begin
{ f2=fO+fli
for i:=2 to n do
fO • fl;
begin
fl • f2I
f2:=f0+fl;
}
fO:=flJ
cout<<f2 J
fl:•f2
end;
}
writeln(f2)
end
end.

D Aplicaţia 6.4. Se dau două numere naturale a şi b. Se cere să se calculeze cel


mai mare divizor comun al lor.

0 Rezolvare. Utilizăm o definiţie recursivă a celui mai mare divizor comun pentru
doui:l numere naturale a şi b:

a a =b
cmmdc(a,b) = c~mdc(a - b, b),a > b
{
cmmdc(a,b - a),a < b
Manual de informatică pentru clasa a Xl-a 1 51

Această definiţie este transcrisă în funcţia recursivă cmm.dc:

var a,b:integer; #include <iostream.h>


int a,b;
function
cmmdc (a,b:integer):integer; int cmmdc (int a,int b)
begin { if (a==b} return a;
if amb then cmmdc:=a el se
else if a>b i f (a>b}
then cmmdc:=cmmdc(a-b,b) return cmmdc(a- b,b);
else cmmdc:=cmmdc(a,b-a) else return cmmdc(a,b-a);
end; }
begin
write('a='); readln(a); main()
write('bc'); readln(b); { cout<<"a="; cin>>a;
writeln(cmmdc(a,b)) cout<<"b="; cin>>b;
end . cout<<cmmdc(a,b);
}

Rezolvăm aceeaşi problemă iterativ (utilizând definiţia de mai sus):

var a,b:integer; #include <iostream . h>


main()
begin { int a,b;
write(•a='); readln(a);
cout<<"a="; cin> >a;
write('b•')l readln(b)l cout<<"b• •; cin>>b;
while a<>b do while (al •b)
if a>b then a:=a-b if (a>b) a=a-b;
else b:=b-a; el se
writeln('cmmdc• ',a) cout<<"cmmdc•"<<a;
end .

Pentru această problemă, este Indiferent ce variantă de rezolvare se alege·.

O Aplicaţia 6.5. Să se scrie o funcţie recursivă pentru a calcula suma cifrelor unui
număr natural.

li::1 Rezolvare. Iniţial, prezentăm varianta iterativă:

var n,s:integer; #include <iostream.h>


begin main()
write(•n~•) ; readln(n)l { int n, ·s=O;
81•0; cout<<"n•"; cin>>n;
while n <> O do begin while (n)
s:=s+n mod 10; { s+=n%10;
n: ~:~n div 10; n/=10 ; }
end; cout<<"s"'"<<s;
writeln( 's=•,s)
end.
' ·· Reţinem Ideea: se izolează ultima cifră, iar lui n i se atribuie câtul întreg
• ' dintre vechea valoare şi 10. Această idee foloseşte pentru a găsi o relaţie de
recurenţă, necesară elaborării variantei recursive. Relaţiile sunt scrise prin
utilizarea operatorilor din Pascal (stânga) şi C++ (dreapta):

0, n=O 0, n=O
S(n) = { S(n) = {
n mod 1O+ S(n div 10), altfel n% 10+S(n 1 10), altfel

Programul de mai jos calculează suma utilizând relaţia prezentată:

Varianta Pascal':·
var n:integer 1 #include <iostream .h>
int n;
function s(n:inte ger):inte ger;
begin int s(int n)
if n=O then s:=O { if (In) return 01
e l :se s:=n mod 10 + s(n div 10) else return n%10 + s(n/10)1
endt }

begin main()
write('n ='}; readln(n }; { cout<<"n ••; cin>>n1
writeln( s(n)) cout<<s (n) 1
end. }

posibilitatea ca un subprogram s~ se autoapeleze prin intermediul altui


Exist~
subprogram. Din moment ce s-a realizat autoapelul, este vorba de recursivitate şi
cum acesta nu s-a realizat direct - ca în exemplele anterioare - o astfel de
recursivitate se numeşte lndlrectă.

0 Aplicaţia 6.6. Se considera şirurile definite recurent astfel: a 0 ...a; b 0 •b; a,b>O:

Să se scrie un program care să citească a, b şi n şi să se calculeze an şi bn.

:·· Varianta Pascal .


1
'·;
"''!''
1
.,.
!
··:li:l•
J 1 varianta c++ ·\!,,!{:·t
' #include <iostream .h>
var a,b:real1 #include <math.h>
nsintege r; double a,b;
function bn(n:int eger):re al; int n;
forward;
function an(n:int eger):re alt double bn ( int n);
begin double an ( int n)
{ i f (In) return a1
if n•O
then an:=a el se
el se an:=(an( n-l+bn(n -1))/3 re turn (an(n-l)+ bn(n-1)) /2;
}
end;
I.J.anual de informatică pentru clasa a Xl-a 153

function bn(nzinteger) : real; 1 double bn Cint n}


begin 1 { if (In} return b;
if n•O then bn:=b else return
else bn:•sqrt(an(n-l)*bn(n-1) sqrt(an(n-1)*bn(n-1));
end; }

main()
begin
{ cout<<"a="; cin>>a;
write('a•'); readln(a);
cout<<"b=•; cin>>b;
writa('b='}; readln(b};
cout<<"n="; cin>>n;
write('n='} ; readln(n);
cout<<an(n}<< ' '<<bn (n} ;
writeln(an(n ):5:10,' '
}
bn(n) : 5:10)
end.

6.5.2. Aplicaţii la care nu dispunem de o formulă de


recurenţă

O Aplicaţia 6.7. Să se scrie o funcţie recursivă care citeşte caractere şi le


afişează in ordinea inversă citirii. Sfârşitul şirului este marcat de caracterul '10".

ltl Rezolvare. Conform principiului, ce se întâmplă la un nivel se întâmplă la orice


nivel, vom gândi funcţia recursivă astfel:

• se citeşte un caracter;
• dacă este diferit de o, se reapelează funcţia;

• se tipăreşte caracterul.

Raţionamentul este suficient pentru a scrie programul.

procedura inv; #include <iostream.h>


var a:char; void inv()
begin { char a; cin>>a;
read(a)J if (al='O') inv();
if a<>'O' then inv; cout<<a;
write(a) }
end;
main()
begin { inv();
inv; cout<<endl;
writeln; }
end.

~ Exerciţiu. Modificaţi programul astfel încât caracterul o - care marchează


sfârşitul şirului - să nu fie tipărit.
154 Capitolul 6. Introducere în recursivitate

D Aplicaţia 6.8. Să se scrie o funcţie recursivă pentru a transforma un număr


natural n, din baza 10 în baza k (1<k<10).

0 Rezolvare. Să ne amintim algoritmul clasic de trecere din baza 10 în baza k.


Numărul se împarte la k, se reţine restul, câtul se împarte la k, se reţine restul...
până când câtul este mai mic decât împărţitorul. Rezultatul se obţine prin scrierea
în ordine inversă a resturilor obţinute. Practic, tipărirea restului se face după
autoapel (ca şi la problema anterioară).

var n , b:integer; #include <iostream.h>


int n,b;
procedura
transform(n,b:integer); void transform(int n,int b)
var rest:integer; ( int rest•n%b;
begin if (n>sb) transform(n/b,b);
rest:=n mod b; cout<<rest;
if n>b }
then transform(n div b,b);
write(rest) main()
end; ( cout<<"n•"; cin>>n;
cout<<"bazaa"; cin>>b;
begin transform(n, b);
write('n='); readln(n); }
write(•baza='); readln(b);
transform(n,b);
writeln;
end.

D Aplicaţia 6.9. Se dă mulţimea {1,2, ... ,n} şi se cer toate permutările acesteia.

Exemplu: pentru n=3 avem:


{3,1,2}, {2,3,1}, {2,1,3}, {3,2,1}, {1,3,2}, {1,2,3).

li'! Rezolvare. În vederea rezolvării problemei, observăm următoarele:

• mu lţimea cu un singur element {1} are o singură permutare: {1);

• din fiecare permutare a mulţimii {1, 2, ••• , n-1} ( ( a1, a:~, ••• , an_1} ), se
obţin următoarele permutări ale mulţimii ( 1, 2, ••• , n):
:a.'lual de informatică pentru clasa a Xl-a 155

r-7 Pentru n=3, priviţi reprezentarea din 321


_.A:-.
.:-:v; • figura alaturata:
21 ~231
213
1
312

Figura 6.1.
Cazul în care n•l

Avem în vedere posibilitatea de revenire la situaţia iniţială : dupa ce am


operat o interschimbare a elementelor ai şi a:l, urmata de o reapelare a funcţiei
pentru valoarea k+l, interschimbam din nou ai cu a:J .

var p: array [1 .• 8] of integer1 #include <iostream.h>


n,i:integert int p[lO],nJ
procedura tipar1 void tipar ( )
var i:integer1 { for (int i=l;i<=nJi++)
begin cout«p [il 1
for i: =1 ton do write(p[i))J cout<<endl1
writeln )
end1
void permut(int k,int n,
procedura permut(k,n : integer; int p[10))
var p:vector); { int i,o,
var i,j,c:integer; if (k••n+l) tipar()/
begin
if k=n+l •1••
{ p[k]•k;
then tipar for (i•l;i<•kJi++)
el se { c•p[i]J
begin p[i]•p[k]J
p[k]t•k; p[k)•CI
for i :=l to k do permut(k+l,n,p);
begin c • p[i]J
c:•p[i )J p[i] .. p[k]J
p[i):•p[k)J p[k)=c;
p [k): =c; }
permut(k+l,n,p)J }
Ct • p[i); }
p[i) :•p[k) 1
p[k) :•c main()
end { cout<<"n="J
end cin>>n;
end; permut (l,n,p);
begin
write('nQ')J readln(n);
permut(l,n,p)
end.
156 Capitolul 6. Introducere în recursivitate

LI Aplicaţia 6.10. Algoritmul Fiii. Se dă o matrice binară. Valorile 1 delimitează


o anumită suprafaţă închisă în cadrul matricei (elementele aparţinând acestei
suprafeţe sunt marcate cu o). De asemenea, se dau coordonatele x şi yale unui
element al matricei, semnificând un punct din interiorul acestei suprafeţe.

$ '...:
' Fie matricea de mai jos:

o 1 1 o
ooo 1
A=
o 1 1 1
1· o o o

Suprafaţa închisă este dată de elementele A ( 1, 1), A ( 2, 1) , A ( 2, 2) ,


A(2,3),A(3 , 1).

Considerăm coordonatele ( 2, 3) ale unui punct situat în interiorul acestei


suprafeţe. După executarea programului, matricea trebuie să arate astfel:

1 1 o
1 1 1
A=
1 1 1 1
1 o o o

J. Algoritmul se dovedeşte extrem de util în colorarea unei suprafeţe închise


atunci când sunt cunoscute coordonatele unui punct situat în interiorul ei.
Acest algoritm este cunoscut şi sub denumirea de algoritmul FILL.

0 Rezolvare. Pentru rezolvare se foloseşte funcţia scriu(), care se autoape-


lează. Iniţial,
matricea se bordează cu două linii şi două coloane ce conţin elemente
care au valoarea 1. Aceasta are ca scop evitarea testului de ieşire din matrice.

Funcţia Scriu () funcţionează astfel:

• testează dacă elementul matricei la care s-a ajuns (de coordonate


(x, y)) are valoarea o;

• în caz afirmativ, acesta ia valoarea 1, iar funcţia se autoapeleatil


pentru fiecare dintre elementele învecinate (sus, jos, dreapta, stânga);

• în caz contrar, se iese din funcţie.

Programul este prezentat în continuare:


Vanual de informati că pentru clasa a Xl-a 157

pr~gram umplere; #include <iostream.h>


var a:matrice: array int a[l.O) [lO),i,j,m,n,x,y;
[0 •• 9,0 •• 9] of integer;
i,j,m, n,x,y:integer; void scriu (int x,int y,
int a[l.OJ[10))
procedura scriu (x,y:integer; { if ( 1a [x] [y] )
var a:matrice); { a[x] [y]=l;
begin scriu(x+1,y,a);
if a[x,yJ=O then scriu (x, y+l., a.) ;
begin scriu(x-1,y,a);
a[x,y]:=l; scriu(x,y-1,a);
scriu(x+l,y,a); }
scriu(x,y+l,a); }
sc:riu(x-1,y,a);
sc:riu(x,y-l,a) main()
end { c:out<<"M="; c:in>>m;
end; cout<<"N="; cin>>n;
for (i=l;i<=m;i+ +)
begin for (:j=l;:i<=n;j++)
write( 0 M= 0 ) ; readln(m); { cout<<"a["<<i<<','
write( 0 N• 0 ) ; readln(n) ; <<j<<"l=";
for i:=1 to m do cin»a[i] [:j];
for :i :=1 t.o n do }
begin
for (i=l; i<=n;i++)
write ( • a [ •, i, o, • , j, •] =o);
{ a(O] [i]=l;
readln(a[i,j])
a[m+1] [il=l;
end;
}
for i:=l to n do
for (i=l;i <~;i+ +)
begin
{ a[i] [O]ml;
a(O,il ;o•1;
a(i] [n+l]=l;
a(m+l,i] :=1
end;
for i:=l to m do cout<<"X="; cin>>x;
begin cout<<"Y="; cin>>y;
a[i,O] :=1 ; for (i=l;i<am;i++)
a[i,n+ll: • l { for (:j=l;:i<=n;j++)
end; cout«a[iJ [j];
write( 0 X= 0 ) ; readln(x); cout<<endl;
write( 0 Y= 0 ) ; readln(y); )
for i:•l tom do scriu(x,y,a);
bagin cout<<endl<<endl;
for j:=1 ton do for (i=l;i<=m;i++)
write(a[i,:j]); { for (j=l;:i<=n;j++)
writeln cout«a[i] [j];
end; cout<<endl;
sc:riu(x,y,a); }
writeln; }
writeln;
for i:=l to m do
begin
for j:=l ton do
write(a[i,:j]);
writeln
end
end.
158 Capitolul 6. Introducere în recursivitate

O Aplicaţia 6.11. Problema fotografiei (aplicaţie Fiii). O fotografie alb-negru


este reprezentată sub forma unei matrice binare. Ea înfăţişează unul sau mai multe
obiecte. Porţiunile corespunzătoare obiectului (sau obiectelor) în matrice au valoarea
"1". Se cere să se determine dacă fotografia reprezintă unul sau mai multe obiecte.

În matricea următoare sunt reprezentate două obiecte:

A=[t ~ ~ il·
iar în matricea de mai jos este reprezentat un singur obiect:

A=[~ !!Il
Ca şi în problemele anterioare, pentru a evita testul ieşirii din matrice,
aceasta este bordată cu linii şi coloane având valoarea "o". Algoritmul este tot cel
din problema anterioară (FIII), dar aici căutarea se face pe 8 direcţii.
În programul principal se citeşte matricea şi se caută primul element "1"
printre elementele acesteia. Se apelează apoi funcţia compact< > care are rolul de
a marca cu o toate elementele matricei care aparţin acestui prim obiect identificat.
La revenire, se testează dacă mai există elemente cu valoarea "1" în matrice. În
caz afirmativ, se poate trage concluzia că în fotografie aveam Iniţial mal multe
obiecte (altfel, fotografia conţinea un singur obiect) .

, '''ii'
•,,JI, ' '
,". . :yarl~nta Pascal·
j

var a1 array [0 •• 9,0 •• 9] of #include <iostream.h>


integer; int a[10] [10], i,j,m,n,
i,j,m,n,x,y1integer; x,y,gaeit;
gasit1boolean;
void compact(int x,int y,
procedura compact int a[10] [10])
(x, y1integer; var a1matrice);
{ if (a[x] [y])
begin
{ a[x] [y)•O;
if a[x,y]=l then begin
compact(x-1,y,a);
a[x,y]I•Ot
compact(x-1,y+1,a);
compact(x-l,y,a);
compact(x,y+l,a);
compact(x-l,y+l,a);
compact(x+1,y+1,a);
compact(x,y+l,a)t
compact(x+l,y,a);
compact(x+l,y+l,a);
compact(x+1,y-1,a);
compact(x+l,y,a);
compact(x,y-1,a);
compact(x+1,y-1,a);
compact(x-1,y-1,a);
compact(x,y-l,a);
)
compact(x-1,y-1,a);
}
e nd
end;
.. ..al de informatică pentru clasa a Xl-a 159

Z~egin main()
~ite('M='}; readln(m); { cout<<"M=";cin>>m;
~ite('N='}; readln(n); cout<<"N=";cin>>n;
for i:=l to m do for (i=l;i<=m;i++)
for j:=l ton do for (j=l;j< .. n;:i++)
begin { cout<<"a["<<i<<','
wri te ( 'a [ ' 1 i 1 ' , ' 1 j, ' ] =' ) ; <<:i<<"l=";
readln(a[ilj]) cin»a[i] [j];
end; }
for i:=l to n do for (i=l;i<=n;i++)
begin { a(O] [i]=O;
a(O,i]:=O; a(m+ll [i]=O;
a(m+l,il :=O; }
end; for (i=l ; i<w.m;i++)
for i:=l to m do { a[i] [0]=0;
begin a[i] (n+ll=O;
a(i,O]:=O; }
a(i,n+l] :•0 x=O ;
end; do
x:sO; { X++;
repeat y=O;
x:=x+l; do
y:=O; { y++;
repeat while (y 1=n &:&: a [x] [y] 1=1) ;
y:=y+l }
until (y=n) or (a[x,y]=l) while ((xl.,m) &:&:a[x][y]l=l);
until (x=m) or (a[x,y]•l); compact(x,y,a);
compact(x,y,a); gasit=01
gasit:=false; for (i=l;i<=m;i++)
for i:=l tom do for (j=l;:i<=n;:i++)
for j :=l ton do if (a[i] [j]==l) gasit=l;
if a[i,j]=l then if (ga sit)
gasit: .. true; cout<<"mai multe obiecte" ;
if gasit else cout<<"un obiect";
then wri teln ( • mai mul t .e }
obiecte•)
else writeln( • un obiect')
end .

Probleme propuse

1. Calculaţi recursiv suma a n numere naturale cîtite.

2. Calculaţi recursiv expresiile:

a) 1X2+2X3+ ••• +nx(n+1);


b) 1+1/2+ ••• +1/n;
c) 1/(2X3)+2/(3*4)+ ••• +n/((n+l)(n+2)).
160 Capitolul 6. Introducere în recursivitatE

3. Se citesc n şi k (numere naturale n>k). Calculaţi recursiv C,~, prin utilizarea


formulei de recurenţă: c,; =
C,~~11 + C,~_ 1 • Este eficient?

4. Scrieţi un program iterativ care rezolvă problema anterioară utilizând aceeaş


formulă.

5. Calculaţi recursiv C: prin utilizarea formulei:


1, k = O;
c: = n- k + 1 ck - 1
{ altfel.
k "
Comparaţi timpul de rezolvare cu cel necesar pentru rezolvarea problemei 4.

6. Scrieţi un subprogram recursiv prin care calculatorul ghiceşte un număr nature


ascuns de dumneavoastră (numărul este cuprins între 1 şi 30000). Aturc
când calculatorul propune un număr i, se va răspunde prin:
l, dacă numărul este prea mare;
2, dacă numărul este prea mic;
o, dacă numărul a fost ghicit.

7. Scrieţi un subprogram recursiv care cal culează câte cuvinte distincte cu 2r.
caractere se pot forma cu n caractere A şi n caractere B.

8. Calculaţi conform formulei următoare valoarea maximă reţinută de un vector~


numere naturale cun componente:

V(J], Il = 1;
max(V[l],V[2], ... V[n]) =
· { max(max(VP ], V[2l, ... vrn - 1]), Y[nl) altfel.

9. Se citeşte un număr natural n . Se cere să se scrie o funcţie recursivă ca·:


returnează cea mai mică bază în care se poate considera n.

10. Scrieţi o funcţie recursivă care testează dacă un număr natural n>l este prirr

11. Scrieţi o funcţie recursivă care returnează suma elementelor pare ale ur-
vector citit.
Exemplu: Pentru n•4 şi V= ( 2 1 2 1 s 1 6) , se returnează 1 o.

12. Scrieţi o funcţie recursivă prin care se testează dacă un număr natural x s:c
regăseşte între componentele unui vector v cu n numere naturale.

13. Scrieţi o funcţie recu rsivă care primeşte ca parametri două numere natura:
i<j şi un număr real xe [ i1jl şi returnează [xJ (parte întreagă din x). Nu s;
vor folosi funcţiile specializate ale limbajului.
::e informatică ntru clasa a Xl-a 161

3.:-eţi o funcţie recursivă care verifică dacă un vector cu componente numere


-:este palindrom (afişarea componentelor de la 1 lan coincide cu afişarea
.:.;; a n la 1).

:..: 1eţio funcţie recursivă care returnează numărul cifrelor pe care le are un
..:. 'latural primit ca parametru.

Scrieţiun subprogram recursiv care afişează, cifră cu cifră, oglinditul unui


a· natural.
plu: pentru n=123, se afişează 321.

3-:tieţi un subprogram recursiv care returnează , oglinditul unui număr natural.

~~plu: pentru n=123, se returnează 321.

S crieţi o funcţie recursivă care testează dacă un vector cu n numere naturale


-e numai valori distincte, caz în care funcţia returnează true, iar în caz contrar,
..,... ează false.

Scrieţi un subprogram recursiv care descompune în toate modurile posibile un


r natural n în două numere n 1 şi nl, n 1~l a căror sumă este n.

Pentru un vector cu n componente o sau 1 care are semnificaţia de număr


, se cere să se scrie o funcţie recursivă care afişează numărul în baza 10.

E:lemplu: pentru n=4 şi V= ( 1011 >, se va returna 11.


2· . Sc rieţi o funcţie recursivă care afişează valoarea unui polinom în punctul a.
::veficienţii polinomului sunt daţi într-un vector. Astfel, pentru V= ( 1, 2, 3 > avem
:o nomul P=xl +2x+3.
22. Fie funcţia definită pe N.XN•. Se citesc n şi k. Se cere să se scrie o funcţie
-ecursivă care evaluează funcţia:

O, k>n
S(n,k)== 1, kE {l,n}
{S(n -1 , k - 1) + kS(n - 1, k) 1< k< n

23. Cal cu laţi s (n, k) nerecursiv.

24. Calculaţi recursiv şi nerecursiv funcţia definită pe N*XN:

0, k ==O sau k > n;


F(n, k) == 1 k == n;
{
F(n -l ,k -1) - nF(n - l,k) 1 < k < n.

Comparaţi eficienţa celor două moduri de calcul.


162 Capitolul 6. Introducere în recursivitate

25. Să se calculeze recursiv şi nerecursiv P (n, k), definit pe N•XN•:

P(n+k,k)=P(n,l) +P(n,2)+ ••• +P(n,k), dacă l<k<n;


P(n,k)=l, dacă k=zl sau k=n;
P(n,k)=O,dacă k>n.

26. Calculaţi iterativ şi recursiv cel mai mare divizor comun pentru două numere
naturale m şi n, utilizând algoritmul lui Euclid:

cmmdc(nrnmodn), n*O d ( ) { cmmdc(nrn%n), n;t;O


cmm dc (m11 ) =
{ m, cmm c mn =
n=O m, n=O
Pascal C++

27. Ce se afişează la: wri teln ( t ( 12) ) ; 1 cout <<t ( 12) ; ?

function t(n:integer):inte ger; int t(int n)


begin { if (n) return lO*t(n/10)+
if n<>O then t:•10*t(n div 10) n%10;
+(n mod 10) else return O;
else t:=O; }

a) 12;
b) 21;
c) eroare de executare;
d) o.
28. Ce calculează funcţia următoare?

Varianta Pasc~l

fl,lnction
begin
if n<>O then return (n%2=•0)*n +t(n-1);
t:•ord(n mod 2=0)*n+t(n-1) elae return O;
else t: • O; }
end;

a) suma primelor n numere naturale impare;


b) suma primelor n numere naturale pare;
c) suma numerelor naturale pare strict mai mici decât n;
d) suma numerelor naturale pare mai mici sau egale cu n.
informatică pentru clasa a Xl-a 163
.:ual de

~ Pentru care dintre numerele de mai jos, care sunt parametri de intrare pentru
:cţia următoare, ultimele 2 numere afişate vor fi o?

~rocedure t(n:inte ger); void t(int n)


~gin { if (n>15)
if n>lS then { t(n/16) ;
begin cout<<n %16<<e ndl;
t(n div 16); }
writeln (n mod 16) else cout<<n <<endl;
end }
else writeln (n);
end;

~} 295;
~) 1024;
:. ) 1000;
:. ) 10000.

- estele de la 30 la 33 se referă la funcţia de mai jos, unde Vl şi v2 sunt vectori


;u n componente numere naturale:

·t·· ciM'I_,t,, ·• '_•' ··' ''•· c·'' ':t ~'"''~·


Varianta ++ •· .,. ,·
, 1.
1 IH• ~.t:1 .,

procedu ra t(n,i,js intege r); void t(int n, int i, int j)


begin { if (i<•n && j<•n)
if (i<=n) and (j<•n) then { i f (Vl[iJ <V2 [jJ)
begin cout<<V l[i++l<< " "1
if Vl[iJ<V 2[jJ then else cout<<V Z(j++J<< w "1
begin t(n,i,j );
write (Vl(i], ' '); }
is•i+l }
end
el se 11 Vl si V2 contin date
begin 11 incepan d cu indicel e 1
write (V2[j), ' ');
j :•j+l
end;
t(n,i,j );
end;
end;

şi v2 .. (4,5,1) ce se afişează la
30. Dacă n este egal cu 3, Vl:o(l, 2,3)
apelul t (n, 1, 1)?

a) 1 2 3;
b) 1 4 2 5 3 1;
c) cerinţa nu este corectă;
d) 4 5 1.
164 Capitolul 6. Introducer e în recursivitate

31. Care dintre afirmaţiile de mai jos sunt corecte dacă n este 3 şi apelul este
t(n,1,1) ?

a)

întotdeauna se vor afişa cel mult 6 numere;
b) întotdeauna se vor afişa cel puţin 3 numere;
c) întotdeauna se vor afi şa cel mult 3 numere;
d) întotdeauna se vor afişa cel puţin 6 numere.

32. Dacă n este 3, pentru care din datele de mai jos se afişează un număr maxirr
de valori distincte, dacă apelul este t ( n, 1, 1)?

a) V1=(1,2 ,8) şi V2:::o(4,5 ,6);


b) V1 .. (1,2,5) şi V2 =(4 , 5 , 6 ) ;
c) V1=(4 ,5 ,6) şi V2=(1,2 ,3);
d) V1=(1,2 ,3) şi V2=(4,5 ,6).

33. Ce va afişa funcţia la apelul t(1,1,n) dacă n=3, V1=(1,2 ,3) ş


V2=(4,5 ,6)?

a) 1 2 3; b) 1 2 3 4 5 6;
c) 4 5 6; d) nici o valoare.

Testele de la 34 la 36 se referă la programul u rmător:

. .,.J ·';.,, Varianta ~ascal ,,,


type vector=a rray[1 • • 10] of #include <iostream .h>
integerJ int m, n,i , V[20];
var m,n,i:in teger;
V:veotor ; void b(int k, int n)
{ i f (n)
procedur a b(k,n:in teger); { b(k+l,n/ 2);
begin if (n%2) cout<<V[ k]<<endl;
if n<>O then }
begin }
b(k+l,n div 2);
if n mod 2=1 then main()
writeln( V[k]);; { int i,n;
end c in>>n;
end; for (i•l;i<un ;i++)
cin»V[i );
begin
cin>>m;
readln (n) ;
b(l,m) 1
for i : =l ton do readln(V (i]);
}
readln (m);b(l,m );
end.

34. Dacă n=4, V= ( 1, 2, 3, 4) şi m=2, ce afişează programul?


a) 4; b) 3; c) 2; d) 1.
nual de i nformatică pentru clasa a Xl- a 165

. Dacă n=4, pentru care dintre valorile de mai jos ale lui m se afişează două

ori?

b) 3; c) 2; d) 1.

pentru care valoare a lui m se afişează toate cele 4 valori ale

b) 16; c) o; d) Nu există o astfel de valoare.

37. În funcţia de mai jos înlocuiţi linia cu una dintre instrucţiunile următoare,
11
•••
11

astfel încât funcţia să-şi încheie execuţia fără eroare pentru orice valoare admisibilă
a argumentului:

Varianta Pascal
x(n:inte ger):inte gerl int x(int n)
begin {
if n<>O then if (n)
begin (
writeln( n); cout<<n1

end }
end; }

a) x:=x(n-2 ) ; a) re turn x(n-2) ;


b) x:=x(n mod 2); b) re turn x(n%2);
o) x:=x(n-1 ); c) re turn x(n-1);
d) x:=x(n div 2). d) re turn x{n/2).

Testele 38 şi 39 se referă la funcţia de mai jos:


.,...,.....
Varianta Pascal , ,
1
Varianta C++
'
function an(n:int eger):in teger; int an(int n)
begin {
if n=O then an: =l if (n==O) re turn 1;
e lse an:=3*an (n-1)+7 else return 3*an(n-1 )+7;
end; }

38. Dacă funcţia este apelată prin an< 4), de câte ori se autoapelează?

a) de 4 ori; b) de 3 ori; c) de s ori; d) depinde de stivă.

39. Pentru care dintre valorile de rnai jos, care sunt parametri de intrare pentru
funcţia an, executarea funcţiei se termină cu eroare?

a) o; b) -1; C) 1; d) ~ici una dintre valorile de mai sus.


166 Capitolul 6. Introducere în recurslvi tatE

40. Pentru programul de mai jos, de câte ori se autoapelează funcţia an?
.. •
'!<
;pf Varianta
. .r.
~

Pascal
. . . ~1;· 1 :
- • o
' - ti
" ~<' .
~··: •,:. ;·.1~"'·
h• ~~·. ' ··~' .~:·~,,,; V~rianta:.~t:+ ~!:: ,,,!;~ .,..
functio n an(n:in teger):i nteger; #includ e <iostrea m.h>
begin int an( int n)
i f n=O then an:=2 { if (n==O) re turn 2;
el se el se
if n=l then an:=-1 if (n••1) re turn 1;
el se an:=an( n-1)-an (n-2)+1 el se
end; re turn an(n-1) -an(n-2 )+1;
}
begin
writeln (an(4)) ; main()
end. { cout<<a n(4); }

a) de 8 ori; b) de 4 ori; c) de 9 ori; d) de 2 ori.

Indicaţii / Rezolvări

Sn = {0, dacan = O
Sn_1 +n, altfel

7. Există (2n) 1 permutări ale literelor. Întrucât pentru fiecare permutare nu


contează dacă s-a inversat A cu A, (2n) 1 se împarte la ni. Pentru că nu
contează dacă s-a inversat B cu B <2n) 1 , se împarte din nou la
n 1. Se obţine:

(2n) =C"
n:n.
' ' 211
8.
,,. .
·ifil~:' ··::~~~
.... ,::~.
1 1• -::-
Varianta Pascal V~r,lanta C++ ' -, c ;
' ' ',

functio n int Maxim( int n)


Maxim( n:intege r):integ er; { int max;
var max:int eger; i f (n•al) return V[l];
begin el se
if n=l then Maxim:= V(1] { max•Ma xim(n-1) ;
el se begin if (max<V[ n]) return V [n] ;
max:•M axim(n-1 ); else return max;
if max<V(n ] then }
Maxim:• V[n] }
el se Maxim:a max;
end;
end;
- jal de informatică pentru clasa a Xl-a 167

Se calculează cifra maximă a numărului şi se adună 1.

• De exemp\u, 'unc\\a oe mai ios se ape\eaz.ă -exi:m\n, 1.) ·.

·· ,.tft';· Varianta Pascal \ ·:::.?:~-)~ •


function int Prim(int n,int i)
Prim(n,i:i nteger):bo olean; { if (i> (int) (sqrt(n)+~))
bag-in return 1;
if i>trunc(sq rt(n))+l el se
then Prim:=true if (n%i==O) return O;
else else return Prim(n,i+ l);
if n mod i=O }

1 then Prim:=fals e
else Prim:•Prim (n,i+l);
end;
1

11.

function int Suma(int n)


Suma(n:in teger):inte ger; { if (n••O) return O;
begin else return Suma(n-1)+
if n•O (V[n]%2••0 )*V[n);
then sumas .. o }
else Sumas•SUm a(n-1)+
ord(V[n) mod 2=0)*V[n];
end;

12.

:.io '. 'ili'tl,,;~::i!


··~ · ., <::: ' ' ~f;~'\~~1' '... 1ant.a
'Var '' .ijl,'·e ::t+,
't, .. ·'•' ·1" :',,, ., r~····
1jr··
' . · ar anta.··.·,'
t:i''I'IW 't'V ·1 ~··' lo•' '
• 'il!l!!!l'
·1iii!l•' Pascal !1
·'lF' ' ,,, .' · ~,;•' 11 " •·:t.rll
function int A.partine( int n, int X)
Apartine(n ,x1integer )1boolean; { if (n==O) return O;
begin el se
if n=O i f (V[nl==x) ret urn 1;
then el se
A.partiner •false return A.partine( n-l,x);
el se )
i f V[n)=x
then A.partine:= true
el se
Apartiner• Apartine(n -l,x);
end;
168 Capitolul 6. Introducere în recursivitate

13. Este clasicul algoritm de căutare binară.

function int P:tnt ( int i,. int j, float x)


Prnt(i,j:integor;x:real):integer; { i nt Mijloc;
var Mijloc:integer; if (j-i>l)
begin { Mijloc=(i+j)/2;
i f j-i>1 if (x==Mijloc)
then return Mijloc;
begin • el se
Mijloc:=(i+j) div 2; if (x<Mijloc:)
if x=Mijloc return
then Pint:=Mijloc Prnt(i,Mijloc:,x);
else el se
if x<~l.lij loc then PZnt(Mijloc:,j,x);
Pint:~Pint(i,Mijloc,x) }
else Pint:=Pint(Mijloc,j,x) else return i;
end }
else Pint:•i
end;

14. Iniţial, se apelează cu Palindrom ( 1, n):

function
Palindrom(i,j:integer):boolean;
begin (i>=:ll roturn 1;
if i>=j eloo
then Palindrom:=true if (V[i] I=V[j]) return O;
el se else return
if V[i] <>V[j] Palindrom(i+1,j-1);
then Palindrom:=false }
else Palindrom:=
Palindrom(i+1,j-1);
end;

15.

func:tion Nrc:if (n:longint): int Nrcif(long n)


integer; { if (n<10) return 1;
begin else return Nrc:if(n/10)+1;
if n<10 }
then Nrc:if:=l
el se Nrcif: =NrCif (n div 10) +1;
end;
::!e i nformatică ntru clasa a Xl-a 169

~edure Oglinda (n:integ er); void Oglinda (int n)


»vin { i f (n)
_f n<>O then { cout<<n% 10;
begin Oglinda (n/10);
write(n mod 10); }
Og1inda (n div 10); }
end;
rod;

- Variabila ninv, transmisă prin referinţă, trebuie să reţină, iniţial, o.

~rocedure Oglinda (n:integ er; void Oglinda (int n, int& ninv)


var ninvzin teger); { if (n)
begin ( ninv=nin v*10+n% 10;
if n<>O then Oglinda (n/10,n inv);
begin }
ninvz~ninv*10+nmod 10; }
Oglinda (n div 10, ninv);
end;
end;

18. Funcţia se apelează cu Distin cte (l,n):

1 ~· 1·
Varianta Pascal ,
'.
, '''· '· ·· .. ,. · Varlanta ·C++ ··•il
funotio n int Distinc te(int i,int n)
Distinc te(i,nzi nteger) zboolea n; { int gasit,j ;
var gasit:bo olean; if (i•=n) return 1;
j:integ er; el se
begin { gasit=O ;
if i=n for (j•i+1;j <=nJj++ )
then Distinc tez•true if (V[il ==V[j)) gasit=l ;
el se if (gasit) return O;
begin e lse return
gasitz• false; Distinc te(i+1, n);
for jz•i+1 ton do )
i f V[i] =V[j] }
then gasit:= true;
if gasit
then Distinc te:=fals e
else Distinc te:=
Distinc te(i+1,n );
end
end;
170 ' Capitolul 6. Introducere în recursivita te:

19. Apelaţi cu Descomp un ( 1, n) :

procedur a Descomp un(i,n:in teger); void Descomp un(int i,int n)


begin { if (i<=n/2)
if i<= n div 2 then { cout<<i<< " "<<n-i<< endll
begin Descomp un(i+l,n) t
writeln (i, ' ', n-i); }
Descomp un(i+l,n) t }
end
end1

20. Apelaţi cu Refac (n, n):

function int Refac(in t i,int n)


Refac(i, n:intege r):intege r; { if (iael) return V[l];
begin el se
if i=l then Refac :=V[l] return 2*Refac( i-1,n)+V [i]1
el se }
Refac:=2 *Refac(i -l,n)+V[i ];
endt

21 . Apelaţi cu Valoare (n,n, a) :

. ,, ' !1 d ' 1'1'· 'j•! + .,


i!V.~rlanta C+ofl ;.·•' ·.
function int Valoare ( int i, int n, int a)
Valoare( i,n,azint eger) zintegert { if (i••l) r e turn V[i];
begin else return a*Valoar e(i-1,
if i•l then Valoare:= V[l] n,a)+V[i ]J
else Valoare: =a*Valo are(i-1, }
n,a)+V[i ]J
endt

22. Este Ineficient să calculăm recursiv.

23. Să presupunem că avem de calculat s ( 8, s) . Mai întâi calculăm:

S(l,l)• l, S(1,2)•0 , S(1,3)•0 , ••• , S(1,8)•0.Urmează:

8(2 , 1)•1, S(2,2)• l,S(2,3) •0, ••• , 8(2,8)•0


8(3,1)•1 , 8(3,2)•8 (211)+2 8(2,2)•3 , 8(3,3)•1 , 8(3,4)•0 , •••
8(4,1)•1 , 8(4,2)•8 (3,1)+2 8(3,2)•7 , •••

Cu excepţia primei linii 8 <1,x), pentru calculul elementelor de pe linia i, s (i,k) ,


se:folosesc rezultatele aflâte pe linia .i-1, adică 8 (i-1 , x).
~tan ual de informati(ă pentru clasa a Xl-a 171

.!S. În cazul în care k<n, avem:


P(n,k)=P (n-k+k,k )=P(n-k ,l)+P(n- k,2)+ ••• +P(n-k,k ).

function int P(int n,int k)


P(n,k:in teger):in teger; { int i,s=O;
var i,s:integ er; if (k>n) return O;
begin el se
if k>n then Pz•O if (ks•l 11 k••n) return 1;
el se el se
if (k•1) or (k•n) { for Ci•1;i<•n -k;i++)
then Pt•l s+=P(n-k ,i);
el se return s;
begin }
sz•O; }
for iz•l to n-k do
s:•s+P(n -k,i);
P:=s;
end
end;

27. a); 28. d); 29. b); 30. a); 31. a), b); 32. a); 33. d);

34. c); 35. b); 36. a); 37.d);

38. a) ; 39. b) ;
40. a) Autoapelurile se efectueazâ după schema de mai jos:

Figura 6.2. Exemplu

Acum, vedeţi "pe viu" motivul pentru care, în cazul unor astfel de formule de
recurenţă, în care, în expresie, intervin mal mulţi operanzi ce se calculeazâ
.
recursiv, este de preferat metoda iterativă.
Capitolul 7
Metoda DIVIDE ET IMPERA

7.1 . Prezentare genera lă

DIVIDE ET IMPERA este o tehnică specială şi se bazează pe un principiu


extrem de simplu: descompunem problema în două sau mai multe subprobleme
(mai uşoare}, care se rezolvă, Iar soluţia pentru problema iniţială se obţine
combinând sol uţiile problemelor în care a fost descompusă. Se presupune că
fiecare dintre problemele în care a fost descompusă problema iniţială, se poate
descompune în alte subprobleme, la fel cum a fost descompusă problema iniţială .
Procedeul se reia până când (în urma descompuneril or repetate) se ajunge la
probleme care admit rezolvare imediată.

Evident, nu toate problemele pot fi rezolvate prin utilizarea acestei tehnici.


Fără teama de a greşi, putem afirma că numărul lor este relativ mic, tocmai datorită
cerinţei ca problema să admită o descompunere repetată.

DIVIDE ET IMPERA este o tehnică ce admite o implementare recursivă. Am


învăţat principiul general prin care se elaborează algoritmii recursiv!: ce se întâmpl ă
la un nivel, se întâmplă fa orice nivel (având grijă să asigurăm condiţiile de
terminare). Tot aşa, se elaborează un algoritm prin DIVIDE ET IMPERA. La un anumit
nivel, avem două posibilităţi:

1) am ajuns la o problemă care admite o rezolvare imediată, caz în care se


rezolvă şi se revine din apel (condiţia de terminare);

2) nu am ajuns în situaţia de la punctul 1, caz în care descompunem problema în


două sau mai multe subprobleme, pentru fiecare din ele reapelăm funcţia,
combinăm rezultatele şi revenim din apel.

7.2. Aplicaţii

7.2.1. Valoarea maximă dintr-un vector

O Problema 7.1. Se citeşte un vector cu n componente, numere naturale. Se


cere să se tipărească valoarea maximă.

Problema de mai sus este binecunoscută. Cum o rezolvăm utilizând tehnica


DIVIDE ET IMPERA?
~ - u al de informatică pentru clasa a Xl-a 173

:;' Rezolvare. Trebuie tipărită valoarea maximă dintre numerele reţinute în vector de
~:.. la j (iniţial i=l şi j =n).

Pentru aceasta, procedăm astfel:

• dacă i=j, valoarea maxim ă va fi v[iJ;

• contrar, vom împărţi vectorul în doi vectori (primul vector va conţine


componentele de la i la ( i+j) div 2, al doilea va conţine componentele
de la (i+j) div 2 + 1 la j, rezolvăm subproblemele (aflăm maximul
pentru fiecare din ele) iar soluţia problemei va fi dată de valoarea maximă
dintre rezultatele celor două subprobleme.

=-ogramul este următorul:

var v:array[1 .• 10) of #include <iostream.h>


integer; int v[lO] ,n;
n,i:integer;
int max(int i,int j)
funotion max(i,j:integer) { int a,b;
:integer; if (i==j ) return v[i);
var a,b:integer; el se
begin { ammax(i, (i+j)/2);
i f i•j b~((i+j)/2+1,j);
then max: cov(i) if (a>b) return a;
el se else return b;
begin }
a:•max(i,(i+j) div 2); }
b:•max((i+j) div 2+1,j);
if a>b main()
then max:=a { oout<<"n="; oin>>n;
else max:=b; for (int i=l;i<•n;i++)
end; { cout<<"v["<<i<<")a";
end; oin»v[i] 1
}
begin
cout<<"max="<<max(l,n);
wri te ( 'n= • ) ; }
readln(n) 1
for i:•l to n do
begin
wri te ( 'v [ ' , i , ' ) =' ) 1
readln(v[iJ)
end;
writeln( 'max•' ,max(l,n))
end.

1 Algoritmul prezentat este exclusiv didactic, în practică este preferat


•f:~ algoritmul clasic . .
174 Capitolul 7. Metoda DIVIDE ET IMPER.A

7.2.2. Sortarea prin interclasare

O Problem a 7.2. Se consideră vectorul a cu n componente numere întregi (sa_


reale). Să se sorteze crescător, utilizând sortarea prin interclasare.

Interclasarea a doi vectori a fost studiată. Dacă dispunem de două şiruri


de
valori, primul cu m elemente, al doilea cu n elemente, ambele sortate,
atunci se
poate obţine un vector care conţi ne toate valorile sortate. Algoritmul de
interclasare
este performant, pentru că efectuează cel mult m+n-1 comparări.

În cele ce urmează, vom utiliza algoritmul de interclasare în vederea sortăr


unui vector prin interclasare.

li:1 Rezolvare. Algoritmul de sortare prin interclasare se bazează pe următoarea


idee: pentru a sorta un vector cun elemente îl împărţim în doi vectori
care, oda~
sortaţi , se interclasează.

Conform strategiei generale DIVIDE ET IMPERA, problema este descompusă


·~
alte două subprobleme de acelaşi tip şi, după rezolvarea lor, rezultatele
se combi~
(în particular se interclasează). Descompunerea unui vector în alţi doi
vectori car:
urmează a fi sortaţi are loc până când avem de sortat
vectori de una sa_
două componente.

În aplicaţie, funcţia sort sortează un vector cu maximum două element


e
intero interclasează rezultatele; divimp implementează strategia
generală '
metodei studiate.

type vector~array [1 • • 10) of #includ e <iostre am.h>


integer 1
int a[lO] , n;
var aiVOCt Orl
n,izin teger1 void sort(in t p,int q,
int a[l0])
proced ura sort(p ,q : intege rJ {
var a:vecto r) l int m1
i f (a[p]>a [q])
var msinte ger; { m=a[Pl l
begin a[pJ=a [q];
i f a [p) >a [q) a[q)=m ,
then }
begin }
m: • a[p) J
a[p]: ..a[q]l void interc (int p,int q,
a[q]:-m int m, int a[10))
end { int b[lO] ,i,j,kl
end1 i=pJ j=m+lJ k ml1

---
---
-----~
1.4anual de informatică pentru clasa a Xl-a 175

procedura interc while (i<=m && j<=q)


(p,q,m:integ er; var a:vector); if (a(i] <=a[j])
{ b[k]ca[i];
var b:vector;
i=i+l;
i,j,k:intege r;
k=k+l;
begin }
i:ap; j:=m+l; k : =l; el se
while (i<=m) and (j<=q) do { b(k]•a[j];
if a[il<=a[j] then j=j+l ;
begin k=k+l;
b[k):aa[i); }
i:=i+l; k:•k+l if (i<=m.)
end for (j=i;j<cm;j++ )
el se { b[k]=a[j] ;
begin k=k+l;
b[k]:=a[j]; }
j:=j+l; k:=k+l el se
end;
for (i=j;j<• q;j++)
if i<=m. then { b[k]=a[i);
for j:=i tom do
begin k=k+l;
}
b[k] :•a[j}; k:ak+l
end
k •l ;
el se for (i=p;i<•q;i++ )
for i: • j to q do { a[i]=b[k];
begin k•k+l;
b[k) :•a(i]; k:•k+l
end; }
k:•l;
for i:=p to q do void divimp (int p,int q,
begin int a [10])
a(i]:=b(k); k: • k+l { int m;
end if ((q-p)<•l ) sort(p,q,a);
end; el se
{ m•(p+q)/2 ;
procedura divimp divimp(p,m,a ) ;
(p,q:integer; var a:vector); divimp(m+l ,q,a);
var m.:integer; interc(p,q,m ,a);
begin }
if (q-p)<•l then sort(p,q,a) }
el se
begin
main()
m:=(p+q) div 2;
{ int i;
divimp(p,m,a );
cout<<"n• "; cin>>n;
divimp(m+l,q ,a);
interc(p,q,m ,a) for (i=l;i<•n;i++ )
end { cout<<"a["<< i<<"l•";
end; cin»a[i] 1
}
begin divimp(l,n,a );
write('n•'); readln(n); for (i=l ; i <•n;i++)
for i:=l to n do cout<<a[i]<< " " ;
begin }
write('a[•,i , ']=') ;
readln(a[i))
end;
divimp(l,n,a );
for i:=l ton do writeln(a(i] )
end.

-
- . ~~~~~~
176 Capitolul 7. Metoda D IVIDE fT IMPERJ.

În continuare, calculăm numărul aproximativ de comparări efectuat de


algoritm . Fie acesta T (n). Mai simplu, presupunem n=2k.

O problemă se descompune în alte două probleme, fiecare cu n /2


componente, după care urmează interclasarea lor, care necesită n/2+n/2=n
comparaţii:

Avem:
T(n) = T(2k) = 2(T(2k-l) + 2k-l ) = 2T(2k-l) + 2k =2T(2k- 2 + 2k-l ) + 2k =
2
2T(2k- )+2k +2k =...2k +2k + ... +2k =n+n+ ... +n =n · k =n ·log 2n
'------v--'
dekori de kori

7.2.3. Sortarea rapidă

O Problema 7.3. Fie vectorul a cun componente numere întregi (sau reale). Se
cere ca vectorul să fie sortat crescător.

0 Rezolvare. Este necesară o funcţie POZ care tratează o porţiune din vector
cuprinsă între indicii daţi de l i (limita inferioară) şi ls (limita superioară) . Rolt.
acestei funcţii este de a poziţiona prima componentă a [li] pe o poziţie k cuprins.1
între l i şi ls, astfel încât toate componentele vectorului cuprinse între l i şi k-l
să fie mai mici sau egale decât a [kl şi toate componentele vectorului cuprinse
între k+1 şi ls să fie mai mari sau egale d~cât a [k).

În această funcţie există două moduri de lucru:

a) i rămâne constant, j scade cu 1;


b) i creşte cu 1, j rămâne constant.

Funcţia este concepută astfel:


• iniţial, iva lua valoarea li, iar j va lua valoarea ls (elementul care iniţia
se află pe poziţia l i se va găsi mereu pe o poziţie dată de i sau de j );
• se trece în modul de lucru a);
• atât timp cât i<j, se execută:
dacă a [il este strict mai mare decât a [j l, atunci se inversează
cele două
rumere şi se schimbă modul de lucru;
i şi j se modifică corespunzător modului de lucru în care se află
programul;
k ia valoarea comună a lui i şi j.
E!.nual de informatică pentru clasa a Xl-a 177

Ex: Pentru a= ( 6, 9,3,1,2), li=l, ls=5; modul de lucru a):

• i=l, j=S;
• a[1 J >a [S], deci se inversează elementele aflate pe poziţiile 1 şi 5,
deci a= ( 2, 9, 3, 1, 6) şi programul trece la modul de lucru b) ;
• i =2 , j=5;
• a [21 >a [51, deci a= (2,6 ,3, 1,9) şi se revine la modul de lucru a);
• i=2, j=4;
• a [ 2 1 >a ( 4], deci a= ( 2, 1, 3, 6, 9); se trece la modul de lucru b );
• ic:3, j=4;
• funcţia se încheie, elementul aflat iniţial pe poziţia 1 se găseşte
acum pe poziţia 4, toate elementele din stânga lui fiind mai mici
decât el, totodată toate elementele din dreapta lui fiind mai mari
decât el (k=4).

modurilor de lucru se explică prin faptul că elementul care trebuie


Alternanţa
se compară cu un element aflat în dreapta sau în stânga lui, ceea ce
poziţionat
mpune o modificare corespunzătoare a indicilor i şi j.

f După aplicarea funcţiei POZ, este evident că elementul care se află iniţial în
• poziţia l i va ajunge pe o poziţie k şi va rămâne pe acea poziţie în cadru l
vectorului deja sortat, fapt care reprezintă esenţa algoritmului.

Funcţia QUICK are parametrii l i şi ls (limita inferioară şi limita superioară).


În cadrul ei se utilizează metoda DIVIDE ET IMPERA, după cum urmează:

• se apelează Poz ;
• se apelează QUICK pentru l i şi k-1 ;
• se ape l ează QUICK pentru k+l şi ls.

l'
Varianta Pascal
type vector=array [1 .. 100] of #include <iostream.h>
integer; int a[100],n,k;
void poz (int li,int ls,int&
var i,n,k:into ger;
k,int a[100])
a :veotor;
{ int i=li,:l=ls,c,i1=0,~1=-1;
procedura poz (li,ls:integer; while (i<:l)
var k:integ er; { if (a[i]>a(j])
va.r a:vector); { c=a (j]; a[j]=a[i];
a[il=c; c=il;
var i,j,c,i1,j1:integ er; i1=-:11; :11=-c;
begin }
i=i+il;
i1:=0; :1=:1+:11;
jl:=-1; }
i:=li; k=i;
j:=ls; }
178 Capitolul 7. Metoda DIVIDE ET IMPERA

while i<j do
void quick (int li,int ls)
begin { if (li<ls)
i f a[i] >a[j]
{ poz(li,ls,k,a);
then
quick(li,k-1);
begin
quick(k+l,ls);
c:=a[j]; }
a [ j] : •a [ i] ; }
a [il :=c;
CJ•il; main()
il:=-jl; { int i;
jlJ•-c cout<<"n="; ein>>n;
end~
for (i•l;i<•n;i++)
i:=i+il;
{ eout<<"a["<<i<<"l•";
j:•j+jl
cin»a[i];
end; }
k:=i
quick(l,n);
end;
for (i•l;i<•n;i++)
procedura quick(li,ls:integer); cout<<a[i)<<endl;
begin }
if li<ls
then
begin
poz(li,ls,k,a);
quick(li,k-1);
quick(k+l,ls)
end
end;
bagin
write( 'n•');
readln(n) 1
for i:•l to n do
bagin
write(•a[•,i,•]•');
readln(a[i])
end;
quick(l,n); ..,.
for i:•l to n do
writeln(a[i])
end.

~~$1 Reţineţi! Sortarea rapidă efectuează în medie n · log 2n operaţii.


~ Demonstraţia necesită cunoştinţe de matematică pe care nu le aveţi la nivelul
acestui an de studiu ...
tJtanual de informatică pentru clasa a Xl-a 179

- .2.4. Turnurile din Hanoi

:J Problema 7.4. Se dau 3 tije simbolizate prin a, b, c.


::e tija a se găsesc discuri de diametre diferite, aşezate
·., ordine descrescătoare a diametrelor privite de jos în
sus. Se cere să se mute discurile de pe tija a pe tija b , utilizând ca tijă intermediară
· 1a c, respectând următoarele reguli:

• la fiecare pas se mută un singur disc;

• nu este perm is să se aşeze un disc cu diametru! mal mare peste un disc


cu diametru! mai mic.

~ Rezolvare.
Dacă n=1, se face mutarea ab, adică se mută discul de pe tija a pe tija b.

Dacă n=2 , se fac mutările ac, ab, cb.

În cazul în care n>2, problema se complică. Notăm cu H (n, a, b, o) şirul


mutărilor celor n discuri de pe tija a pe tija b, utilizând ca tijă intermedi ară , tija c .

Conform strategiei DIVIDE ET IMPERA, încercăm să descompunem problema


în alte două subprobleme de acelaşi tip, urmând apoi combinarea s~luţillor. În
acest sens, observăm că mutarea celor n discuri de pe tija a pe tija b, utili~ând ca
tijă intermed i ară tija c, este echivalentă cu:

- mutarea a n-1 discuri de pe tlja a pe tija c, utilizând ca tijă Intermediară tija b;


- mutarea discului rămas pe tija b;
- mutarea a n-1 discuri de pe tija c pe tija b , utilizând ca tijă intermediară tija a .

Parcurgerea celor trei etape permite definirea recursivă a şirulu i


H(n,a,b,c) astfel:

ab, n= 1
H(n,a,b,c) ={ H(n - 1,a,c,b),ab,H(n - 1, c, b, a), n >1

/!.x: Priviţi următoarele exemple:


1} pentru n=2, avem: B(2, a, b, c) =H(1,a, c, b), ab,H( 1, c, b, a) =ac , ab, cb;

2) pentru n=3, avem:


B(3,a,b,c)aH(2,a,c,b), ab,H{2,c, b,a)=H(1 ,a,b, c),ac,B(1,b,c , a),
ab,H(1,c,a,b),cb,H{1,a ,b,c)=ab,ac,bc,ao,ca,c b,ab.
180 Capitolul 7. Metoda DIVIDE ET IMPERA

var a,b,c:char; #include <iostream . h>


n:integor; char a,b,c;
procedura han (n:integer; int n;
a,b,c:char);
begin void han (int n , char a,
if n=l then char b,char c)
writoln(a,b) { if (n==l) cout<<a<<b<<endl;
el se el se
begin { han(n-l,a,c,b);
han{n-l,a,c,b); cout<<a<<b<<endl;
writeln(a,b); han(n-l,c,b,a);
}
han(n-l,c,b,a)
}
end
end; main()
begin { cout<<"N="; cin>>n;
write('N='); readln(n); a=•a•; b='b't c=•c•;
a:='a'; b : c'b'; c:='c'; han(n,a, b,c);
}
han(n,a,b,c)
end.

7.2.5. Problema tăieturilor

D Problema 7.5. Se dă o bucată dreptunghiulara de tablă cu lungimea 1 şi


înălţimea:q, având pe suprafaţa ei n găuri de coordonate numere întregi. Se cere
să se decupeze din ea o bucată de arie maximă care nu prezintă găuri. Sunt
permise numai tă ieturi verticale şi orizontale.

0 Rezolvare. Coordonatele găurilor sunt reţinute în doi vectori XV şi YV.


Dreptunghiul iniţial, precum şi dreptunghiurile care apar în procesul tăierii sunt
memorate în program prin coordonatele colţului din stânga-sus (X, Y), prin
lungime şi înălţime (L,H).

Pentru un dreptunghi (iniţial pornim cu toată bucata de tablă), _verificăm dacă


avem sau nu o gaură în el (se caută practic prima din cele n găuri). In situaţia când
acesta prezintă o gaură, problema se descompune în alte patru probleme de
acelaş i tip. Dacă bucata nu prezintă găuri, se compară aria ei cu aria unei alte
bucăţi fără gaură, găsită în fazele precedente.

Menţionă m că dreptunghiul de arie maximă fără găuri este reţinut prin


aceiaşi parametri ca şi dreptunghiul cu găuri, în zonele XF, YF, LF, BF.

În concluzie, problema iniţială se descompune în alte patru probleme de


acelaşi tip, mai uşoare, întrucât fiecare nou dreptunghi are cel mult n-1 găuri, dacă
dreptunghiul iniţial avea n găuri. La această problemă compararea soluţiilor constă
în a reţine dreptunghiul cu aria maximă dintre cele fără găuri.
'•lan ual de informatică pentru clasa a Xl-a 181

Fie dreptunghiul cu o gaură:

h 1 •xv(i),yv(i)
x,y
Pentru a se afla în interiorul dreptunghiului, gaura trebuie să îndeplinească
simultan cond iţi ile:

1) xv(i) >x;
2) xv(i) <x+l;
3) yv(i) >y;
4) yv(i)<y+h.

Dacă facem o tăietură verticală prin această gaură, obţinem două dreptunghiuri:
1) x, y, xv(i) -x, h;
2) xv(i), y, l+x-xv(i), h.

În urma unei tăieturi pe orizontală se obţin cele două dreptunghiuri:

1) X, y , l , yv ( i) -y;
2) x, yv(i), 1, h+y-yv(i ).

Programul este următorul:

, , :j,.. .'' Vari~nta Pascal.' ',


,,· ..
type vect=array [1 .• 9] of #include <iostream.h>
integer; int l , h,i,n,xf,yf,lf,
hf ,xv[lO] ,yv [lO] ;
var l,h,i,n,xf,yf,
lf,hf:integer; void dimp(int x,int y,int 1,
xv, yv:vect; int h , int& xf, int& yf,
int&: lf,int&: hf,
procedura dimp int xv[lO],int yv[ lO])
(x, y,l ,h:integer; { int gasit=O,i=l;
var xf,yf,lf,hf:integer; while (i<=n &:& !gasit)
var xv,yv:vect); if (xv [i)>x &&: xv[i]<l &:&:
var gasit:boolean; yv[i)>y &&: yv[i]<y+h)
i:integer; gasit=l;
e lse i++;
begin
if (gasit)
i:=1;
{ dimp(x,y,xv [i] -x,h,xf ,
gasit:=false; yf,lf,hf,xv,yv);
while (i<=n) and (not gasit) dimp(xv[i],y,l+x-xv[i],
do h,xf,yf,lf,hf,xv,yv);
if (xv[i]>x) and (xv[i]<l) dimp(x,y,l,yv[i]-y,xf,
and (yv[i] >y) and yf,lf,hf,xv,yv);
(yv[i]<y+h) dimp(x,yv[i],l,h+y-yv[i],
then gasit:=true ~f,yf, lf ,hf ,xv ,yv);
else i:=i+l; }
182 Capitolul 7. Metoda DIVIDE ET IMPERA

if. gasit el se
then if (l*h>lf*hf)
begin { xf=x;
dimp(x,y,xv[i]-x , yf..y;
h,xf,yf,lf,hf,xv, yv)l lf•l;
dimp(XY[i],y,l+x -xv[i], hf•h;
h,xf.,yf,lf,hf.,xv ,yv); }
dimp(x,y,l,yv[i] -y, }
xf.,yf,lf,hf,xv,y v)l
dimp(x,yv[i],l,h +y-yv[i], main ()
xf.,yf,lf,hf,xv,yv ) { cout<<"n•"1 cin>>n1
end for (int i•l;i<•n1i++)
el se { cout<<"x["<<i<<" ]c•;
i f (l*h)>(lf*ht) cin»xv[i];
then cout<<"Y["<<i<<" l•"1
begin cin»yv [il 1
Xfi•XI )
yfi•YI oout<<"1•"1 cin>>l1
lf.a•l1 cout<<"h•"; cin>>h1
hfl•h dimp(O,O,l,h,xf ,yf,lf,
end hf,xv,yv);
cout<<"x•"<<xf<< " y•"<<yf
<<" l•"<<lf<<" h • "<<hf1
begin )
write ( 'n•') ;
readln(n);
for ia•l to n do
begin
write ( 'x [ •, i, • l • • ) 1
readln(xv[i]);
~1ri te ( •y [ ' , i, • l • • ) 1
readln(yv[i])
end;
write( '1•');
readln(l)l
write( 'h• •) 1
readln(h) 1
lf :•O;
h.f :•01
dimp(O,O,l,h,xf, yf,
lf,hf,xv,yv) 1
writeln( 'x•' ,xf, • Y•' ,yf., •
1•' ,lf,. h•',hf)
end.
Manual de informatică pentru clasa a Xl-a 183

7.3. Fractali

Fractalii au fost in troduşi în anul 1975 prin lucrarea revoluţionară a


matematicianului francez Benoit Mandelbrot, "O teorie a seriilor fractale", ce
reuneşte totodată diversele teorii dinaintea sa. El este cel care a inventat cuvântul
"fractal", de provenienţă latin ă ("frângere"- a sparge în fragmente neregulate).

Noţiunea de fractal a apărut ca urmare a studiului vieţii reale, în care


i nformaţia genetică conţinută in nucleul unei celule se repetă la diferite scări.
Calculatorul permite ca o anum ită figură (de exemplu, un segment) să se
transforme într-o alta, formată din mai multe figuri iniţiale (de exemplu , o linie
frântă) şi fiecare figură obţinută să se transforme în mod asemănător (aceste
transformări necesită foarte multe calcule).

Aceste forme geometrice au fost considerate în trecut haotice sau "aberaţi i


geometrice", iar multe dintre ele erau atât de complexe încât necesitau
calculatoare performante pentru a le vizualiza. Pe parcurs, domenii şti i nţifi ce ca
fizica, chimia, biologia sau meteorologia descopereau elemente asemănătoare cu
fractalii în viaţa real ă. Aceştia au proprietăţi matematice extrem de interesante,
care de multe ori contrazic aparenţa, dar acestea depăşesc cu mult cunoştinţe l e de
matematică din liceu.

Înainte de a prezenta câteva exemple, trebuie cunoscute mai întâi noţiunile


de bază pentru a lucra în mod grafic. Acestea vor fi prezentate în continuare.

7.3.1. Elemente de grafică

7.3. 1.1 . General i tăţ i (varianta Pascal)

Limbajul Pascal conţine o serie de proceduri şi funcţii care permit realizarea


unor aplicaţii grafice. Acestea sunt reunite în unitatea GRAPH, ce se găseşte în
subcatalogul UNITS.

· Pentru ca o imagine să poată apărea pe ecran, calcu latorul utilizează placa


video, cme diferă în funcţie de memoria video şi alţi pararrietri. Pentru a accesa o
placă video, trebuie să folosim r.~numite rutine speciale, speci~ice lor,, numite
Drlver-e. Limbajul Pascal deţi ne o colecţie de astfel de componente software şi în
funcţie de placa ce a fost detectată în sistem, se încarcă un driver sau altu l. Aceste '
fişiere au extensia ''bgi". Deoarece performanţele componentelor hardware au'
depăşit cu mult capacităţile CGA sau EGA, ne vom referi în continuare doar rla
driver-ul VGA (Video Graphlcs Array), dezvoltat de firma IBM. Driver-ul VGA poate
lucra Tn mai multe moduri, însă vom prefera modul standard de îna l tă rezo l uţie
''vGAHI" (constantă de tip întreg), ce poate afişa 640 x 480 puncte în 16 culori.
184 Capitolul 7. Metoda DIVIDE ET IMPERA

Selectarea driver-ului şi a modului de lucru se face prin utilizarea procedurii


ini.tgraph. Aceasta are trei parametri: gdriver (de tip integer) care conţine
codul asociat driver-ului, gmode (de tip integer) care reţine modul de lucru şi o
variabilă de tip string, care arată calea către unitatea GRAPH. Forma generală a
acestei proceduri este

initgraph(gdriver,gmode,'cale');.

Primii doi parametri sunt transm işi prin referinţă.

Iniţializarea sistemului grafic se poate face tn două feluri:

1) prin a solicita să se identifice automat placa grafică şi corespunzător ei să


se încarce un anumit driver şi să se selecteze modul de lucru - cel mai bun din
punct de vedere al performanţelor:
procedure initg;
begin
gdriver := detect;
initgraph(gdriver,gmode,•c:\tp\bgi');
if graphresult<>O then
begin
writeln("Tentativa esuata!");
halt
end
end;

Constanta detect are valoarea o şi se specifică procedurii identificarea


automată a driver-ului şi a modului de lucru.

Vom reţine această procedură pentru că o vom utiliza în exemplele ulterioare.

2) prin indicarea cu ajutorul primilor doi parametri a unui driver şi a unui mod
de lucru solicitate de programator (în acest caz, nu se poate executa programul pe
un calculator ce nu este dotat cu placa grafică specificată):
gdriver := VGA;
gmode :"' VGAHI;
initgraph(gdriver,gmode,'cs\tp\bgi');
if graphresult<>O then
begin
writeln("Tentativa esuata!");
halt
end;

Tentativa de iniţializare grafică poate eşua din diverse motive, cum ar fi: lipsa
unităţii
GRAPH, calea indicată greşit, etc. Testarea se realizează cu funcţia tntreag ă
graphresult care returnează o în caz afirmativ şi o valoare diferită de o, în
caz contrar.

f: Odată intraţi în modul grafic, nu se mai poate scrie pe monitor, ca până


...Il" acum (cu write sau writeln). Ieşirea din modul grafic se face prin
utilizarea procedurii closegraph.
Manual de informatică pentru clasa a Xl-a 185

7.3.1.2 . Generalităţi (varianta C++)

Limbajul C++ (în varianta Borland), conţine o serie de funcţii care permit
realizarea unor apli caţii grafice. Acestea sunt reunite in fişierul GRAPHJ:CS. H, ce se
găseşte in folderul :INCLUDE.

Pentru ca o imagine să poată apărea pe ecran, calculatorul utilizează placa


video, care diferă in funcţie de memoria video şi alţi parametri. Pentru a accesa o
placă video, trebuie să folosim anumite rutine speciale, specifice lor, numite
Driver-e. Limbajul C++ deţine o colecţie de astfel de componente software şi în
funcţie de placa ce a fost detectată in sistem, se încarcă un driver sau altul. Aceste
fişiere au extensia "bgi". Deoarece performanţele componentelor hardware au
depăşit cu mult capacităţile CGA sau EGA, ne vom referi in continuare doar la
driver-ul VGA (VIdeo Graphlcs Array), dezvoltat de firma IBM. Driver-ul VGA
poate lucra în mai multe moduri, insă vom prefera modul standard de înaltă
rezol uţie "VGAH:r" (constantă de tip Tntreg), ce poate afişa 640 x 480 puncte in
16 culori. Fişierul ce conţine driver-ul utilizat este "EGAVGA.CG:I".

Selectarea driver-ului şi a modului de lucru se face prin utilizarea funcţiei


initgraph. Aceasta are trei parametri: gdriver (de tip integer) care conţine
codul asociat driver-ului, gmode (de tip integer) care reţine modul de lucru şi o
variabilă de tip string, care arată calea către unitatea GRAPH. Forma generală a
acestei funcţii este

initgraph(&gdriver,&gm ode,"oale"); .

Primii doi parametri sunt transmişi prin referinţă.

Iniţializarea sistemului grafic se poate face în două feluri:

1) prin a solicita să se identifice automat placa grafică şi corespunzător ei să


se Tncarce un anumit driver şi să se selecteze modul de lucru - cel mai bun din
punct de vedere al pertor:manţelor:
void init()
( gdriver a DETECT;
initgrapb(&gdriver,&gmode, "E:\\BORLANDC\\BGX");
if (graphresult())
( cout<<"Tentativa nereusita.";
cout<<"Apasa o tasta pentru a inchide ••• ";
getch() 1
exit(l);
}
}

Constanta DETECT are valoarea o şi se specifică funcţiei identificarea


automată a driver-ului şi a modului de lucru.

Vom reţine funcţia init () pentru că o vom utiliza ln exemplele ulterioare.


186 Capitolul 7. Metoda DIVIDE ET IMPERA

2) prin indicarea cu ajutorul primilor doi parametri a unui driver şi a unui mod
de lucru solicitate de programator (în acest caz, nu se poate executa programul pe
un calculator ce nu este dotat cu placa grafică specificată):

gdriver := VGA; gmode := VGAHI;


initgraph( &gdriver, &gmode," E:\\BORL ANDC\\B GI");
if {graphre sult{))
{ cout<<"T entativa n ereusita . ";
cout<<"A pasa o tasta pentru a inchide ••• ";
getch() 1
exit{1);
}

Tentativa de iniţializare grafică poate eşua din diverse motive, cum ar fi: lipsa
un ităţii GRAPHICS , ca lea i ndicată greşit, etc. Testarea se realizează cu funcţia

,
întreagă graphre sul t ( ) care returnează o în caz afirmativ şi o valoare diferită
de o, în caz contrar.

Odată intraţiîn modul grafic, nu se mai poate scrie pe monitor ca până acum
(de exemplu, cu cout). Ieşirea din modul grafic se face prin utilizarea
• procedurii closegra ph ( ) .

Atenţiei Pentru a putea scrie şi rula programe C++ ce utilizează modul grafic al
limbajului, trebuie bifată următoarea opţiune , din meniu:

Options 1 Llnker 1 Libraries 1 Graphlcs library.

7.3. 1. 3. Setarea culorilor şi procesu l de desenare (Pascal şi C++)

Cu siguranţă, placa video utilizată de dvs. are performanţe superioare


modu lui standard VGA, ce se regăseşte in driver-ul limbajului Pascal sau C++.
Pentru a generaliza însă, vom considera modul menţionat anterior, ce poate reda
16 culori, reprezentate pe 4 biţi.

Fiecare culoare de bază are atribuită o constantă de la o la 15, precum


urmează: o - black (negru); 1 - blue (albastru); 2 - green (verde); 3 - cyan
(turcoaz); 4 - red (roşu); 5 - mage nta (violet); 6 - brown (maro); 7 - lightgre y
(gri deschis); 8 - darkgrey (gri închis); 9 - lightblu e (albastru deschis); 10 -
lightgre en (verde deschis); 11 - lightcya n (turcoaz deschis); 12 -
lightred (roşu deschis); 13 - lightmag enta (violet deschis); 14 - yellow
(galben) ş i 15- white (alb).

f Aceste culori sunt cele implicite. Pentru a utiliza mai multe culori (dar nu în
J,jitl acelaşi timp), se poate schimba setul (paleta) de culori. Întrucât în acest
moment nu sunt necesare o multitudine de culori, nu vom prezenta în detaliu
acest aspect.
..,a., ual de Informatică pentru clasa a Xl-a 187

-+ Pentru a seta culoarea de fundal, se utilizează procedura (în Pascal) sau


funcţia (în C++)

setbkcolor(c uloare);.

Exemple: setbkcolor ( 6) 1 sau aetbkcolor ( RED) ; .

-+ Selectarea culorii cu care se desenează se face cu ajutorul procedurii (în


Pascal) sau funcţiei (Tn C++)
setcolor(culo are);.

Exemplu: setcolor ( 15) ; sau setcolor (WHITE) ; .

~ Observaţii

Schimbarea culorii nu afectează ce am desenat anterior, ci doar ce este scris
după apelul acestei rutine.

În cazul limbajului C++, numele simbolic al culorii se scrie obligatoriu cu


majuscule.

Operaţia de desenare

Oricare ar fi modul de lucru ales, un punct se reprezintă printr-un pixel de


coordonate x (linia) şi y (coloana), ambele valori intregi. Punctul din stânga-sus are
coordonatele <o, o>. Pentru a ne muta la poziţia (x, y), vom folosi procedura (în
Pascal) sau funcţia (Tn C++) moveto(x,y). Pentru a trasa o linie de la punctul
curent, determinat anterior, pană la o nouă poziţie, vom utiliza procedura (în
Pascal) sau funcţia (în C++) lineto(xl,yl ). Astfel, vom obţine o linie Tntre
punctele de coordonate (x,y) şi (xl,yl).

Exemplu. Mai jos, este prezentat un program ce desenează o linie pe diagonala


principa lă a ecranului (de la colţul din stanga-sus la colţul din dreapta jos):

Varlanta·P~scal

begin main ()
initg; { init() 1
setcolor(red ); ••tcolor(RED ) 1
moveto(O,O); moveto(O,O);
lineto(getmax x,getmaxy); lineto(getma xx(),getmaxy ());
readln1 getch() 1
end. }

De asemenea, două funcţii foarte utile sunt get:maxx şi getmaxy (în


Pascal) sau getmaxx() şi getmaxy() (în C++). Acestea tntorc valoarea minimă
şi respectiv, maximă a coordonatelor de pe ecran. Astfel, cu ajutorul lor se poate
obţine o independenţă relativă a programelor faţă de modul grafic al sistemului.
188 Capitolul 7. Metoda DIVIDE ET IMPERA

7.3.2. Curba lui Koch pentru un triunghi echilateral

Se consideră un triunghi echilateral. Fieca re latură a sa se transformă aşa


cum se vede in figura următoare (se împarte în trei segmente congruente, se
elimină segmentul din mijloc şi se construieşte deasupra un triunghi echilateral):

_/\_
Figura 7.1. Exemplu de transformare

Fiecare latură a acestui poligon se transformă din nou, după aceeaşi regulă.
Să se vizualizeze figura obţinută după ls paşi {n um ăr citit de la tastatură).

AKceahst(ăHcurbă esteKcunhosc ută in literatut~~ de spedcialit~te ~a fii~d ct urba luăi


1
f,,,,
• oc erge van oc a ost materna 1c1an sue ez ŞI a 1magma aceast
curbă în anul 1904 ).

Programul principal va apela, pentru fiecare segment care constituie o latură


a triunghiului, o procedură numită generator. Aceasta execută transformarea de
ls ori, având ca parametri de intrare coordonatele punctelor care constituie
extremităţile segmentului, numărul de transformări făcute (n) şi numărul de
transformări care trebuie efectuate (ls). Pentru a înţelege funcţionarea procedurii,
trebuie să avem un minimum de cunoştinţe specifice geometriei analitice (ce se
poate face fără matematică?).

Fie AB un segment de dreaptă, unde A este un punct de coordonate (x 1,y1),


iar B are coordonatele (x 2 ,y2) . Două puncte P1 şi P2 împart segmentul într-un
anumit raport, notat cu k

Y1 - k · Y2
1-k
Demonstraţi singuri aceste formule!

Fie segmentul AB cu A(x 1,y1) şi B(x2,y2). Considerăm punctele C şi D care


împart segmentul în trei segmente congruente.

Aflăm coordonata punctului D:

k = DA =-2·
DB '
x = x 1 +2·x 2
1' 3 ' Y,, = Y1 +2·
3 -
)'?

Problema constă în stabilirea coordonatelor vârfului noului triunghi


echilateral. Acestea se obţin dacă se roteşte punctul C în jurul punctului D cu
unghiul re 13. Rotaţia o efectuează procedura rotplan.
Manual de informatică pentru clasa a Xl-a 189

Să prezentăm algoritmul care stă la baza procedurii generator:

• se porneşte de la segmentul AB;


• se determină coordonatele punctului care constituie vârful triunghiului
echilateral (să-I notăm cu V);
• în cazul în care segmentul nu a fost transformat de ls ori, se apelează
generator pentru segmentele AC, CV, VD şi DB;
• contrar, se apelează procedura care trasează linia frântă ACVDB.

in programul principal au fost alese punctele care determină triunghiul


echilateral iniţial - plasat în centrul ecranului - şi pentru fiecare segment ce
constituie o latură a acestuia s-a apelat procedura generator. Odată trasă curba,
se colorează interiorul acesteia.

Programul este următoru l :

uses graph,crt; *include "graphics.h"


var L,gdriver,gmode, *include <iostream.h>
ls:integer; *include <stdlib.h>
xmax,ymax:integer; *include <conio.h>
#include <math.h>
procedura initg1 int gdriver,gmode,ls,LJ
void init()
procedure rotplan(xc,yc,xl,
yl:integer1 var x,y:integer;
( ... }

unghi:real); void rotplan(int xc,int yo,


begin int xl, int yl,int &x,
x :• round(xc+(xl-xc)* int &y, float w1ghi)
cos(unghi)-(yl-yc)* {x = ceil(xc+(xl-xo)*cos(ungh i)-
sin(unghi)); (yl-yc)*sin(unghillJ
y ;a round(yc+(xl-xc)* y = ceil(yc+(xl-xc)*sin(ungh i)+
sin(unghi)+(yl-yc)* (yl-yc)*cos(unghi))J
cos(unghi)) }
end1 void desenez(int xl,int yl,
procedura desenez(xl,yl,x2, int x2,int y2,int x3,int y3)
y2,x3,y3:integer); { moveto(xl,yl);
begin lineto(div((2*xl+x2),3) .quot,
moveto(xl,yl); div((2*yl+y2),3) . quot)J
lineto((2*xl+x2) div 3, lineto(x3,y3)J
(2*yl+y2) div 3)1 lineto(div((xl+2*x2),3). quot,
lineto(x3,y3); div((y1+2*y2),3).quot);
lineto((xl+2*x2) div 3, lineto(x2,y2); }
(y1+2*y2) div 3)1 void generator(int xl,int yl,
lineto(x2 , y2); int x2,int y2,int n, int ls)
end1 { int x,y;
procedure rotplan(div((2*xl+x2),3) . quot,
generator(xl,yl,x2,y2, div((2*yl+y2),3).quot,
n,ls:integer); div((x1+2*x2),3).quot,
var x,y:integer; div((y1+2*y2),3).quot,
x,y,M PJ:/3);
190 Capitolul 7. Metoda D IVIDE ET IMPERA

begin if (n<ls)
rotplan((2*xl+x2) div 3, {generator(x1,y1,div((2*xl+x2),
(2*yl+y2) div 3, (x1+2*x2) div 3).quot,div{(2*yl+y2),
3, (y1+2*y2) div 3,x,y,pi/3); 3).quot,n+l,ls);
if n<ls then generator(div((2*xl+x2),
begin 3).quot,div((2*yl+y2),
generator(xl,yl, (2*xl+x2) 3),quot,x,y,n+l,ls);
div 3,(2*y1+y2) div 3, generator(x,y,div((x1+2*x2),
n+l, ls); 3).quot,div((y1+2*y2),
generator{{2*xl+x2) div 3, 3).quot,n+l,ls);
(2*yl+y2) div 3, generator(div((x1+2*x2),
x,y,n+l,ls); 3).quot,div((y1+2*y2),
generator(x,y, (x1+2*x2) div 3).quot,x2,y2,n+l,ls);
3,(y1+2*y2) div 3,n+l,ls); }
generator({x1+2*x2) div 3, else desenez(xl,yl,x2,y2,x,y);
(yl+2*y2) div 3, }
x2, y2, n+l, ls);
main()
end
{ cout<<"ls= "; cin>>ls;
else desenez{xl,yl,x2,y2,x,y);
init ();
end;
setcolor(6);
begin L = getmaxx()-320;
write('ls= '); readln(ls); generator(160,getmaxy()-150,
initg; 160+L,getmaxy()-150, 1,ls) ;
setcolor(red); generator(160+L,getmaxy()-
L:=getmaxx-320; 150,160+div(L,2).quot,
generator(l60,getmaxy-150, getmaxy() -150-
160+L,getmaxy-1SO,l,ls)/ ceil(L*(sqrt(3)/2)),l,ls);
generator(160+L,getrnaxy-150, generator(160+div(L,2) ,quot,
160+L div 2,getmaxy-150 - getrnaxy() -1SO-
L*round(sqrt (3)/2) , 1,ls) ; ceil(L*{sqrt(3)/2)),160,
generator(160+L div 2,getmaxy- getmaxy()-lSO,l,ls);
150-L*round(sqrt(3)/2),160, setfillstyle(1,4);
getmaxy- lSO,l , ls)/ floodfill(div(getmaxx(),2)
setfillstyle(l,blue); .quot,div(getmaxx(),
floodfill(getmaxx div 2, 2).quot,6);
getmaxy div 2, red); getoh()/
readln closegraph();
end . }

Priviţi mai jos rezultatele obţinute pentru diferite valori ale lui la:

la = 2 ls ::: 3 ls =4
Figura 7.2. Exemple de fractali formaţi cu ajutorul curbei lui Koch (triunghi echilateral)
"'.anual de informatică pentru clasa a Xl-a 191

.... .3.3. Curba lui Koch pentru un pătrat

Se consideră un pătrat. Fiecare latură a sa se transformă după cum se vede


·, figura de mai jos:

Figura 7.3. Exemplu de transformare

Fiecare segment al liniei frânte astfel formate se transformă din nou după
aceeaşi regulă. Se cere să se vizualizeze curba după ls transformări (valoare
citită de la tastatură).

Transformarea şi desenarea unui segment sunt realizate de procedura


desen. Aceasta are ca parametri de intrare coordonatele punctului care determină
segmentul, numărul de transformări efectuate (n) şi numărul de transformări
cerut (ls).

Procedura conţine următorul algoritm:

• dacă nu a fost efectuat numărul de transformări necesar, se


calculează coordonatele punctelor care determină linia frântă obţinută
pornind de la segment şi pentru fiecare segment din această linie se
reapelează procedura desen;

• contrar, se desenează linia frântă obţinută.

In final, figura se colorează. Programul este prezentat în continuare:

uses graph,crt; #include "graphica.h"


var gdriver,gmode,ls:integer; #include <iostream.h>
#include <atdlib.h>
procedura initg; #include <conio.h>
#include <math.h>
procedura rotplan(xc,yc,xl, int gdriver,gmode,la,L;
yl : integer;
var x,y:integer; void init()
unghi : real); {
}
192 Capitolul 7. Metoda DIVIDE ET IMPERA

procedura desen(xl,yl,x 2,
y2,n,ls:inte ger); void rotplan(int xc,int yc,
var x3,x4,x5,x6, x7,x8,xc, int xl,int yl,int &x,int &y,
y3,y4,y5,y6, y7,y8,yc:inte ger; float unghi)
{
begin
}
if n<=ls then
begin void desen(int xl,int yl,int
x3:=(3*xl+x2 ) div 4; x2,int y2,int n,int ls)
y3:=(3*yl+y2 ) div 4; { int x3,x4,x5,x6, x7,x8,
rotplan(x3,y 3,xl,yl,x4,y4 , xc,y3, y4,y5,y6,y7, y8,yc;
-pi/2); if (n<=ls)
xc:=(xl+x2) div 2; { x3=div(3*xl+ x2,4) .quot;
yo:=(yl+y2) div 2; y3=div(3*yl+ y2, 4).quot;
rotplan(xc,yo ,x3,y3,x5,y5 , rotplan(x3,y 3,xl,yl,x4,y4 ,
-pi/2); -M_PX/2);
rotplan(xc,y c,x3,y3, xc=div(xl+x2 ,2) .quot;
yc=div(yl+y2 ,2).quot;
x6,y6,pi/2);
rotplan(xc,y c,x3,y3,x5,y 5,
x8:=(x1+3*x2 ) div 4;
-M_PI/2);
y8:=(y1+3*y2 ) div 4;
rotplan(xc,y c,x3,y3,x6,y 6,
rotplan(x8,y 8,xc,yc,x7,y 7,
M_PJ:/2);
pi/2); x8=div(x1+3* x2, 4).quot;
desen(xl,yl, x3,y3,n+l,ls ); y8=div(yl+3* y2, 4) . ·q uot;
desen(x3,y3 ,x4,y4,n+l,ls ); rotplan(x8,y 8,xc,yc,x7,y 7,
desen(x4,y4, x5,y5,n+l,ls) ; M_ PJ:/2);
desen(xS,yS ,xc,yc,n+l,ls ); desen(xl,yl, x3,y3,n+l,ls );
desen(xc,yc, x6,y6,n+l,ls) ; desen(x3,y3 ,x4,y4,n+l,ls );
desen(x6,y6, x7,y7,n+l,ls) ; desen(x4,y4, x5,y5,n+l,ls) ;
desen(x7,y7 ,x8,y8,n+l,ls ); d e sen(xS,yS , xc,yc,n+l,ls );
desen(x8,y8 ,x2,y2,n+l,ls ); desen(xc,yc, x6,y6,n+l,ls );
if n = ls then begin desen(x6,y6 ,x7,y7,n+l,ls );
moveto(xl,yl ); desen(x7,y7 ,x8,y8,n+l,ls );
lineto(x3,y3 ); desen(x8,y8, x2,y2,n+l,ls) ;
lineto(x4,y4 ); if (n == ls)
lineto(xS,yS ); { moveto(xl,yl );
lineto(x6,y6 ); lineto (x3 , y3) ;
lineto(x7,y7 ); lineto(x4 , y4);
lineto(x8,y8 ); lineto(xS,yS );
lineto(x2,y2 ); lineto(x6,y6 );
lineto(x7,y7 );
end
lineto(x8,y8 );
end
lineto(x2,y2 ); }
end;
}
begin }
write('ls= '); readln(ls); main()
initg; setcolor(red ); ( cout<<"ls= "; oin>>ls;
desen(100,10 0,300,100,1, ls); init(); setcolor{6);
desen(300,10 0,300,300,1,1 s); desen(100,10 0,300,100,1, ls);
desen(300,30 0,100,300,1, ls); desen(300,10 0,300,300,1, ls);
desen(100,30 0,100,100,1,1 s); desen(300,30 0,100,300,1, ls);
setfillstyle( l,blue); desen(100,30 0,100,100,1, ls);
floodfill(getm axx div 2, setfillstyle( 1,3);
getmaxy div 2, red); floodfill(div {getmaxx(),2 )
readln .quot,div(ge tmaxy(),2).q uot,6);
end. getch(); closegraph() ;
•.ţanual de informatică pentru clasa a Xl- a 193

Sunt prezentate mai jos imaginile obţinute în urma rularii programului, pentru
4
:: erite valori ale lui ls:

ls==l ls=2

Figura 7.4. Exemple de fractali formaţi cu ajutorul curbei lui Koch (pătrat)

7.3.4. Arborele

Se d ă un segment AB. Cu ajutorul lui se construieşte un arbore, aşa cum se


vede in figura de mai jos:

Figura 7.5. Exemplu de transformare in cazul unul arbore

Lungimea fiecarei ramuri este o treime din lungimea iniţială a segmentului.


Fiecare latură se transformă Tn mod asemănător. Se cere sa se vizualizeze figura
astfel rezultată, dupa ls transformări.

Pentru obţinerea ramurilor se procedează astfel:

• se consideră punctul situat pe dreapta determinată de segment şi


pentru care avem:

• se roteşte acest punct în jurul punctului B(x2.y2) cu un unghi de 1r /4;


• se roteşte punctul în jurul lui B cu unghiul -re /4 .
194 Capitolul 7. Metoda DIVIDE ET IMPW

ln urma acestor rotaţii se obţin coordonatele punctelor care, împreună c...


punctul B, determină segmentele ce costituie ramurile arborelui.

Procedura desenez are ca parametri de intrare coordonatele unui segment


numărul de transformări efectuate (n) şi numărul de transformări care trebu1e
efectuate {ls). În cazul în care nu s-au efectuat toate transform~rile, se traseaz~
segmentul (cu o culoare oarecare), se calculează coordonatele punctelor care
determină ramurile şi, pentru fiecare segment, se reapelează procedura.

Programul este prezentat mai jos:

uses graph,crt; #include "graphics. h"


#include <iostream. h>
var gdriver,gm ode,ls:inte ger; #include <stdlib.h>
xmax,ymax :integer1 #include <conio.h>
procedure initg; #include <math.h>
int gdriver,gm ode,ls,LI
void init()
procedure rotplan(xc ,yc,xl,
yl:integor 1 var x,ytintoge rt
{... }

unghi:rea l); void rotplan( ••• )


{... )

procedure desenez(x l,yl,x2,y2, void desenez(in t xl,int yl,


n,ls linteger) 1 int x2,int y2,int n,int ls)
var x,y:intege r; { int x,y,
begin if (n<=ls)
if n<=le then { setcolor(l+ random(15 ))1
begin moveto(xl ,yl);
eetcolor(l+ random(15 ))1 lineto(x2,y 2)1
moveto(xl ,yl)l rotplan(x2 ,y2,div(3* x2-
lineto(x2,y 2)1 x1 ,2).quot,di v(3*y2-y1 ,2)
rotplan(x2 ,y2, (3*x2-x1) div .quot,x,y,M_ PI/4)1
2,(3*y2-yl ) div 2,x,y,pi/4 )1 desenez(x 2,y2,x,y,n +l,ls);
desenez(x 2,y2,x,y,n +l,ls)t rotplan(x2 ,y2,div(3* x2-
rotplan(x2 ,y2,(3*x2- xl) div x1,2),quot ,div(3*y2- yl,
2, (3*y2-yl) div 2,x,y,-pi/ 4)t 2),quot,x,y ,-M_PI/4) ;
deeenez(x 2,y2,x,y,n +l,ls)1 desenez(x 2,y2,x,y,n +l,ls)1
}
end
}
end1
begin main()
randomize; { randomize ();
write('lsa '); readln(ls) J cout<<"ls• "1 cin>>1s;
init()l setcolor(6 )1
initg;
desenez(di v(getmaxx (),2)
setbkcolor (white)l
• quot, getmaxy ( ) ,
desenez(ge tmaxx div 2,
div(getma xx(),2).qu ot,
getmaxy, getmaxx div 2,
getmaxy() -250,l,ls);
getmaxy-2 50,1,ls)l getoh();
readln closegraph ( ) 1
end. }
vanual de informatică pentru clasa a Xl- a 195

Pentru diverse valori ale parametrului de intrare ls, vom obţine arborii:

ls =3 ls =5 ls =7
Figura 7.6. Exemple de fractali de tip arbore

~ Observaţii

~ Exemplele grafice prezentate au fost generate pentru valori mici ale lui ls
deoarece la tipărire, detaliile sunt greu de observat peste o anumită limită .

./ Generarea fractalilor reprezintă o apl i caţie a recursivităţii, tehnica aplicată


fiind DIVIDE ET IMPERA. Pentru valori mari ale lui ls, timpul de efectuare al
calculelor poate fi şi de ordinul zecilor de secunde, ceea ce poate fi
considerat un inconvenient major.

Probleme propuse
1. Se citeşte a~1. număr real. Se cere să se scrie o funcţie care calculează ln(a)
cu 3 zecimale exacte. Nu este permisă utilizarea funcţiei logaritmice a limbajului.

2. Scrieţi o funcţie care calculează prin metoda DIVIDE ET IMPERA suma numerelor
reţinute dintr-un vector.

3. Referitor la problema anterioară: care este complexitatea algoritmului folosit?


Se va considera ca operaţie de bază adunarea.

4. Se citeşteun număr real xe ( -1oooo, 10000). Să se afişeze partea


fracţionară. Exemple: pentru x=1.23, se va afişa: 0.23 ; pentru x=-12.7, se va
afişa o. 7 . Nu se vor folosi funcţii specializate ale limbajului.

5. Se ştie că ecuaţia x 3 +x-1=0 are o singură rădăcină reală Tn intervalul <o, 1).
Scrieţi un program, care o afişează cu 4 zecimale exacte.
196 Capitolul 7. Metoda DIVIDE ET IMPERA

6. Problema selecţiei. Se consideră un vector cun componente numere naturale


şi l~t~. Se cere să se determine al t-lea cel mai mic element. Imaginaţi
o
rezolvare care utilizează funcţia Poz de la sortarea rapidă!
7. Seconsideră un vector care reţine n numere naturale. Se cere să se determine
dacă exi stă un element majoritar (adică un număr care se găseşte în mai
mult de
[n 1 2]+ 1 elemente).
Victor Mitrana
8. Fiind dat x real, să se calculeze
funcţii
l\G Jcu patru zecimale exacte! Nu se vor folosi
specializate ale limbajului.
9. Se pleacă de la un pătrat a cărui suprafaţă se divide în 9 părţi egale prin
împărţirea fiecărei laturi in 3 părţi egale. Pătratul din mijloc se elimină. Cu
pătratele
rămase se procedează la fel. Vizualizaţi figura după ls astfel de transform
ăn
(Covorul lui Sierpinsk1).

Răspunsuri

1. ln (a) =x (::> a=e>C (::> ex-a=O. Dacă notăm cu f (X) =ex-a, atunci trebuie
rezo lvată ecuaţia f(x)•O. Avem f(O)=e 0 -a==1-a<O şi f(a)=e 4 -a>O. De
aici,
rezultă că f(x) are o rădăcină în intervalul (O,a). Cum f(x) este
strict
crescătoa re (ca diferenţă între funcţia strict crescătoare e" şi o constantă ),
rădăcina este unică . Algoritmul pe care îl folosim se numeşte în matematică
"metoda Tnjum~t~ţirii intervalului', dar, din punct de vedere informatic, corespund e
metodei DIVIDE ET IMPERA.
Fie liaO şi ls•a, m=(a+b) /2. Dacă f(li)Xf(m )<O, rădăcina se găseşte în
(li,m) , altfel rădăcina este în [m,ls) . Condiţia de terminare este ca
jli - lsj < 0.0001, pentru că trebuie să avem 3 zecimale exacte.

' Varianta Pascal · . ." ..


··1' Varianta C++
var a:real; #include <iostream .h>
function LogN{a, li,ls:dou ble): #include <math.h>
double; double a;
begin double LogN ( double a, double li,
if aal then LogN:=O double l s)
e l se { if (a==l) return O;
if abs(li-ls )< O.OOOl el se
then LogN:a( li+ls)/2 if (fabs(li-l s)<O.OO Ol)
el se return (li+ls)/2 ;
if (exp(li) -a )* el se
(e.x p((li+ls) /2) -a)<O if ((exp(li) -a)*
then {exp((li +ls)/2) -a)<O)
LogN:=Lo gN (a,li ,(li+ls)/ 2 ) return LogN(a,l i,
el se (li+ls)/2 );
LogN:=L ogN(a,(li +ls)/2,ls) else return LogN(a,
end1 (li+ls)/2 ,ls);
}
'anual de informatică pentru clasa a Xl-a 197

begin main()
write {'a=' ) ; readln(a); { cout<<"a="; cin>>a;
writeln(' rezultat cout<<"rezultat calculat "
calculat:',LogN(a,O,a):3 :3); <<LogN(a,O,a)<<endl;
writeln(' rezultat preluat • cout<<"rezultat preluat "
ln(a):3:3); <<log(a)<<endl;
end. }

f Practic, la fiecare pas se injumătăţeşte intervalul în care se caută soluţia şi


• aceasta corespunde strategiei generale DIVIDE ET IMPERA.

2. Programul este prezentat mai jos:


·•:.,
Varianta Pascal 1 Ilo 1! ,,,

type vector=array[1 •• 9] of tinclude <iostream.h>


integer;
int n, i , •t [10] 1
var v:vector; int Suma(int li, int ls)
n,i:integer; { if (li==ls) return v[li]J
else return
function Suma(li, (li+ls)/2)+
Suma(li,ls:integer):inte ger; Suma((li+ls)/2+1,1s);
begin }
if li=ls then Suma:=v(li] main()
el se { cout<<"n=";
Suma:=Suma(li,(li+ls) div 2) cin>>n;
+ Suma((li+ls) div 2+l,ls); for (isl;i<•n;i++)
end; cin»v [i];
begin cout<<Suma(l,n);
wri te ( • n= • ) ; }
readln(n);
for i:=l to n do
readln(v[iJ);
writeln(suma(l,n));
end .

3. Fiecare problemă se descompune in alte două şi rezultatul se adună. Pentru


simplitate, consideraţi n=2~<. fn final, se obţine o (n). Puteţi scrie şi funcţia recursivă
care calculează T (n), dar, pentru a obţine rezultatul corect, luaţi n•2~<:

4. A calcula [x] se reduce la DIVIDE ET IMPERA. Partea fracţionară se obţine uşor,

dacă calculăm Jx- [x] .


198 Capitolul 7. Metoda D IVIDE ET IMPERA

5. Vedeţi problema 1.

6. Funcţia Poz returnează poziţi a k pe care se va găsi, după rularea ei, primul
element al vectorului. în plus, toate elementele de indice mai mic decât k sunt mai
mici sau egale decât A [kl şi toate elementele de indice mai mare decât k sunt mai
mari sau egale decât A [kl . Altfel spus: elementul A [ 11 , care se află după rularea
funcţiei pe poziţia k, este al k-lea cel mai mic element din vectorul A. Atunci, în
cazul în care k=t , problema este rezolvata. Dacă t<k, elementul căutat are
indicele cuprins între l i şi k-1 şi reluăm rularea funcţiei Poz între aceste limite, iar
dacă t>k, elementul căutat are indicele între k+l şi ls şi rel uăm rularea fu ncţiei
Poz între aceste limite. Datorită faptului că, la fiecare pas, se restrânge numărul
valorilor de căutare, se ajunge în situaţia în care t=k.

Secvenţa este:

lil=1; li=l;
ls:=n; ls=n;
repeat do
poz(li,ls,k,a); { poz(li,ls,k,a);
if t<k then lsJ=k-1; i f (t<k) ls=k-1;
if t>k then li:=k+l; i f (t>k) lia k+1;
until t=k; }while ( t 1=k) ;
writeln('Elementul cautat • cout<<"elementul cautat "
a [t]) 1 «a[t] 1

7. După aplicarea algoritmului de la problema anterioară, elementul din mijloc


trebuie să fie majoritar.
Capitolul 8
Metoda 8ACKTRAC KING

3.1 . Prezentarea metodei

5. 1.1. Când se utilizează metoda backtracki ng?

Metoda backtracking se foloseşte în rezolvarea problemelor care îndeplinesc


: multan u rmătoarele condiţi i :

• soluţia lor poate fi pusă sub forma unui vector S=xl,x::~, .•. ,x0 , cu
X1EA1 , X 2 EA::~, . .. , XnEAo;

• mulţim i le A 1 , A2 , ... , An sunt mulţimi finite, iar elementele lor se consideră


că se află într-o relaţie de ordine bine stabilită;

• nu se dispune de o altă metodă de rezolvare, mai rapidă .

În continuare, este prezentat un exemplu de problemă care poate fi


>J..v•
-"'"'· rezolvat prin utilizarea tehnicii backtracking.

Generarea permutărilor. Se citeşte un număr natural n . Să se 123


;enereze toate permutările mulţimii {1, 2, ••• , n). De exemplu, pentru 132
::~ •3 , permutări le mulţimii {1, 2, 3} sunt prezentate alăturat. 213
231
=>entru această problemă, A 1aA2=A3={1, 2, 3}. Fie permutarea 213. Ea 312
321
:ste scrisă sub formă de vector, unde 2eA1 , leA2 şi 3eA3 •

8.1 .2. Principiul care stă la baza metodei backtracki ng

Principiul care stă la baza metodei backtracking va fi prezentat printr-un


:xemplu, acela al generării permutărilor. Cum se poate rezolva această
oroblemă?

O primă soluţie ar fi să generăm toate elementele produsului cartezian:

{1,2,3}X(1,2 ,3}X{1,2,3} :{11, 12, 13, 21, 22, 23, 31, 32, 33}
X{1,2,3)= {111, 112, 113, 121, 122, 123, 131, 132, 133, 211,
2 12, 213, 221, 222, 223, 231, 232, 233, 311, 312, 313, 321,
322, 323, 331, 332, 333}.
200 Capitolul 8. Metoda backtracking

Apoi, urmează să vedem care dintre elementele acestui produs cartezian


sunt permutări, adică să conţină numai numere distincte. Astfel, 111, 112 ...
nu sunt permutări, dar 123 este permutare, ş.a.m.d. Produsul cartezian are 27 de
elemente şi dintre ele, doar 6 sunt permutări. in general, produsul cartezian are
nn elemente, din care permutări sunt doar n t. Vă daţi seama că un astfel de
algoritm de generare a permutărilor este ineficient. ..

intrebarea este dacă problema nu se poate rezolva eficient? Să observăm


că nu are rost să generăm un element al produsului cartezian pentru ca apoi, să ne
dăm seama că nu este permutare, deoarece nu este alcătuit din numere distincte.
De exemplu, dacă la un pas al algoritmului am generat 22, e clar că nu se poate
o
obţine permutare, oricare ar fi num ărul care urmează pe poziţia 3.

Principiut metodei

+ M~toda backtracking are la bază un principiu simplu: dacă fn procesul de


generare a unui vector soluţie S=x1 x 3 , • • • ,xn. pentru componenta k, atunci
când am generat deja x 1x 3 ••• x~c, constatăm că valoarea xk nu este bine
aleasă (păstrand-o nu se va ajunge la o soluţie), nu trecem componenta k+l
ci reluăm căutarea pentru altă valoare pentru componenta k, iar dacă
această valoare nu există, reluăm căutarea pentru componenta k-1.

~· Observaţi faptul că după ce am analizat posibilele valori pe care le poate lua


• componenta k, avem două posibilităţi: ori trecem la componenta k+1 (facem
pasul 'înainte), ori mergem la componenta k-1 (facem pasul înapoi).

Trecem la exemplificarea algoritmului pentru generarea permutări lor, în


cazul în care n=3.

• Componenta 1 va memora numărul 1. Întrucât există


permutări care încep cu 1, trecem la elementul 2 - facem
pasul înainte.

• Componenta 2 va memora numărull. l1 l1 1


• Nu există permutări care incep cu 1,1, motiv pentru care, 1 1 1 1
pentru aceeaşi componentă, vom reţine valoarea următoare, 1 2
adică 2. intrucât există permutări care incep cu 1,2, vom trece la elementul
3 (înainte).

• Componenta 3 va memora numărul! . 1112111


• Nu există permutări care sunt de forma 1,2,1, motiv pentru
care aceeaşi componentă va reţine numărul următor, 2. 1112121
• Nu există permutări care sunt de forma 1,2,2, motiv pentru
care aceeaşi componentă va memora numărul următor, adică 1112131
3. Am obţinut deja o prima soluţie şi o afişi:!m .
Manual de informatică pentru clasa a Xl-a 201

Pentru componenta 3 , nu există o altă valoare pe care o 1 3 0


1 1 1 1
putem utiliza. Din acest motiv, vom trece la elementul 2,
(înapoi). Componenta 2 are deja memorată valoarea 2. Alegem valoarea
următoare, 3. întrucât există permutări care incep cu 1,3, vom trece la
elementul următor, 3 (înainte).

Prima valoare care poate fi memorată este 1 . Întrucât nu


există permutări de forma 1, 3, 1 trecem la valoarea
următoare 2. Dar 1, 3, 2 este soluţie şi o afişăm.

Algoritmul continuă până când se ajunge la componenta de indice o. În acel


moment, au fost deja afişate toate permutările.

Exerciţiu. Arătaţi cum funcţionează algoritmul până se ajunge la


componenta de indice o.

8.1 .3. O modalitate de implementare a metodei backtracking

Pentru uşurarea înţelegerii metodei, mai întâi vom prezenta un subprogram


general, aplicabil oricărei probleme. Subprogramul va apela alte subprograme care
au intotdeauna acelaşi nume şi parametri şi care, din punct de vedere al
metodei, realizează acel aşi lucru. Sarcina celui care face programul este să scrie
explicit, pentru fiecare problemă fn parte, subprogramele apelate de acesta .

../ Evident, o astfel de abordare conduce la programe cu multe instrucţiuni. Din


acest motiv, după înţelegerea metodei backtracking, vom renunţa la această
formă standardizată. Dar, principiul rămâne nemodificat.

lată subprogramul care implementează metoda. El va fi apelat prin


back(1) .

j,f, 1 1 ••
rl::,,. ' Varianta C++ ...•r,,,,. "'..":; .
,"·'" v~~la"'ta ~~scai .. l",. : ·!:r 1 '/: lr, · .

procedura back(k:integer); void back(int k)


begin {
if solutie(k) if (solutie(k)) tipar()/
then tipar el se
el se { init (k) 1
begin while(succesor(k))
init(k); if (valid(k)) back(k+l);
while s uccesor(k) do }
if valid(k) then back(k+l) }
end
end;
202 Capitolul 8. Metoda backtracking

Să-I analizăm! Subprogramul are parametrul k de tip întreg. Acest parametru


are semnificaţia de indice al componentei vectorului pentru care se caută o valoare
convenabilă. Algoritmul va porni cu componenta de indice 1. Din acest motiv,
subprogram ul se va apela cu back ( 1). După cum observaţi, subprogramul
este recursiv.

-+ Iniţial se testează dacă s-a generat o soluţie . Pentru aceasta, se apelează


subprogramul solutie (k).

Pentru permutări, vom avea o soluţie când s-a generat o secvenţă alcătuită din n
numere distincte. Cum subprogramul este recursiv, acest fapt se intâmplă atunc1
când s-a ajuns pe nivelul n+l.

-+ Dacă s-a obţinut o soluţie, aceasta se afişează. Pentru această operaţie se


va utiliza subprogramul tipar.

-+ În situaţia in care nu a fost obţinută o soluţie, se iniţializează nivelul k .


Iniţializarea se face cu valoarea aflată înaintea tuturor valorilor posibile. Se
va folosi subprogramul init.

Pentru permutări, iniţializarea se face cu o.

-+ După iniţializare, se generează, pe rând, toate valorile mulţimii A~t. Pentru


aceasta se utilizează subprogramul succesor. Rolul său este de a atribui
componentei k valoarea următoare celei deja existente.

-+ Pentru fiecare valoare generată, se testează dacă aceasta


îndeplineşteanumite condiţii de continuare. Acest test este realiza1
de subprogramul valid:

-+ în cazul în care condiţiile sunt îndeplinite, se trece la


componenta k+l, urmând ca generarea valorilor pe nivelul k să
continue atunci când se revine pe acest nivel;

-+ dacă condiţiile de continuare nu sunt îndeplinite, se


generează următoarea valoare pentru componenta k.

-+ După ce au fost generate toate valorile mulţimii Al< se trece, implicit, la


componenta k-1, iar algoritmul se încheie când k=O.

Pentru permutări, pe fiecare nivel, valorile posibile sunt A~ta { 1, 2, ••• , n) .


Condiţia de continuare, în acest caz este ca valoarea aflată pe nivelul k să fie
distinctă în raport cu valorile aflate pe nivelurile inferioare.

Programul de generare a permutărilor este prezentat în continuare.


\1anual de info rmatică pentru clasa a Xl-a 203

var n:integer; linclude <iostream.h>


sol:array [1 •. 10] of #include <iostream.h>
integer;
int n, sol[10];
procedura init(k:integer);
begin void init(int k)
sol[k]:=O { aol[kJ=O;
}
end;
function succesor int succesor(int k)
(ktinteger):boole an; { if (sol[k] <n)
begin { sol[k]++t
if aol[k]<n then return 1; }
begin el se return O1
}
sol[k]t=aol[k]+l t
succesor: .. true int valid(int k)
end { int i, ev=l;
else succesor:=false for (i•l;i <•k-lt i++)
endt if (sol[kJ ==sol [i]) ev~o;

function valid return ev;


}
(k:integer):boole an;
var i:integert int solutie(int k)
begin { return k ..•n+l;
validt•true; }
for i:=l to k-1 do
if •ol[i]•sol[k] then void tipar ( )
valid:=false { for {int i •l;i<•n;i++)
end; cout«sol [il 1
cout<<endl;
function solutie }
(krinteger):boole an;
begin void back(int k)
solutie:=(k=n+l) { if (solutie(k)) tipar();
end; el se
{ init(k);
procedura tipar; while(succesor(k ))
var i:integer; if (valid(k)) back(k+1);
begin )
for it•1 to n do }
write(sol[i]);
writeln main()
end; { cout<<"n•"; cin>>n;
back(1);
procedure back(k:integer);
begin
if solutie(k)
then tipar
el se
begin
init (k) 1
w~le succesor(k) do
~f valid(k) then
back(k+1)
end
end;
begin
write('n=')t readln(n);
back(l)
end.
204 Capitolul 8. Metoda backtrackir=

8.1.4. Problema celor n dame

O Enunţ. Fiind dată o tablă de şah cu dimensiunea nXn, se cer toate


soluţiile
de aranjare a n dame, astfel încât să nu se afle două dame pe
aceeaşi linie, coloană sau diagonală (damele să nu se atace reciproc).

De exemplu, dacă n=4, o soluţie este reprezentată în figura 8.1., a). Modul de
obţinere al soluţie i este prezentat în figurile urm ătoare, de la b) la i):

~ .:
\lW 1·· "

~
' '
,.... " .. ·'· '·· !1: i.:,"·
l .r. ,,, •.·..
·" li '

a) b) c)

.-!.;·:·,~.
~
~-\
~
1'11 1'

1 ~·· ·~
:

',. ~ ...
~ 'i:::;jl:,
!" :r;;-!:- .1•1:
1;: ti,..: '
1·· '

d) e) f)
,..,
~ ~
li:;. :;ii'' ' ;1 1!•

·-
1. :.:'
1

\liV \lW
~ \}JV
'::' ~
g) h) i)

Figura 8.1. Exemplu pentru n • 4


\ianual de informati că pentru clasa a Xl-a 205

Comentarii referitoare la figurile anterioare


b) Observăm că o damă trebuie să fie plasată singură pe linie. Poziţionărn prima
damă pe linia 1, coloana 1.
c) A doua damă nu poate fi aşezată decat în coloana 3.
d) Observăm că a treia damă nu poate fi plasată în linia 3. încercăm atunci
plasarea celei de-a doua dame în coloana 4.
e) A treia damă nu poate fi plasată decât în coloana 2.
f) in această situaţie dama a patra nu mai poate fi aşezată. Tncercând să
avansăm cu dama a treia, observăm că nu este posibil să o plasăm nici în coloana
a-3-a, nici în coloana a-4-a, deci o vom scoate de pe tablă . Dama a doua nu mai
poate avansa, deci şi ea este scoasă de pe tablă . Avansăm cu prima damă in
coloana a-2-a.
g) A doua damă nu poate fi aşezată decat in coloana a 4-a.
h) Dama a treia se aşează în prima coloană.

i) Acum este posibil să plasăm a patra damă în coloana 3 şi astfel am obţin ut o


soluţie a problemei.

Algoritmul continuă fn acest mod până când trebuie scoasă de pe tablă


prima damă.

-+ Pentru căutarea şi reprezentarea unei soluţii folosim un vector cu n componente,


numit sol (având in vedere că pe fiecare linie se găseşte o singură damă) . Prin
sol [il înţelegem coloana în care se găseşte dama de pe linia i.

Alătu.rat, puteţi observa m~dul in care este reprezentată


soluţia cu ajutorul vectorulUI sol .
2 4 1 3
- _ _ _ _
l 1 1 1 1

-+ Două dame se găsesc pe aceeaşi diagonală dacă şi numai dacă .este


Tndeplinită condiţia:

lsol(i) -sol(j)l=li-jl
(diferenţa, în modul, dintre linii şi coloane este aceeaşi).

Exemple:
a)
sol(l) = 1 i • 1
sol(3) = 3 j = 3
lsol(l) - sol(3) 1 = 11 - 31 a 2
l i - jl = 11 - 31 = 2

Agura 8.2.
206 Capitolul 8. Metoda backtracking

b)
sol(l) = 3 i 1 =
sol(3) = 1 j =3
lsol(i) - sol(j) 1 = 13 - 11 = 2
li- jl = 11- 31 = 2

Figura 8.3.

1ntrucat două dame nu se pot găsi în aceeaşi coloană, rezultă că o soluţie


este sub formă de permutare. O primă idee ne conduce la generarea tuturor
permutărilor şi la extragerea soluţiilor pentru problemă (ca două dame să nu fie
plasate in aceeaşi diagonală) . Dacă procedăm astfel, înseamnă că nu lucrăm
conform strategiei backtracking. Aceasta presupune ca imediat ce am găsit două
dame care se atacă, să reluăm căutarea în alte condiţii. Faţă de programul de
generare a permutărilor, programul de generare a tuturor sol uţiil or problemei celor
n dame are o singură condiţie suplimentară, în subprogramul valid. Mai jos,
puteţi observa noua versiune a subprogramului valid. Dacă îl utilizaţi in locul
subprogramului cu acelaşi nume din programul de generare a permutărilor, veţi
obţine programul care rezolvă problema celor n dame.

f unction valid(k:integer) : int valid(int k)


boolean; {
var i:integer; for (int i=1;i<k;i++)
if (sol[kl==sol[i] 11
begin abs(sol[k]-sol[i]) ~•abs(k-i))
valid:•true; return O;
for i : •l to k-1 do return 1;
if (sol[k) • sol[i l ) or }
(abs(sol[k]-sol[i ])=abs(k- i))
then
valid: • false
end;

Problema este un exemplu folosit în mal toate lucrările in care este


prezentată metoda backtracking.

In ceea ce ne priveşte, dincolo de un exemplu de backtracking, am avut


ocazia să vedem cat de mult seamănă rezolvările (prin această metodă) a
două probleme între care, aparent, nu există nici o legătură.

Exerciţii

1. Desenaţi configuraţia tablei corespunzătoare vectorului sol .. ( 3, 1, 4, 2, s) şi


verificaţi dacă aceasta reprezintă o soluţie a problemei damelor.
'Aanual de Informatică pentru clasa a Xl-a 207

2. Explicaţi de ce configuraţia corespunzătoare vecorului sol= ( 3 1 11 3 1 41 5 > nu


aste o soluţie a problemei damelor. Care este poziţia din sol la care nu s-a făcut
alegerea corectă a unei valori valide?

3. Explicaţi de ce nu orice permutare a mulţimii {11 21 ... 1 n) este o soluţie a


oroblemei damelor pe o tablă cu n linii şi n coloane.

4. Determinaţi, folosind metoda backtracking, o soluţie care se obţine pe o tablă cu


şase linii şi şase coloane, ştiind că dama plasată pe ultima linie trebuie să se afle
pe a doua coloană. Consideraţi că mai este util să completăm vectorul sol
pornind de la poziţia 1? Dacă pornim de la poziţia 1, ce adaptări trebuie făcute?

8.2. Mai puţine linii În programul sursă

Pană în prezent, am rezolvat două probleme prin metoda backtracking:


generarea permutărilor şi problema celor n dame. În ambele cazuri, am utilizat
subprogramul standard.

? Este Tntotdeauna necesar să-I folosim pe acesta sau putem reţine numai
• ideea şi, după caz, să scriem mai puţin?

Evident, după ce am înţeles bine metoda, putem renunţa la subprogramul


standard. Pentru aceasta, încorporăm în subprogramul standard unele dintre
subprogramele pe care le-ar fi apelat.

Vom exemplifica această incorporare pentru problema celor n dame, deja


rezolvată standardizat.

-+ Putem elimina subprogramul init. Este foarte uşor de realizat această


operaţie. În locul apelului vom scrie instrucţiunea prin care componentei k i
se atribuie o.

-+ Putem elimina subprogramul solutie. ln locul apelului vom testa dacă k


este egal cu n+l.

-+ Putem elimina subprogramul tipar. tn locul apelului vom scrie secvenţa


prin care se afişează st.

-+ Putem elimina subprogramul succesor. fn locul apelului vom testa dacă


st [kJ <n şi avem grijă să incrementăm valoarea aflată pe nivelul curent.

Puteţi observa în continuare programul obţinut. Este cu mult mai scurt!


Oricum, Ideea de rezolvare rămane aceeaşi. Subprogramele prezentate au fost
numai încorporate, nu s-a renunţat la ele.
208 Capitolul 8. Metoda backtracking

! .,. (, ,..,,..
Varianta Pascal .;, : ..'' Varianta C++
var sol:array[1 •• 9] of integer; #include <iostream.h>
n:integer1 #include <math.h>
int n, sol[lO];
function
valid(k:integer):boolean l int :valid(int k)
{ for (int i=l;i<k;i++)
var i :integer; if {sol [k] ==sol [il 11
begin abs(sol[k]-sol[i])
valid:•true; ==abs(k-i))
for i:=l to k-1 do return O;
if (sol[k]=sol[i]) or return 1;
(abs(sol(k]-sol[i])=abs( k-i)) }
then void back ( int k)
valid:=false { if (k==n+l) // solutie
end; //tipar
{ for (int i=l;i<=n;i++)
procedura back(k:integer); cout<<sol[i];
cout<<endl;
var i:.integer; }
begin el se
if k•n+l {solutie} { sol[k]cO;
then while(sol[k]<n)
begin 11 succesor
{tipar} { sol[k]++i
for i:=l to n do if (valid(k))back(k+l);
write(sol[i));
writeln; }
end
main()
el se
begin { cout<<"n'""l cin>>n;
sol[k] :=O; {init} back(l);
while sol[k]<n do }
{succesor}
begin
sol[k]: .. sol[k]+l;
if valid(k)
then
back(k+l)
end
end
end1
begin
write( 'n=');
readln(n);
back(l)
end.

Uneori veţi întâlni şi o rezolvare precum următoarea, care în subprogramul


back, pentru succesor se foloseşte o instrucţiune repetitivă de tip for:
anual de informatică pentru clasa a Xl-a 209

··t:· · Varianta Pascal ·.> · :... ··'' . ·varianta C++

procedura back(k:int eger); void back(int k)


var i:integer; { int i;
begin if (k==n+l)
i f k=n+l { for (i=l;i<=n;i ++)
then cout<<sol [i];
begin cout<<endl ;
for i:=l to n do }
write(sol [i]); el se
writeln; for (i=l;i<=n;i ++)
end { sol[k]=i;
el se i f (valid(k))
for i:=l to n do back(k+l);
b egin }
sol[k] : =i ;
if valid(k)
then back(k+l)
end
end;

~ Exerciţii
1. Testaţi subprogramuf anterior pentru problema generării permutărilor.

2. Adaptaţi rezolvarea problemei permutărilor astfel încât să se afişeze numai


permutările în care oricare două numere consecutive nu sunt a lăturate.

3. Observaţi că ordinea de afişare a soluţiilor depinde de ordinea în care se


consideră elementele mulţimilor Al, A2 , ... Ce modificări trebuie aduse
procedurii recursive back astfel încât permutarile de 4 elemente să fie afi şate în
o~~ea:4321,4312,4231,4213,4132,4123,3421,3412 ... 1243,1234 ?

4. Renu nţaţi la utilizarea subprogramului valid, utilizând un vector folosit , în


care folosit [il are valoarea o dacă numărul i nu este deja folosit în soluţie
şi are valoarea 1 în caz contrar. Astfel, plasarea valorii i în vectorul soluţie
(sol [kl ~i) trebuie însoţită de memorarea faptului că i este utilizat
(folosit [il ~1), la revenirea din recursie (cand se înlătură valoarea de pe
poziţia curentă) fiind necesară memorarea faptului că i nu mai este utilizat în
so luţie (folosit [il ~o). Condiţia de validare se reduce în acest caz fa:

Dacă folosit [i] =O atunci •••

5. toate modalităţile diferite de a aşeza patru obiecte identificate prin


Urm ăriţi
numerele 1, 2, 3, 4 pe un cerc, la distanţe egale. Vom observa că nu toate
permutările de patru obiecte sunt configuraţii distincte, datorită distribuţiei pe
cerc. Astfel permutările 1234 , 2341, 3412 şi 4123 reprezintă una şi aceeaşi
configura ţi e. Scrieţi un program care afişează numai permutări le distincte
conform aşezării pe un cerc. Indicaţie: se va considera ~ol [1] =1 şi se vor
permuta doar celelalte elemente.
210 Capitolul 8. Metoda backtraclc-:::

8.3. Cazul în care se cere o singură soluţie.


Exemplificare: problema colorării hărţilor

Sunt probleme care se rezolvă cu metoda backtracking şi in care se cere :


singură soluţie.

Implementarea "ca la carte" presupune utilizarea unei variabile <*


semnalizare (de exemplu, variabila gata) care să fie iniţial o, la obţinerea solu ţie
dorite aceasta primind valoarea 1 . Orice succesor va fi condiţionat Tn plus dE
valoarea variabilei gata.

De această dată, pentru simplitate, vom renunţa la programarea structura ~


şi vom opri în mod forţat programul.

-+ În Pascal, veţi utiliza procedura halt.


-+ În C++, veţi folosi funcţia exit, cu parametrul EXIT_succEss
(o constantă). Pentru a o putea utiliza, trebuie sa includeţi fişierul ante~
"stdlib.h":
#inolude<stdlib .h>.

ln ambele cazuri, secvenţa care determină oprirea forţată este trecuta


imediat după ce prima soluţie a fost afişată.

~ Exerciţiu. Modificaţi programul care rezolvă problema celor n dame, astfel


încât acesta să afişeze o singură soluţie.

O Problema colorării hărţllor. Fiind dată o hartă cu n ţări, se cere o soluţie de


colorare a hărţii, utilizând cel mult 4 culori, astfel incat două ţări cu frontieră
comună să fie colorate diferit.

~ Este demonstrat faptul că sunt suficiente numai 4 culori pentru ca orice hartă
• .~ să poată fi colorată.

Pentru exemplificare, vom considera harta din figura 8.4., unde ţările sunt
numerotate cu cifre cuprinse între 1 şi 5 .

O soluţie a acestei probleme este următoarea:

• ţara 1 - culoarea 1;
• ţara 2 -culoarea 2;
• ţara 3 -culoarea 1;
• ţara 4 -culoarea 3;
Figura 8.4.
• ţara 5 - culoarea 4 .
Manual de info rmati că pentru clasa a Xl-a 211

Harta este fu rnizată programului cu ajutorul unei matrice (tablou) An,n:

A(i,j) = {~: ţara


altfel
i are frontiera comună cu ţara j

Matricea A este simetrică. Pentru rezolvarea problemei se utilizează


vectorul sol , unde sol [kl reţi ne culoarea ataşată ţării k. Evident, orice soluţie
are exact n componente.

var sol:array(1 •• 9] of #incl ude <iostream. h>


integer; #incl ude <stdlib . h>
a:array[1 • • 10 1 1 •. 10] of int n 1 i, j, sol [10] 1 a [10] [10];
integer;
n1i 1 j:integer; int valid(int k)
{ for (int i=1;i<k;i++)
function
if (sol[k]==sol[i) &&
val id(k:integer):boolean;
a[i] [kl==1) return O;
begin
return 1;
valid:=true;
}
for i:~l to k-1 do
if (sol (k]=sol ( i)) void back(int k}
and (a[klil=1) { int i;
then valid:=false if (k==n+1)
end; { for (int i=1;i <=n;i++)
procedura back(k:integer); cout<<sol(i];
var i:integer; exit(EXIT_SOCCESS);
begin }
if k=n+1 then el se
begin for (i=1;i<=4;i++)
for j:=l ton do { sol[k]ci ;
write(sol [ j]); if (valid(k))
halt; back(k+1);
end }
el se }
for i:=l to n do
begin main()
sol[kl:=i; { cout<<"Numarul de tari =";
if valid ( k) cin>>n;
then back(k+l) for (int i=l;i<=n;i++)
end; for Cint j=1;j<= i - 1;j++)
end; { cout<<"a["<<I
begin <<','<<:!<<"]=";
write('Numarul de tari='); cin»a(i) (j] ;
readln(n); a ( j] [i] =a(i] [j];
for i:•l to n do
for :1:=1 to i-1 do back(1);
begin }
wri te ( 'a [ • , i 1 • 1 • , j 1 ' l = ' ) ;
readln(a[i 1 j]);
a[j,i] :;:a[i,j ]
end;
back(1)
end.
212 Capitolul 8. Metoda backtrackinc

Exerciţii

1. Soluţia afişată este şi soluţia care utilizează un număr


minim de culori?
2. Dacă ţările din centrul figurii alăturate sunt numerotate
cu 1, 2, 3, 4, iar cele de la exterior cu 5 şi 6, care este
soluţia afişată de programul dat? Este acesta numărul
minim de culori necesare?
3. Câte culori sunt suficiente pentru colorarea unei hărţi
particulare in care orice ţară se învecinează cu cel Figura 8.5.
mult două ţări?
4. Daţi exemplu de particularitate pe care poate să o aibă o hartă pentru a fi
suficiente două culori pentru colorarea tuturor ţărilor?

8.4. Aplicaţii ale metodei backtracking în


combinatorică

8.4.1 . O generalizare utilă

Acum, că am invăţat să generăm permutările mulţimii {1, 2 ••• n}, se pune


problema să vedem de ce este util acest algoritm. La ce foloseşte faptul că putem
aranja numerele {1, 2 ••• n} în toate modurile posibile?

Să observăm că acest algoritm poate fi folosit pentru a aranja oricare n


elemente distincte în toate .ţ11oduri1e posibile.

E,x: Exemple

1. Se dă o mulţime alcătuită din n litere distincte. Se cer toate cuvintele care se


pot forma cu ele,· astfel încât fiecare cuvânt să conţină n litere distincte. De
exemplu, dacă mulţimea este {a,b,c), vom avea cuvintele: abc, acb, bac,
bea, cab şi eba.

2 • Se dau numele a n persoane. Se cere să se afişeze toate modurile posibile în


care acestea se pot aşeza pe o bancă. De exemplu, dacă n:o3, iar persoanele sunt
Ioana, costel şi Mihaela, atunci soluţiile sunt:

Ioana Costel Mihaela;


Ioana Mihaela Costel;
Costel Ioana Mihaela;
•anual de informatică pentru clasa a Xl-a 213

În astfel de cazuri, cele n elemente distincte se memorează într-un vector v,


:: ş a cum vedeţi mai jos:

1 Ioana 1 Costel 1Mihaela 1


1 2 3 1 2 3

Atunci când s-a generat o permutare, de exemplu 213, vom afişa


'7[2]V [l]V[3], adică bac, în primul caz sau Costel J:oana Mihaela, în al
:loilea caz.
Procedeul de mai sus poate fi folosit pentru oricare altă aplicaţie din
în probleme cum ar fi : generarea tuturor submulţimilor unei mulţimi,
combinatorică ,
generarea aranjamentelor, a combinărilor sau a tuturor părţilor unei mulţimi.

p? Exerciţiu. Scrieţi programul care rezolvă exemplul 2.


, Mulţimea permutărilor mulţimii toate funcţiile
<11 21 ••• n} reprezintă
• bijectlve f:(1121'''1n> - H1121 ••• 1n} . De exemplu, dacă n=3,
permutarea 213 este funcţia f:{11213}-H112,3} definită astfel:
f(1)=2; f(2)=1; f(3)=3.

8.4.2. Produs cartezian

O Enunţ. Se dau n mulţimi: A1, A 2 , ... A", unde A1 =U1 2 1 ... 1 k 1 }, pentru
k==1, 2, ••• 1 n. Se cere produsul cartezian al celor n mu lţimi.

Exemplu: A1 ={1,2}, A 2 ={1,2,3}, A 3 ={1,2,3}.


A1XA2XA3 ={(
1, 1, 1) , ( 1, 1, 2 ) , ( 1, 1, 3 ) , ( 1, 2 , 1) , ( 1, 2 , 2 ) , ( 1, 2, 3 ),
(1,3, 1), (1,3,2), (1,3,3), (2,1,1), (2,1,2), (2,1,3), (2,2,1),
( 2, 2, 2) 1 ( 2, 2, 3) , ( 2, 3, 1) , ( 21 3, 2) , (2, 3, 3) } •

0 Rezolvare. De la inceput observăm că este necesar să aflşăm toate soluţiile.


Să observăm că o soluţie este de forma x 1,x2, • •• ,xD, cu x 1eA1 , x:~EA:~, ... ,
xnEAn· De aici rezultă necesitatea folosirii unui vector sol, cu n componente,
unde sol [1] va conţine numerele naturale între 1 şi k 1 , sol [21 va conţine
numerele naturale între 1 şi k 2 , ... , sol [nJ va conţine numerele naturale între 1 şi
kn· Observăm că valorile care pot fi luate sol [1] sunt între 1 şi k 1 , valorile care
pot fi luate sol [2 J sunt între 1 şi k 2 , ... , valorile care pot fi luate sol (nJ sunt între
1 şi kn. Pentru a putea reţine aceste valori, vom utiliza un vector a, cu n
componente, unde a [1] = k 1 , a [2 1= k:~ , ... , a [n] = k". Pentru exemplul dat,
vectorul a va reţine ( 2, 3, 3) .
Important! Să observăm că orice valoare reţinută de sol [il între 1 şi k 1
indeplineşte condiţiile de continuare (este validă) .
Din acest motiv, nu mal este
necesar să utilizăm subprogramul valid.
214 Capitolul 8. Metoda backtrackinc

Mai jos, puteţi observa programul care generează produsul cartezian al


mulţimilor date:

var n,i:integer; #include <iostream.h>


sol,a:array[1 .• 10] of int n, sol[lO],a[lO],i;
integer; void back(int k)
{ if (k==n+l)
procedura back(k:integer); { for (i=l;i<•n;i++)
begin cout<<sol[i];
if k=n+l then
cout<<endl; }
begin
el se
for i1•l to n do { sol[k]=O;
write(sol[i]); while(sol[k]<a[k])
writeln { sol[k]++; back(k+1); }
end )
}
el se
begin main()
sol [kJ :o• O; {
while sol[k]<a[k] do cout<<"Numarul de multimi•" 1
begin cin>>n;
sol[kl••sol[kJ+1; for(int i•l;i<•n;i++)
back(k+l) { cout<<"a["<<i<<"]•";
end cin»a[i] 1
end }
end; back(1);
)
begin
write('Numarul de multimi•');
readln(n);
for i:=l to n do
begin
wri te ( 'a [ • , i, '] •' ) 1
readln(a[i])
end;
back(l)
end.

J~· Observaţii
1. Avem k1Xk:.~x... xk,. elemente ale produsului cartezian. De aici rezultă că
algoritmul este exponenţial.

2. O altă interpretare pentru metoda backtracking: fiind date n mulţimi: A 1 , A2 , ... ,

A... produsul cartezian al lor A1XA:.~x ••• XAz. se mai numeşte spaţiul soluţiilor. În
acest context, metoda backtracking caută una sau toate soluţiile, care sunt
elemente ale produsului cartezian şi care indeplinesc anumite condiţii. Astfel, se
poate justifica faptul că, în generarea produsului cartezian, nu este necesar
subprogramul valid pentru că se generează toate elementele produsului
cartezian, fără a verifica anumite condiţii.
Manual de informatică pentru clasa a Xl-a 2 ,-
.~

Deş i algoritmul este exponenţia l , există aplicaţii utile, evident, atunci când
fiecare mulţime A1 poate lua numai câteva valori şi unde n este suficient de mic.

~ Exerciţii
1. Tabelarea anumitor funcţii. Se dă funcţia

unde fiecare mulţime A1 este dată de numerele întregi din intervalul [a 1 ,bd şi

Se cere să se realizeze un tabel, în care pentru fiecare valoare din


domeniul de definiţie să se afişeze valoarea funcţiei corespunzătoare acelei valori.

2. Scrieţi programul care generează toate "cuvintele" cu patru litere care au pdma
şi ultima literă vocale, litera a doua consoană din mulţimea {P, R, s, T} , iar a treia
1teră consoană din mulţimea {B, M, R, T, V}.

3. Scrieţi programul care generează şi numără eate cuvinte de cinci litere ale
alfabetului englez se pot forma, cu condiţia să nu existe două consoane al ăturate
şi nici două vocale alăturate .

8.4.3. Generarea tuturor submulţimilor unei mulţimi

::J Enunţ. Fiind dată mulţimea A={1 1 2, ••• ,n}, se cere să se afişeze toate
SJbm ulţimile ei.

:tl Rezolvare. Să ne amintim că submulţimile unei mulţimi A se pot reprezenta


:um vectorul caracteristic v, unde: · ·

V[i] ={1, pentru i e A


O, pentru i e A
)e exemplu , dacă A•U~2,3}, pentru submu lţimea {1,3} vom avea
..,... ( 11 o1 1 >. De aici, rezultă că problema se reduce la generarea tuturor valorilor
:osibile pe care le poate reţine vectorul caracteristic.
Aceasta inseamnă căo soluţie este de forma x1, x:~ 1 • • • 1 xn, unde
~e <o, 1}. Şi in acest caz, orice valoare ar reţine componenta i, ea nu trebuie să
--de plinească nici o condiţie de continuare, motiv pentru care subprogramul valid
- .. este necesar.

ln continuare, puteţi observa programul care generează toate valorile pe


:.are le poate reţine vectorul caracteristic:
216 Capitolul 8. Metoda backtracking

var n,i:int eger; #includ e <iostre am.h>


sol:arr ay[1 •• 10] of i nt n, sol[lO ], i ;
intege r; void back(i nt k)
proced ura back (k:inte ger); { if (k==n+ l)
begin { for (i=l;i< =n;i++ )
if k=n+1 then cout<< sol[i);
begin cout<< endl;
for i:=1 to n do }
write( sol[il) ; el se
writel n { sol[kJ= -1;
end while( sol[k]< l)
el se { sol[k]+ +;
begin back(k +l);
sol[k]: =-1; }
while sol[k]< l do }
begin }
sol[k] :=sol[k ]+1;
main()
back(k +l)
{ cout<< "n="; cin>>n ;
end back(l );
end }
end;
begin
write ( 'n=');
readln (n);
back(l )
end.

~ Exerciţii
valor.:
1. Problema nu este rezolvată in totalitate. Programul afişează numai toate
program ul s.:
pe care le poate lua vectorul caracteristic. Completaţi-! astfel incat
afişeze ţoate submulţimile mulţimii <1, 2 ••• n} 1

2. Se citesc numele a 6 e~evi. Afişaţi toate submutţimile mulţimii celor 6 elevi.

in baza ;.
3. Să se afişeze toate numerele scrise in baza 10 a căror reprezentare
cu 1. Valorile n şi k se citesc de ..i
are n cifre, dintre care exact k sunt egale
tastatură (n<12, k<n). De exemplu, pentru n:c3 şi k•2, se obţin valorile: s şi 6.

4. un program care generează combinaţii de n cifre o şi 1 cu proprieta:=.i.


Realizaţi
că in orice grup de 3 cifre consecutive există cel puţin
o cifră de 1. De exemo _
dacă nc:4, se afişează combinaţiile: 0010, 0011, 0100,
0101, 0110, 0111, 1 0 c:
1010,1 011,11 00,110 1,1110 ,1111.
mulţ•,....
5. Se citesc două numere naturale n şi s (n<10, s<lOOO ). Să se afişeze
elemen telor din fieca;
formate din n numere prime cu proprietatea că suma
mulţime este exact s.
tanual de informatică pentru clasa a Xl-a 217

,
1
Observaţii
Fiind dată o mulţime
cu n elemente, avem 2n submulţimi ale ei. Mulţimea
dat~ şi submulţimea vidă sunt submulţimi ale mulţimii datei De ce? Fiecare
componentă a vectorului caracteristic poate reţine două valori. Prin urmare,
numărul de submulţimi este

2·2·2
~
...2=2" .
uenori

De aici rezultă că algoritmul care generează toate submulţimile mulţim i• 1 ,


2 •... , n este exponenţial.

Uneori, veţi rezolva probleme in care se dă o mulţime şi se cere o submulţime


a sa care îndeplineşte anumite caracteristici. in anumite situaţii, problema se
poate rezolva prin utilizarea unor algoritmi mai rapizi (polinomiali). Greşeala
tipică care se face este că se generează toate submulţimile, după care se
selectează cea (cele) care indeplineşte condiţiile date.

Exemplu. Se dă o mulţime de numere reale. Se cere să se determine o


submulţime a sa care are suma maximă. Problema se rezolvă uşor: se
consideră ca făcand parte din submulţime numai numerele pozitive. Altfel,
dacă am genera toate submu lţimile...

4.4. Generarea combinărilor

Fiind dată mulţimea A=U~2~ ••• 1 n}, se cer toate submulţimile ei cu p


mente. Problema este cunoscută sub numele de "generarea combinărilor de
ate câte p". Se ştie că numărul soluţiilor acestei probleme este

CP = n!
n (n - p)!p!

exemplu, dacă n=4 şi pa3, soluţiile sunt următoarele:


{ 11 2 1 3} • { 11 2 1 4 } , { 11 3 1 4} şi { 21 31 4 }.

Enunţ. Se citesc n şi p numere naturale, n~. Se cere să se genereze toate


<
~m ulţimile cu p elemente ale mulţimii A• 1, 2, ••• , n}.

Rezolvare. O soluţie este de forma x 1, x 2, ••• 1 :xp, unde x 1, x:~. ..., :xpe A.
•lus, x1, x2, ... , xp trebuie să fie distincte. Cum la o mulţime ordinea elementelor
prezintă importanţă, putem genera elementele ei in ordine strict crescătoare .
~stă observaţie ne ajută foarte mult în elaborarea algoritmului.

Pentru k>1, sol [k] >sol [k-1].

~---- -r~=~- -
218 Capitolul 8. Metoda backtracking

b) Pentru fiecare ke{1,2, ••• ,p}, soltk1~-p+k. Să presupunem , prin


absurd, că această ultimă relaţie nu este respectată. Aceasta fnseamnă că 3k,
astfel încât sol [k] >n-p+k. Deci:

sol [k+l] >n-p+k+1,

sol [p] >n-p+p=n.

Absurd. De aici rezultă că:

l :S:sol [1] ~-p+l ,


sol[l]<sol[2]SD-p+2,

sol[n-1]<sol(n]~-p+p=n.

Relaţiile de mai sus simplifică mult algoritmul, pentru că ţinând cont de ele, nu mai
este necesar să se testeze nici o condiţie de continuare.

var sol:array[l .. 9] of integer; #include <iostream.h>


n,p:inteoer; int n,p,sol[lO];
procedura back(k:inteoer); void back(int k)
var i:inteoer; { int i;
beoin i f (k==p+l)
if k=p+l { for (i•lJi<•p;i++)
then cout<<sol[i]J
begin cout<<endlJ
for it=l to p do }
write(sol[i]); el se
writeln; { if (k>ll sol[k]=sol [k-l]t
end else sol[k]=OJ
el se while(sol[k]<n-p+kl
begin { sol[k]++;
if k>l then sol[k] t =sol[k-1] back(k+l); }
elee sol[k]:•O; }
)
while sol[k]<n-p+k do
beoin main()
sol[k] :=sol[k]+l; { cout<<"n•"J cin>>n;
back(k+ll; cout<<"p="; cin>>p;
end back(l) 1
end }
end;
begin
wri te ( ' n= • ) 1
readln(n);
write ( ' p= ' );
readln{p);
back(l);
end.

Examinând raţionamentul propus putem observa că, fn anumite cazuri,


ana\rz.a une\ ţ>lc'o\em~ CCŢ,\\\\lce \a \ln ~~c~~m C\l m\l\\ ma\ ra:?\\\.
c:-ual de informatică pentru clasa a Xl-a 219

.".fi Exerciţii
· Se dau coordonatele din plan a n puncte. Afişaţi coordonatele vârfurilor tuturor
::c:ratelor care au ca vârfuri puncte din mulţimea considerată.

; . Se dau n substanţe chimice. Se ştie că, în anumite condiţii, unele substanţe intră
,. ·eacţii chimice cu altele. Fiind date p perechi de forma ( i, j) cu semnificaţia că
s..~stanţa i intră în reacţie cu substanţa j, se cer toate grupurile de s<n substanţe
:sfel încât oricare două substanţe din grup nu intră în reacţie.

5.4. 5. Generarea aranjamente;pr

Se dau două mulţimi A={l,2, ••• ,p} şi B={1,2, •• • ,n}. Se cer toate
·~ncţiileinjective definite pe A cu valori în B. O astfel de problemă este una de
;-enerare a aranjamentelor de n luate câte p (A~ ).

: xemplu: p=2, n=3. Avem: 12, 21, 13, 31, 23, 32. De exemplu, 21 este funcţia
: :A-4B dată astfel: f (1) =2; f (2) =1. Avem relaţiile:

ni
A~ = · = n(n-1) ... (n-p+1).
(n - p)!

O Enunţ. Se citesc n şi p . Să se genereze toate aranjamentele de n luate câte p.


Să observăm că dacă se cunoaşte fiecare submulţime de p elemente a
m ulţimii de n elemente, atunci aranjamentele se pot obţine permutând în toate
modurile posibile elementele unei astfel de mulţimi. Pornind de la această
o bservaţie, suntem tentaţi să generăm toate submulţimile cu p elemente ale
mulţimii cun elemente şi, din fiecare astfel de submulţime, să obţinem permutările
el. Exerciţiu!

Pe de altă parte, se poate lucra mult mai eficient. O soluţie este de forma:
x1x2 ••• xp. unde x1, x:~, ... , XpEB. În plus, x 1, x:~, ... , Xp trebuie să fie distincte.
Spre deosebire de algoritmul de generare a combinărilor, aici ne interesează toate
permutările unei soluţii (acestea sunt, la rândullor, alte soluţii). Aceasta înseamnă
că nu mai putem pune în soluţie elementele în ordine crescătoare. Să recapitulăm:

- o soluţie are p numere din B;

- numerele trebuie să fie distincte.

Rezultă de aici că algoritmul este acelaşi de la permutări, diferenţa fiind dată


de faptul că soluţiaare p numere, nun ca în cazul permutărilor. ·
:
220 Capitolul 8. Metoda backtracking

.•••_ d·
Varianta C++

var sol:ar ray[1 •• 9]of intege r; #inclu de <iostre arn.h>


n,p:in teger; int n,p,so l[10];

functi on int valid (int k)


valid( k:inte ger):b oolea n; { for (int i=l;i< k;i++ )
var i:inte ger; if (sol[k l==so l[i])
begin return O;
valid: =true ; return 1;
}
for i:•l to k-1 do
if sol[k] =sol[i ] then void back( int k)
valid: =false { int i,j;
end; if (k==p+ l)
proced ura back( k:inte ger); { f or (j=l;j< =p;j++ )
var i,j:in teger ; cout< <sol[j ];
begin cout<< endl;
if k•p+l then
begin el se
for j:=l to p do for (i=l;i< =n;i++ )
write (sol[j ]); { sol[k] =i;
writel n if (valid (k))
end back(k +l);
el se
for i:=l to n do
begin main( )
sol[k ]:•i; { cin»n ;
if valid( k) then cin>>p ;
back(k +l) back( l);
end }
end;
begin
readln (n) J
readln (p);
back( l)
end.

~ Exerciţii
care se pot forma
1. Se citesc n, p şi apoi n litere distincte. Afişaţi toate cuvintele
cu p dintre ele.
numele care se
2. Se citesc n şi apoi: numele mici a n persoane. Ştiind că toate
nume de băieţi, să se afişeze
termină cu a reprezintă nume de fată, celelalte fiind
Două mulţimi sunt distincte
toate mulţimile de perechi fată-băiat care se pot forma.
lu, pentru n=S, Maria , Ana,
dacă cel puţin una dintre perechi diferă. De exemp
a-Dor u, Ana-C osmin },
Doina , Doru, Cosmi n, se afişează mulţimile: {Mari
{Doin a-Dor u,
{Ana- Cosmi n,Mar ia-Dor u}, {Mari a-Dor u,Doin a-Cos rnin},
-Cosm in}.
Maria -Cosm in}, {Ana- Doru,D oina-C osmin }, {Doin a-Dor u,Ana
Manual de informatică pentru clasa a Xl-a 221

3. Cei n acţionari ai unei firme trebuie să organizeze un număr maxim de şedinţe


tip mas~ rotund~ ta care s~ participe exact p dintre ei. Ştiind că oricare două
şedinţe trebuie să difere fie prin acţionarii prezenţi, fie prin vecinii pe care
îi au
aceştia la masă, stabiliţi numărul de şedinţe pe care le pot organiza. De exemplu,
dacă n=.O. şi p•3, atunci sun\ posibi\e s conftguraţi"l cliterite ale celor 3
acţionari
aşezaţi la masa rotundă : 1-2-3; 1-3-2; 1-3-4; 1-4-~; 2-3-4;
2-4-3
(configuraţ iile 2-3-1 şi 3-1-2 nu se consideră, deoarece sunt echivalente,
la
masa rotundă, cu configuraţia 1-2-3).

8.4.6. Generarea tuturor partiţiilor mulţimii {1, 2, ... , n}

Definiţia 8.1. Fie mulţimea A=< 1, 2, •• • , n}. Se numeşte partiţie a


mulţimii A, un set de :Jc;;n mulţimi care îndeplinesc condiţiile de mai jos:

Exemplu. Considerăm mulţimea A=< 1, 2, 3}. Avem partiţiile:


{1,2,3}
{1, 2} {3}
{1, 3} {2}
{2,3} {1}
{1} {2} {3}

D Enunţ. Se citeşte un număr natural, n. Se cer toate partiţiile mulţimii


A= { 11 2 1 ••• , n} .

0 Rezolvare. Chiar dacă ştim să generăm toate submulţimile unei mulţimi, tot nu ne
ajută să generăm toate partiţiile.

1 • Pentru a putea genera toate partiţiile, trebuie să găsim o metodă prin care să
putem reţine o partiţie. O primă idee ne conduce la folosirea unui vector, sol, astfel:
dacă sol [il •k, atunci elementul i se găseşte în mulţimea k a partiţiei. Totuşi, nu
ştim câte mulţimi sunt in partiţia respectivă . Există o partiţie care conţine n mulţimi
atunci când fiecare element este într-o mulţime şi una care conţine toate mulţimile,
adică tocmai mulţimea A. Cu alte cuvinte, numărul mulţimilor dintr-o partiţie este
între 1 şi n.

2 • Pentru a avea o ordine în generarea soluţiilor, elementele mulţimii A trebuie să


aparţină de submulţimi consecutive ale partiţiei.

-+ Din acest motiv, sol [il va lua valori între 1 şi


l+max{s ol [1], sol [2] 1 ••• 1 sol [i-1]}.
222 Capitolul 8. Metoda backtracklng

Prin această condiţie se evită situaţia in care, de exemplu, vectorul sol reţine
<1, 3, 1). Aceasta ar avea semnificaţia că elementele 1 şi 3 se găsesc in
submulţimea 1 a partiţiei, iar elementul 2 se găseşte in submulţimea 3 a partiţiei. ln
acest caz, lipseşte submulţimea 2 a partiţiei.

Să exemplificăm funcţionarea algoritmului pentru cazul n=3:

- sol=(l,1,l) - A1 =(1,2,3);

- so1=(1,1,2)- A1 =(1,2} A2 ={3};

- sol=(1,2,l) - A1 ={1,3} A2 =(2};

- sol=(1,2,2)- A1 ={1} A2 =(2,3};

- sol-::(1,2,3)- A1 ={1} A2 ={2} A3 ={3}.

~.11 Să observăm că nici in cazul acestei probleme nu trebuie să verificăm existenţa


fi .! anumitor condiţii de continuare.

Analizaţi programul astfel obţinut!


' .~ ...;;. ' .!l•y'..: ·•' . "•·•:.• " '1
Varianta Pascal r ·, •• arlanta C+-f:, ••
var sol:array[0 .• 10]of integer; #include <iostream.h>
n,i,j,max~:integer; int n, sol[10],
max[lO],i,j,maxim;
oroced",;re t.~art ..... a.~~ "'C,.~~""""C. '-"\
"l:1o-.":...-,;"
{ maxim•lJ
m.axim:=l;
for (i•2;i<•nJi++)
for i:=2 to n do
if (maxim<aol[i])
if maxim<sol[i]
maxim-sol (i] 1
then maxim1gsol[i];
cout<<"Partitie "<<endl;
writeln( 'Partitie •) 1
for (i•l;i<=maxim;i++)
for i:=1 to maxim do
{ for (j•1; j<-n;j++)
begin
if (sol[j)•uzi)
for j:a1 ton do
cout<<:l<<" ";
if sol[j]•i
cout<<endl;
then write (j, 1 1 )
1
}
writeln1
}
end;
end; void back(int k)
{ int i,j,maxprec;
procedura back (k1integer); if (k••n+l) tipar();
var i,j,maxprec;integer; else
begin { maxprec•O;
if k=n+l for (j•1;j<=k-1;j++)
then tipar if (maxprec<aol[j])
el se maxprec=sol[j];
begin for (i•1;i<=maxprec+1;i++)
maxprec:=O; { sol[k)=i; max[k]=sol[k]J
for jz=1 to k-1 do back(k+l);}
if maxprec<sol[j] then }
maxpreCI"'80l[j]; }
Manual de informatică pentru clasa a Xl-a 223

for i:=l to maxprec+l do main()


begin ( cout<<"n=";
sol [k] P•it cin>>n;
back(k+l) back(l);
end; }
end
end;

begin
write('n='); readln(n);
back(l);
end.

~ Exerciţiu. Puteţi arăta că oricărei partiţii îi aparţine un unic conţinut al


vectorului sol, obţinut ca în program?

Indicaţie. Observaţi că întotdeauna elementul 1 aparţine primei submulţimi a


partiţiei, elementul 2 poate aparţine submulţimilor 1 sau 2 ale partiţiei, ... ,
elementul n poate aparţine submulţimilor, 1, 2 sau n ale partiţiei. Pornind de aici,
construiţi vectorul sol!

Ţinând cont de faptul că oricărei partiţii îi corespunde un unic conţinut al


1
~• J·tt' vectorului sol şi oricărui conţinut al vectorului sol ii corespunde o unică
partiţie, am obţinut, practic, o funcţie bijectivă de la mulţimea partiţiilor
mulţimii A la mulţimea conţinuturilor generate de algoritm ale vectorului sol.
Pornind de la această bijecţie, în loc ca algoritmul să genereze partiţiile, el
va determina conţinuturile vectorului sol. Apoi, pentru fiecare conţinut al
vectorului sol, se obţine o partiţie .

/ Exerciţiu. Modificaţi programul precedent pentru ca acesta să afişeze toate


partiţile care conţin exact 3 submulţimi.

8. 5. Alte tipuri de probleme care se re-zolvă prin


utilizarea metodei backtracking

8.5 .1. Generalităţi

Toate problemele pe care le-am întâlnit până acum admit soluţi i care
îndeplinesc următoarele caracteristici:
-+ soluţiile sunt sub formă de vector;
-+ toate soluţiile unei probleme au aceeaşi lungime, unde prin lungime
înţelegem numărul de componente ale vectorului soluţie.
224 Capitolul 8. Metoda backtracking

Exemple. Fie mulţimea A== (1, 2 •• ·X?-}. Atunci:


1

a) Toate permutările mulţimii A au lungimea n.

b) Toate submulţimile fU p elemente ale mulţimii A (generarea combinărilor) au


lungimeap. ·

c > Toate soluţiile sub formă de vector ale problemei generării tuturor partiţii lor
mulţimii A au lungimea n.

ln realitate, cu ajutorul metodei backtracking se pot rezolva şi probleme care nu


îndeplinesc condiţiile de mai sus. Astfel, există probleme în care nu se cunoaşte de la
început lungimea soluţiei, există probleme care admit mai multe soluţii de lungimi
diferite, există probleme în care soluţia este sub forma unei matrice cu două sau trei
linii etc. Exemplele următoare vă vor convinge.

8.5.2. Generarea partiţiilor unui număr natural

O Enunţ. Se citeşte un număr natural n. Se cere să se tipărească toate modurile


de descompunere a lui ca sumă de numere naturale. De exemplu , pentru nc:4,
avem:4,31,22,211,13,121,112,1111.

f Ordinea numerelor din sumă este importantă . Astfel, se tipăreşte 11.2 dar
J;. şi 211, 121.

0 Rezolvare. De la început, observăm că nu se cunoaşte lungimea unei soluţii.


Ea poate fi cuprinsă între 1, în cazul în care numărul în sine constituie o
descompunere a sa şi n, atunci când numărul este descompus ca sumă a n
numere egale cu 1.

Trscem la stabilirea algoritmului pe care îl vom folosi.

1. Fiecare componentă a vectorului sol trebuie să reţină o valoare mai


mare sau egală cu 1.

2. Mai întâi să observăm că, în procesul de generare a soluţiilor, trebuie ca


în permanenţă să fie respectată relaţia

sol[l]+sol[2]+ ••• sol[k]~.

3. Avem soluţie atunci când

so1[1]+sol[2]+ ••• sol[k] =n.

Rezultă de aici că trebuie să cunoaştem, la fiecare pas k, suma


s= sol [1] +sol [2] + ••• sol [k-1].
Manual de inform atică pentru clasa a Xl- a
225
O primă posibilitate ar fi ca la fiecare pas să calculăm
această sumă. Dar, se
poate lucra eficient. Suma va fi reţinută în perma
nenţă intr-o variabilă globală,
numită s.

Mai jos, este prezentată funcţionarea algoritmului pentru n=4:

~~ie
[lŢo 1o o J1l1lo o 11111110
s=O,I<=l s=l,k =2 s=2,k::=3
~
s=3,k=4

~ie ~ie ~ie


s=2,k=3
2
Il 1 2 1o 1o 1 1 3
s=l,k =2 s=3, k=3 s=l,k =2

'
Observaţimodul in care ca lculăm suma la fiecare pas. De câte
ori se trece
• la componenta următoare (k+l) , las se adună sol
[kJ, de câte ori se face
pasul înapoi (se trece la componenta k-1), din s se
scade sol [kl.
Programul este prezentat în continuare:

Varia nta· Pasca l ' ' .., ·. .,. Vari a ni~ C++
var sol: array [1 .• 100] of integ er; #incl ude <iost ream. h>
n,i,s :inte ger; int sol[lO O], n,i,s ;
proce dura back (k:in teger ); void back (int k)
begin
{ if {s===n )
if s=n then begin { for (i=l;i <==k -l;i++ )
for i:=l to k-1 do cout< <sol CiJ;
write (sol[ iJ); cout< <endl ;
write ln; }
end el se
else begin { sol[k ]=O;
sol[k ]:=O; while (sol[ k]+s <n)
while sol[k ]+s<n do ( sol[k ]++;
begin s+=s ol[k] ;
sol[k ] :=sol [kl+l ; back( k+l);
s:=s+ sol[k ]; back( k+l); s-=so l[k];
s: =s-so l[k]
end; }
end }
end;
main( )
begin
{ cout< <"n=" ; cin>> n;
write {'n= ') ; readl n{n);
back (l);
back{ l) }
end.
226 Capitolul 8. Metoda backtracking

~Exerciţii
1. Cum trebuie procedat în cazul în care se cere ca soluţiile să fie afişate o singură
dată? Spre exemplu, dacă s-a afişat descompunerea 1,1, 2 să nu se mai afişeze
2 , 1,1sau1, 2,1?
Indicaţie: procedeul a mai fost întâlnit, de exemplu la generarea combinărilor.
Soluţiile se vor genera în ordine crescătoare. Modificaţi programul în acest sens.
2. Adaptaţi metoda de rezolvare astfel încât să se genereze numai partiţiile formate
din numere naturale distincte.
3. Adaptaţi metoda de rezolvare astfel încât să se genereze numai partiţiile formate
din cel puţin p numere naturale distincte (n şi p citite de la tastatură).
4. Adaptaţi metoda de rezolvare astfel încât să se genereze numai partiţiile formate
din numere naturale aflate în intervalul [a, bl (n, a şi b citite de la tastatură).
5. Rezolvaţi problema scrierii numărului natural n ca sumă de numere naturale
alese dintr-o mulţime formată din k valori date {v1, v2, ... , vk}. Astfel, 10 se
poate scrie ca sumă de numere alese din mulţimea {2,3,6} în felul următor:
10=2+2+2+2+2, 10=2+2+3+3,10=2+2+6.

8.53. Plata unei sume cu bancnote de valori date

O En unţ. Se dau suma s şi n tipuri de monede având valori de a 1 ,a2 , • •• ,a0 lei. Se
cer toate modalităţile de plată a sumei s utilizând aceste monede. Se presupune
că se dispune de un număr nelimitat de exemplare din fiecare tip de monedă.

lată soluţiile pentru Suma=5, n=3 (trei tipuri de monede) cu valorile 1, 2, 3:

1) 1 de 2, 1 de 3; 2) 1 de 1, 2 de 2 ; 3) 2 de 1, 1 de 3;
4) 3 de 1, 1 de 2; 5) 5 de 1;

0 Rezolvare. Valorile celor n monede sunt reţinute de vectorul a. Astfel, a [1] va


reţine valoarea monedei de tipul 1, a[2] valoarea monedei de tipul 2, ş.a.m.d.
Numărul de monede din fiecare tip va fi reţinut de vectorul sol . Astfel, sol [1] va
reţine numărul de monede de tipul 1, sol [2] va reţine
numărul de monede de tipul 2, ş.a. m .d . 1n aceste condiţii, o sol 2 O 1
ffiffij
soluţie pentru exemplul anterior arată ca alăturat, unde suma 5 a 1 2 3
se formează cu două monede de 1 şi o monedă de 3.

.1•·1..~· Ce observăm?
22 /
,,.anual de informatică pentru clasa a Xl-a
ă situaţie corespunde
torului sol care reţin o. Aceast
1. Există componente ale vec ul. Din ace st motiv, fiecare
vă nu este luată fn calc
:az ulu i în care mo ned a respecti are aflată înaintea tuturor celo
r
componentă a vectorului
sol va fi iniţializată cu o valo
oosibile, adică cu -1.
de tipuri de monede).
componente (n este numărul
2. Orice soluţie are exa ct n t luate în calcul.
edele, chiar şi cele care nu sun
Acest număr include toate mon
manenţă sum a obţinută
la un
rioară, vom reţine în per
3. Ca şi la pro blem a ante
, avem la dispoziţie suma:
moment dat. Astfel, la pasul k
l[k -1]
ol[ 2] + • • • + a[k -l] *so
s • a(l ]*s ol[ l] + a(2 ]*s

4. Avem soluţie dacă:


a[n ]*s ol[ n] =S um a
s = a[l ]*s ol[ l] + a[2 ]*s ol[ 2] + ••• +

prin desene, ca la problema ante


rioară, modul
~ Exerciţiu. lncercaţl să arăttruaţi,sum a.. s şi n•3 .
de obţinere a tuturor soluţiilor
pen
gramul:
În continuare, este prezentat pro
~· ..·c··l·.'· ·,~~ .,.~~" ........
.· f· 1a......
:· .,, ,·;·.Y~ )\!:'!: ..
.. ~' .:• ..... ,,;;,. ' ~ '' ++ ~ · · ..1 'irr
. ·"·',~. . Pascal· ......., "·': .'' :..', ·~: 1
'', 1 , '•"1 . :1!.~··· Varianta
1

#in clu de <io stre am .h>


var sol ,a: arr ay[ 1 •• 100 ) int sol [lO O], a(lO O),
of int ege r; n,i ,s,s um a;
n,i ,s,S um a:in tag arJ
r); voi d bac k(i nt k)
pro ced ura bac k (k: int ega { if (s=:aSUJIIa)
beg in { cou t<< "So 1ut ie "<< end ll
if s•su ma the n for(i•l;i<~k-1;i++
)
beg in if( sol [i] )
wr ite ln( •So lut ie• ); cou t<< sol [i]< <"m one de de
"
for i:= l to k-1 do <<a [i]< <en dl;
if sol [i]< >O the n cou t<< end l;
wr ite ln (so l[i] ,• mon eda }
de ',a [il ) ;
el se
wr ite ln;
{ sol (kJ :o-1 ;
end wh ile( sol [k] *a[ k]+ s<S um a
el se &:&: k<n +l)
beg in {
sol [k] :=- 1; sol ,(kl ++;
a)
wh ile (so l[k) *a[ k]+ s<S um s+a sol (k] *a( k];
and (k< n+l ) do
bac k(k +l) ;
beg in s-" .so l [)tl *a[ k];
sol [k] :=s ol[ k]+ l;
}
s:= s+s ol[ k)* a[k );
}
bac k(k +1) ;
}
s:= s-s ol[ k]* a[k l
end ; mai n ()
end { cou t<< "gu mac "; cin> >Su ma;
end ; cou t<< "n• "; cin >>n;
228 Capitolul 8. Metoda backtracking

begin for (i=l;i <=n;i ++)


write ('sum a='); readln (suma ); { cout< <"a["< <i<<" ]=";
write ('n=') ; readl n(n); cin>> a[i];
for i:=l to n do }
begin back( l);
write {'a[• ,i,'l= '); }
readl n(a[i ]);
end;
back( l)
end.

~ Exerciţiu. Adaptaţi rezolvarea problemei plăţii


unei sumei cu bancnote date
cunoscând, în plus, pentru fiecare valoare ai numărul
lim ită bi de bancnote cu
valoarea respectivă.. disponibile. Astfel, pentru s=100 , a=<
2, 5, so), b= <10, 6, 3) ,
varinata s=l.Ox 5+l.x5 0 nu corespunde cerinţei deoarece
nu avem la dispoziţie 10
monede de 5, ci doar 6.

8.5.4. Problema labir intul ui

O Enunţ. Se dă un labirint sub formă de matrice cu m linii şi n coloane. Fiecar


e
element al matricei reprezintă o cameră a labirintului.
într-una din camere, de
coordonate lin şi col, se găseşte un om. Se cere să se
găsească toate ieşirile
din labirint. Nu este permis ca un drum să treacă de două
ori prin aceeaş i cameră .
O primă problemă care se pune este precizarea modului de codificare a
ieşirilor din fiecare cameră a labirintului.

Fie 1 (i, j) un element al matricei. Acesta poate lua valori


între o şi 15. Se
consideră ieşirile spre nord, est, sud şi vest, luate fn această ordine. Pentru
fiecare
direcţie cu ieşire se reţine 1, iar in caz contra
r, se reţine o. Un şir de patru cifre :.
sau o formează un număr in baza 2. Acest număr este
convertit în baza 10 ş.
reţinut în 1 <i, j ) . De exemplu, pentru o camer
ă care are ieşire în nord şi ve s~
avem 1001m =9(10 l·

Exem plu. Alăturat este prezentat un labirint. Acolo


unde nu este permisă trecerea dintr-o cameră în alta, se 15 11., "10 1 if 14
marchează cu o linie oblică. De asemenea,
matricea 111 1~ 114 ~ 1)
reţine şi valorile corespunzătoare ieşirilor, aşa
cum sunt ...7
ele cerute de program. 11.- " ...6 15

0 Rezol vare

1. O cameră vizitată se reţine prin coordonatele ei:


lin (linia) şi col (colana) .
Din acest motiv, pentru a reţine un traseu vom utiliza o matric
e cu două coloane şi
mai multe linii: sol. De exemplu, dacă camera iniţială
este cea de coordonate
( 2, 2 > o soluţie este <2 , 2 >, ( 2, 3 ). ( 1, 3 ) .
Manual de informatică pentru clasa a Xl-a 229

2. Nu toate soluţi ile au aceeaşi lungime, întrucât există trasee de lungime d iferită.
Se obţine o soluţie atunci când coordonatele camerei unde s-a intrat sunt în afara
matricei (nu au linia între 1 şi m şi nu au coloana intre 1 şi n). Evident, atunci când
s-a găsit o situaţie, aceasta se afi şează.
3• Spunem că o cameră este accesi bilă dacă există intrare din camera curentă
către ea. Atenţie la modul (vedeţi programul) Tn care testăm dacă o cameră este
accesibilă sau nu. Este o operaţie in care se testează conţin u tul unui anumit bit.
Acesta se obţine efectuâ nd un ŞI logic intre dou ă valori. De exemplu, dacă testăm
i eş irea spre sud , atunci efectuăm ŞI logic între 0010( 21=2<lo> şi valoarea reţinută
în matrice pentru camera curentă. Dacă valoarea obţinută este diferită de o, atunci
avem ieşire din camera cu rentă către sud.
4 . Înainte de a intra într-o cameră accesibilă se testează dacă respectiva cameră
a mai fost vizitată sau nu. Pentru aceasta utilizăm funcţia vizitat. ln caz că a
fost vizitată, se face pasul inapoi.
Analizaţi programul:

Varianta Pa~cal
var sol : arr ay [1 •• 100,1 •. 2] of # include <iostream.h>
integer; int sol [100] [ 2] , 1 [10] [10],
l:array [0 .• 10,0 . • 10] of m,n, i,j,lin,col;
integer; i nt vizitat ( int k, int lin,
m,n,i , j,lin,col:intego r; int col)
function vizitat (k,lin, [ int v • O;
col:integer):boo lean; for (i.,l;i<=k;i++)
begin if (sol [i] [O]==lin &&
vizitat: = false; sol [i] [l]a=col) val;
for i: =l to k-1 do 1 return v;
if (sol[i,l]=lin) and }
(sol[i,2]=col)
void tipar (int k,i nt lin,
then vizitat:=true; int col)
end; { cout <<" Solutie "<<end l. ;
procedura for (i.,1 ; i<=k-1;i++)
tipar(k,lin,col:i nteger); cout<<sol[i] [0] <<" "
begin <<sol [i] [1] <<e ndl;
writeln('Solutie '); if ( lino:n•O)
for i: =1 to k-1 do cout<<"iesire prin nord"
writeln(sol [i , 1],' •, <<endl ;
sol[i,2 ]); el se
if lin.. o then i f (lin==m+l)
writeln('ie sire prin nord ' ) cout<<"iesire prin sud"
el se <<endl;
if lin=m+l then el se
wri teln ( • iesire pri n sud' ) if (col==O)
el se cout<< "iesire prin vest"
if col= O t:hen <<endl ;
writeln(' l esire prin vest') el se
el se cout<< "iesire prin est"
writeln( • iesire prin est '); <<endl;
readln; }
end;
230 Capitolul 8. Metoda backtracking

procedura void back(int k, int lin,


back{k,lin,col:i nteger); int col)
var i : integer; { if (lin==O 11 lin==m+1 11
begin col==O 11 col==n+1)
if (lin=O) or {lin~+1) or tipar(k,lin~col);
{col=O) or {col=n+1) el se
then tipar{k,lin,col) {
el se sol [k] [0) =lin;
begin sol[k] [l)acol;
sol[kl1]:=lin;
for (int i=1;i<"'4 1i++)
sol[k~2J:=col;
switch(i)
for i:=1 to 4 do
{
case i of
1:if (l[lin,col] case 1:
and 8<>0) and not if {l[lin] [col] & 8 &&
vizitat(k, lin-11col) 1 vizitat(kl lin-1,col))
then back(k+1,lin-1,c ol)1
back(k+1,lin-1~col); break;
2:if {l[lin,col] case 2:
and 4<>0) and not if (l[lin] [col] & 4 &&
vizitat(kllinlcol +1) 1 vizitat(k, lin,col+l))
then back(k+lllin,col +l);
back(k+11lin~col+1)1 break;
J:if (l[lin,col) case 3:
and 2<>0) and not if (l[lin] [col] & 2 &&
vizitat{kllin+11 ool) 1 vizitat(k 1 lin+l,col))
then back(k+l,lin+l,c ol);
back(k+1 1lin+1,col); break;
41if (l[lin 1 col] case 4:
and 1<>0) and not if (l[lin] [col] & 1 &&
vizitat(k,lin,co l-1) 1 vizitat(k, lin 1Col-1))
then back(k+l,lin,col -1);
baok(k+1,lin,col -1) break1
end; {oase} }
end }
end;
begin main()
write( 'M• •) 1 { cout<<"M•";
readln (m) 1 cin>>m1
write( 'N• •) 1 cout<<"N•";
readln(n) 1 cin>>nl
for i:•1 to m do for (i=l1i<=m1i++)
for :1:=1 ton do for(:l•ll:l<=n;:l++ )
begin { cout<<"l["<<i<<" ,"
wri te ( ' l [ • 1 i, ' 1 ' , :1 1 ' J=' ) 1 <<:!<<")•";
readln(l[i 1 :1]) cin»l [il [j];
end1 }
wri te ( • lin.. ' ) 1 cout<<"Linie=";
readln (lin); cin»lin;
write{ 'col'"') 1 cout<<"Coloana.. ";
readln(col)l cin>>col;
back(11lin~col) back(1,lin,col);
end.
\1anual de informatică pentru clasa a Xl- a 231

? Intrebare. Cum s-ar putea găsi un drum de lungime minimă de la camera


• iniţială către ieşirea din labirint? Prima idee care ne vine in minte este să
9 enerăm toate ieşirile, ca in program, pentru a o putea depista pe cea de lungime
m inimă . Ei bine, răspunsul nu este satisfăcător. Nu u itaţi, tehnica backtracking
n ecesi tă un timp exponenţial. Problema se poate rezolva cu mult mai eficient. Dar,
pentru asta, trebuie să studiem teoria grafurilor. Toate la timpul lor...

~ Exerciţii
1. Adaptaţi rezolvarea pentru un labirint Tn care fiecare căsuţă reţine valoarea 1
sau o (1 semnificând căsuţă plină , prin care nu se poate trece, iar o căsuţă liberă ,
pe unde se poate trece). Ca şi in problema prezentată , deplasarea se poate face
dintr-o căsuţă in orice altă căsuţă alăturată, orizontal sau vertical , cu condiţia ca ea
să existe şi să fie l iberă. Val idaţi poziţia iniţială a omului (lin, col), astfel încât
aceasta să coresp undă unei căsuţe libere. Esti maţi spaţi u l de memorie utilizat
in această vari a ntă.

2. Realizaţi o variantă a rezolvării de la 1, adăugând câte o linie sau coloa nă


s uplimentară pe fiecare margine a labirintului, astfel încât să nu se mai testeze ca
celula in care se trece să existe. Plasarea într-o celulă de pe margine este
echivalentă cu obţinerea unei soluţii.

8.5.5. Problema bilei

O Enunţ. Se dă un teren sub formă de matrice cu m linii şi n coloane. Fiecare


element al matricei reprezintă un subteren cu o anum ită altitudine dată de valoarea
reţinută de element (număr natural). lntr-un astfel de subteren, de coordonate
(lin, col) se găseşte o bilă . Ştiind că bila se poate deplasa Tn orice porţiune de
teren aflată la nord, est, sud sau vest, de altitudine strict inferioa ră porţiunii pe care
se găseşte bila. Se cere să se găsească toate posibi lităţile ca bila să părăsească
terenul.
6 8 9 3
Exemplu. Fie terenul a l ătu rat. Iniţial, bila se află în subterenul
de coordonate ( 2, 2). O posibilitate de ieşire din teren este dată 9 7 6 3
de drumul: (2,2), (2,3), (3,3), (3,4) . În program, 5 8 5 4
altitudinile subterenului vor fi reţinute de matricea t.
8 3 7 1

0 Analiza problemei şi rezolvarea ei. Problema seamănă cu cea anterioară,


deci putem gândi că dacă inlocuim testul de intrare Tntr-o cameră cu cel de
altitudine mai mică, am rezolvat-o! Este adevărat , se obţine o rezolvare, dar se
poate şi mai uşor. Să analizăm : mai este necesar să testăm dacă bila nu a ajuns
pe un teren pe unde a mai trecut? Nu, deoarece, la fiecare pas, bila se deplasează
pe un teren de altitudine strict inferioară . Prin urmare, problema este mai uşoară
decât precedenta.
232 Capitolul 8. Metoda backtracking

În rest, vom propune o rezolvare în care sol este o matrice cu 3 coloane şi


un număr mare de linii. Astfel, sol (k, 1) va reţine direcţia în care pleacă bila (1 pt
nord, 2 pentru est, 3 pentru sud şi 4 pentru vest), sol (k, 2) va reţine linia
subterenului, iar sol (k, 3) va reţine coloana subterenului.

~ Exerciţiu. Arătaţi, prin reprezentare grafică, modul de funcţionare a


algoritmului, pe baza structurii de date propusă.

Varianta Pascal . · ·
• l' • 1
.. ~·, · · :.. ' · ...;·~ Varianta C+;+- , , .' ·, i, 1,.. :.!··
var sol:array [1 •• 100,1 •• 3] #include <iostream .h>
of integer1
t:array [0 •• 10,0 •• 10] int sol[100] [3],t[10] [10],
of integer; m,n,i,j,l in,col1
m,n,i,j,l in,col:in teger;
procedur a t ipar(k:in teger); void tipar(in t k)
begi n { cout<<"S olutie "<<endl;
writeln( 'Solutie •); for(i=l;i< =k-1;i++ )
for i:=1 to k-1 do cout<<so l [i] [1] <<" "
writeln( sol [i, 2],' ', <<sol[i] [2]<<end l;
sol[i,J] )I
end; void b a ck(int k, i n t lin, int col)
procedur a { if (linu=O 1 1 lin==m+1 11
back(k,l in,col:in teger); col==O 1 1 col==n+1 )
begin tipar(k) l
if (lin=O) or (lin•m+1 ) or el se
(col=O) or (col•n+1) { sol[k] [0 ]=0;
then tipar(k) sol [k) [1] =lin;
olse begin sol[k] [2) r:ocol;
sol[k,1]: =0; while (sol[k] [0)<4)
sol [k,2] :=lin; (
sol[k,J) :=col1 sol[k) [0]++;
while sol[k,1]< 4 do switch(s ol [k] [0])
begin {
sol[k,1J: =sol(k,1 ]+11 case 1:
case sol[k,l] of if(t[lin- 1] [col]<t[ lin] [col])
1:if t[lin-1,c oll< back(k+ l,lin-l,co l); break;
t[lin,co l] then case 2:
back(k+ 1,lin-1,c ol); i f(t[lin) [col+l]< t[lin] [col])
2:if t[lin,col +1]< back(k+ l,lin,col +l); break;
t[lin,co l] then case 3:
back(k+1 ,lin,col+ 1) J if(t[lin +l l [col)<t[l in] [col])
3:if t[lin+1,c ol]< back(k+1 ,lin+1,co l ); break;
t[lin,co l) then case 4:
back(k+ l,lin+1,c ol)J if (t [lin] [col -1] <t [lin] [col])
4:if t[lin,col -1]< back(k+ l,lin,col -1); break;
t[lin,co l) then )

_j
back(k+1 ,lin,col- 1); }
end1 {case} }
end end end; }
begin main ( )
write('M =')1 readln(m ); { cout<<"m ="; cin>>m;
write('N =')1 readln(n );
~~ cout<<"n ="; cin>>n;
~
Vanual de informatică pentru clasa a Xl-a 233

for i:=1 to m do for (i=l;i<=m;i++)


for j:=1 ton do for (j=1;j <=n;j++)
begin { cout<<"t["<<i<<" ,"<<j<<"l=";
write('t[•,i, •, •,j, cin»t [il [j ];
' ] = .); }
readln(t[i,j]) cout<<"lin~•; cin>>lin;
end; cout<<•col="; cin>>col;
write(•lin=');re adln(lin); back(1,lin,col);
write('col=');rea dln(col); }
back(1,lin,col)
end.

p? Exerciţiu. Modificaţi programul astfel încât să se afişeze şi direcţia în care


bila părăseşte terenul.

8.5.6. Săritura calului

O Enunţ. Se consideră o tablă de şah nxn şi un cal plasat 1 16 11 20 3


in colţul din stânga, sus. Se cere să se afişeze o posibilitate 10 21 2 17 12
de mutare a acestei piese de şah, astfel încât să treacă o 15 24 19 4 7
si ngură dată prin fiecare pătrat al tablei. Alăturat, observaţi o 22 9 6 13 18
soluţie pentru n=5. 25 14 23 8 5

0 Analiza problemei şi rezolvarea ei. Fiind dată o poziţie în care se găseşte


calul, acesta poate sări in alte 8 poziţii. Pentru a scrie mai puţin cod, cele 8 poziţii
sunt reţinute de vectorii constanţi x şi y . Astfel, dacă lin şi col sunt coordonatele
poziţiei unde se găseşte calul, acesta poate fi mutat in:

(lin+x [O ] , col.+y [O] ) • • . (lin+x [7], col+y [7 l ) .

Reţineţi acest procedeu! De altfel, acesta poate fi folosit pentru problemele


deja prezentate. Matricea t reţine poziţiile pe unde a trecut calul. fn rest, s-a
procedat ca la problema anterioară .

Varianta ~ascal ., . '


Varianta C++ .• 1
'
const x:array [1 •• 8] of #include <iostream .h>
integer=(-1,1,2, 2,1,-1,-2,-2); #include <stdlib .h>
y:array[l. . 8] of
integer=(2,2,1,- 1,-2,-2,-1,1); const int x[S]={-1,1,2,2,1 ,-1,
var n:integer; -2,-2};
st: array[1 •• 1000,1 .• 2] of const int y[8]={2,2,1, - 1,-2,-2,
integer; -1,1};
t: array[-1 .• 25,-1 •• 25] of
integer; int n,sol[1000] [2],t[25] (25];

procedura back(k, lin, void back ( int k,int lin,


col:integer); int col)
var i,linie,coloana: integer; { int linie,coloana,i;
234 Capitolul 8. Metoda backtracking

begin if (k=-n*n)
if k=n*n { for (i=l;i<=k-l;i++)
then cout<<sol[i] [0]<<" "
begin <<sol[i] [l]<<endl;
for i:=1 to k-1 do cout<<lin<<" "<<col;
writeln(st[i,1], ' ' exit(EXIT_SUCCES S);
st[i,2]); }
writeln(lin,' •,col); el se
halt; { sol[k] [O]=lin;
end sol[k] [l]=col;
el se for (i=O;i<=7;i++)
begin { linie=lin+x[i];
st[k,1] :=lin; coloana=col+y[i] ;
st[k,2] :=col; if (linie<=n && linie>=1
for i:=1 to 8 do
&& coloana<=n &&
begin
coloana>=l &&
linie:=lin+x[i];
t[linie] [coloana]==Ol
coloana:=col+y[i ];
{
if (linie<=nl and
t[linie] [coloana)=l;
(linie>=l) and
back(k+l,linie,co loana);
(coloana<=n) and
(coloana>=1) and t[linie] [coloana]=O;
}
(t[linie,coloana]g O)
}
then
}
begin
}
t[linie,coloana] :=1;
back(k+l,linie,
main()
coloana) 1
t [linie,coloana]:a O; { cout<<"n=";
end; cin>>n;
end back(1,1,1);
e .n d
end;

begin
write ('n=');
readln(n);
back(1,1,l);
end.
Vanual de informatică pentru clasa a Xl-a 235

Probleme propuse

Avem la dispoziţie 6 culori: alb, galben, roşu , verde, albastru şi negru. Să se


precizeze toate drapelele tricolore care se pot proiecta, ştiind că trebuie
respectate regulile:

• orice drapel are culoarea din mijloc galben sau verde;


• cele trei culori de pe drapel sunt distincte.

2. Dintr-un grup de n persoane, dintre care p femei, trebuie formată o delegaţie


qe k persoane, din care 1 femei. Să se precizeze toate delegaţiile care se
pot forma.

3. La o masă rotundă se aşează n persoane. Fiecare persoană reprezintă o


firmă. Se dau k perechi de persoane care aparţin unor firme concurente. Se
cere să se determine toate modalităţile de aşezare la masă a persoanelor,
astfel7ncat să nu stea alături două persoane de la firme concurente.

4. Se dă o permutare a primelor n numere naturale. Se cer toate permutările care


se pot obţi ne din aceasta astfel incat nici o succesiune de două numere,
existentă în permutarea iniţială, să nu mai existe în noile permutări.

5. Se cer toate soluţiile de aşezare în linie a m câini şi n pisici astfel încât să nu


existe o pisică aşezată între doi câini.

6. Anagrame. Se citeşte un cuvânt cu n litere. Se cere să se tipărească toate


anagramele cuvântului citit. Se poate folosi algoritmul pentru generarea
permutări lor?

7. Se dau primele n numere naturale. Dispunem de un algoritm de generare a


combinărilor de n elemente luate câte p pentru ele. Se consideră un vector cu
n componente şiruri de caractere, unde, fiecare şir reprezintă numele unei
persoane. Cum adaptaţi algoritmul de care dispuneţi pentru a obţine
combinări l e de n persoane luate câte p?

8. Se citesc n numere naturale distincte. Se cere o submu lţime cu p elemente


astfel încât suma elementelor sale să fie maximă 7n raport cu toate
submulţimile cu acelaşi număr de elemente.

9. Să se determine s numere de câte n cifre, fiecare cifră putând fi 1 sau 2, astfel


încât oricare dintre aceste s numere să coincidă exact in m poziţii şi să nu
existe o poziţie în care să apară aceeaşi cifră în toate cele s numere.
236 Capitolul 8. Metoda backtracking

10. Fiind dat un num~r natural pozitiv n, se cere s~ se producă la ieşire toate
descompunerile sale ca sumă de numere prime.

11. "AttUa şi regele". Un cal şi un rege se află pe o tablă de şah. Unele câmpuri
sunt "arse", poziţiile lor fiind cunoscute. Calul nu poate călca pe câmpuri "arse",
iar orice mişcare a calului face ca respectivul câmp să devină "ars" . Să se afle
dacă exist~ o succesiune de mutări permise (cu restricţiile de mai sus), prin
care calul să poată ajunge la rege şi să revină la poziţia iniţială. Poziţia iniţială
a calului, precum şi pozi ţi a regelui sunt considerate "nearse".

12. Se dau n puncte în plan prin coordonatele lor. Se cer toate soluţiile de unire a
acestor puncte prin exact p drepte, astfel încât mulţimea punctelor de
intersecţie ale acestor drepte să fie inclusă în mulţimea celor n puncte.

13. Găsiţi toate soluţiile naturale ale ecuaţiei 3x+y+4xz= 100.

14. Să se ordoneze in toate modurile posibile elementele mulţimii {1 1 21 • • • 1 n}


astfel încât numerele i, i+l, ... , i+k să fie unul după celălalt şi în această
ordine (1=1 1 i+Jc;n).

15. Se consideră o mulţime de n elemente şi un număr natural k nenul. Să se


calculeze câte submulţimi cu k elemente satisfac, pe rând, condiţii l e de mai jos
şi să se afişeze aceste submulţimi.

• conţin p obiecte date;


• nu conţin nici unul din q obiecte date;
• conţin exact un obiect dat, dar nu conţin un altul;
• conţin exact un obiect din p obiecte date;
• conţin cel puţin un obiect din p obiecte date;
• conţin r obiecte din p obiecte date, dar nu conţin alte q obiecte date .
16. Se dă un număr natural par N. Să se determine toate ş iruril e de N paranteze
care se închid corect.
Exemplu: pentru N=6: < C ( ) ) ) 1 ( () () ) 1 () () () 1 () ( () ) 1 ( () ) ().

17. Se dau N puncte albe şi N puncte negre în plan, de coordonate intregi. Fiecare
punct alb se uneşte cu câte un punct negru, astfel încât din fiecare punct, fie el
alb sau negru, pleac~ exact un segment. S~ se determine o astfel de
configuraţie de segmente astfel încât oricare două segmente să nu se
intersecteze. Se citesc N perechi de coordonate corespunzând punctelor albe
şi N perechi de coordonate corespunzând punctelor negre.

18. Să se genereze toate permutările de N cu proprietatea că oricare ar fi 2Si~.


există 1SjSi astfel încât

Ivei> -vCj > 1=1.


vanual de informatică pentru clasa a Xl-a 237

Exemplu: pentru N=4, permutările cu proprietatea de mai sus sunt:


2134, 2314, 3214, 2341, 3241, 3421, 4321.

' 9. O trupă cuN actori işi propune să joace o piesă cu A acte astfel încât:

• oricare două acte să aibă distribuţie diferită;


• in orice act există, evident, cel puţin un actor;
• de la un act la altul, vine un actor pe scena sau pleacă un actor de pe
scenă (distribuţia a două acte consecutive diferă prin exact un actor).

Să se furnizeze o soluţie , dacă există vreuna.

20. Fiind dat un număr natural N şi un vector v cuN componente întregi, se cere:

• să se determine toate subşirurile crescătoare de lungime [N/5];


q să se calculeze p(l)+p(2)+ ••• +p(k), unde p(k) reprezintă numarul
subşiruri lor crescătoare de lungime k.

21. Pe malul unei ape se găsesc c canibali şi m misionari. Ei urmează să treacă


apa şi au la dispoziţie o barcă cu 2 locuri. Se ştie că, dacă atât pe un mal, căt
şi pe celălalt avem mai mulţi canibali decât misionari, misionarii sunt mâncaţi
de canibali. Se cere să se scrie un program care să furnizeze toate soluţiile de
trecere a apei, astfel încât să nu fie mâncat nici un misionar.

22. Se dă un careu sub formă de matrice cu m linii şi n coloane. Elementele


careului sunt litere. Se dă, de asemenea, un cuvânt. Se cere să se găsească
în careu prefixul de lungime maxima al cuvântului respectiv. Regula de căutare
este următoarea:

a) se caută litera de început a cuvântului;

b) litera următoare se caută printre cele 4 elemente învecinate cu elementul


care conţine litera de început, apoi printre cele 4 elemente învecinate cu
elementul care conţine noua literă, ş . a . m . d.

23. Se dau coordonatele a n puncte din plan. Se cere să se precizeze 3 puncte


care determină un triunghi de arie maximă . Ce algoritm vom folosi?

a) Generarea aranjamentelor; b) Generarea combinărilor;

c) Generarea permutărilor; d) Generarea tuturor submulţimilor.

24. Fiind date numele a n soldaţi , ce algoritm vom utiliza pentru a lista toate
grupele de câte k soldaţi? Se ştie că într-o grupă, ordinea prezintă importanţă .

a) Generarea aranjamentelor; b) Generarea combinărilor;

c > Generarea permutărilor; d) Generarea tuturor partiţiilor.


238 Capitolul 8. Metoda backtracking

25. Fiind date n numere naturale, ce algoritm vom utiliza pentru a determina
eficient o submulţime maximală de numere naturale distincte?

a) se generează toate submulţimile şi se determină o submulţime maximală


care îndeplineşte condiţia cerută;
b > se generează toate partiţiile şi se caută o submulţime maximală care
aparţine unei partiţii oarecare şi care îndeplineşte condiţia cerută;

c) se compară primul număr cu al doilea, al treilea, al n-lea, al doilea cu al


treilea, al patrulea, al n-lea, ... şi atunci când se găseşte egalitate se e limină un
număr dintre ele.

d) nici un algoritm dintre cei de mai sus.

26. Dispunem de un algoritm care generează permutările prin backtracking.


Primele două permutări afişate sunt: 321, 312. Care este următoarea
permutare care va fi afişată?

a) 321; b) 123; c) 213; d) 231.

Indicaţii

6. Deşi
algoritmul este asemănător, nu este acelaşi, trebuie pusă o condiţie
suplimentară. De exemplu, in cuvântul "mama" nu se poate inversa a de pe poziţia
2 cu a de pe poziţia 4.

7. Dacă vectorul care reţine numele persoanelor este v, în loc să se afişeze i, se


va afişa V[i].

23. b)

24. a)

25. d)
Explicaţie: primele două variante prezintă soluţii exponenţiale, a treia este în
o (nl >. Dar dacă sortăm numerele, atunci le putem afişa pe cele distincte dintr-o
singură parcurgere. Sortarea se poate efectua in o (nXlog (n) ), iar parcurgerea
în O(n). Prin urmare, complexitatea este O(nXlog(n)).

26.d)

Explicaţie: pe fiecare nivel al stivei se caută succesorii în ordinea n, n-1, ... , 1.


Capitolul 9
Introducere În teoria grafurilor

9.1. Grafuri neorientate

9.1 .1. Introducere


Uneori, algoritmii trebuie să prelucreze date referitoare la anumite
elemente între care există anumite relaţii. Să analizăm exemplele următoare:

1. Se dau n oraşe. Unele dintre ele sunt unite prin şosele directe (care nu mai
trec prin alt oraş).
2. Se cunosc relaţiile de prietenie dintre n persoane.
3. Se dau n ţări şi se cunoaşte relaţia de vecinătate între ele.
4. Se dau n triunghiuri, iar unele dintre ele sunt asemenea.

Pentru fiecare dintre aceste exemple se poate imagina o reprezentare


graficăcare să exprime relaţiile existente.
-+ Convenim ca fiecare element să-I numim nod sau vârf.
Astfel, în cazul 1. nodul este oraşul, în cazul 2. nodul este persoana, în
cazul 3. nodul este ţara şi în cazul 4. nodul este triunghiul. Convenim ca un nod
(vârf) să-I notăm cu un cerculeţ în care să înscriem numărul lui (de la 11a n).

-+ Relaţia existentă între două noduri o vom reprezenta grafic unindu-le


printr-un segment de dreaptă. Convenim ca un astfel de segment să-I numim
muchie şi dacă ea uneşte nodurile i şi j, s-o notăm cu ( i, j).

În cazul 1., muchia (i,j) are


semnificaţia că între oraşele i şi j există o şosea
directă. În cazul 2., muchia <i, j) are
semnificaţia că persoanele i şi j sunt prietene, în
cazu 1 3 • muchia <i, j > are semnificaţia
că ţările i şi j sunt vecine, iar în cazul 4 • , că
triunghiurile i şi j sunt asemenea.

Procedând aşa, obţinem o descriere grafică


precum cea din figura 9.1 şi convenim ca o astfel
de reprezentare s-o numim graf neorlentat. Figura 9.1.
Exemplu de graf neorlentat
240 Capitolul 9. Introducere în teoria grafurilor

Desigur, această abordare este intuitivă. Teoria grafurilor este fundamentală


matematic şi în cele ce urmează ne propunem s-o prezentăm sistematic.

9.1.2. Definiţia grafului neorientat

Definiţia 9.1.1 . Un graf neorientat este o pereche ordonată G= (V, E) .


unde:

-+ v = {v1 , v 2 , ••• vn> este o mulţime finită şi nevidă . Elementele


mulţimii v se numesc noduri (vârfuri).
-+ E este o mulţime finită
de perechi neordonate de forma (v1 , vj) ,
unde i:;tj, şi v 1 ,vjev. Elementele mulţimii E se numesc muchii.
Semnificaţia unei muchii este aceea că uneşte două noduri.

Un graf poate fi desenat aşa cum se observă


în exemplul următor (vezi figura 9.2.), unde
G={V,E),

V= {1,2 1 3 ,4,5,6};
E = {(1,2), (1,3), (1,5), (2,3), (3,4),
(4,5)}

Notaţie: în graful G= (V, E), vom nota


cu n numărul nodurilor şi cum numărul
muchiilor. Figura 9.2.
Alt exemplu de graf neorientat

Observaţii

./ Două noduri distincte pot fi unite prin cel mult o muchie. În exemplul de
mai sus, <1., 2) este muchia care uneşte nodul 1 cu nodul 2. Dacă scriem
(2,1.), ne referim la aceeaşi muchie (perechea este neordonată) .

./ Nu există o muchie care uneşte un nod cu el însuşi (o muchie uneşte


două noduri distincte).

Definiţia 9.2. În graful G=(V,E), nodurile distincte v 1 ,vjEG sunt


adiacente dacă există muchia (v11 vj) EE.
Vom spune că muchia (vi, vj) EE este incidentă la nodurile vi şi vj.

În exemplul dat anterior, nodurile 1. şi 5 sunt adiacente, dar nodurile 2 şi s nu


sunt adiacente. Muchia ( 4, 5) este incidentă la nodurile 4 şi 5.

Definiţia este restrictivă, în unele lucrări veţi întâlni definiţii mai puţin restrictive, de
1

exemplu, poate exista o muchie de la un nod la el însuşi sau nu se cere ca mulţimea


nodurilor să fie finită.
\llanual de informatică pentru clasa a Xl-a 241

Definiţia 9.3. Într-un graf neorientat, prin gradul unui nod v se înţelege
numărul muchiilor incidente cu nodul v şi se notează cu d(v). Un nod
cu gradul o se numeşte nod izolat, iar unul cu gradul 1 se numeşte nod
terminal.

În exemplul dat, d(2 >=2, d( 1) =3, iar d( 6) =O (6 este nod izolat).

O relaţie utilă: fie un graf neorientat cun noduri şi m muchii. Dacă notăm cu d1,
dl .... , a., gradele celor n nodu,·i, atunci avem relaţia:-

" d1 +d 2 +d 3 +.~d-:-=2m.l
~ Demonstraţie: fiecare muchie face să crească gradele celor două noduri la
care este incidentă cu câte o unitate. Prin urmare, se obţine re l aţia anterioară.

Pentru a înţe l ege bine noţiunile prezentate în acest paragraf, ne vom referi la
exemplele din 9 .1.1:

- fie gradul nodului i este k. Pentru exemplul 1., ea are


afirmaţia:
semnificaţia că din oraşul i pleacă (sosesc) k şosele, pentru exemplul 2., are
semnificaţia că persoana i are k prieteni, pentru exemplul 3., are semnificaţia că
ţara i se învecinează cu k. ţări , iar pentru exemplul 4., are semnificaţia că pentru
triunghiul i se cunosc k triunghiuri asemenea. Aici trebuie făcută observaţia că ar
putea să existe şi alte triunghiuri asemenea cu el, dar modul în care putem afla
aceasta va fi tratat separat.
- fie afirmaţia: nodurile i sunt adiacente. Pentru exemplul 1., ea are
şi j
semnificaţia că oraşele i sunt unite printr-o şosea care nu trece prin alte
şi j
oraşe, pentru exemplul 2., are semnificaţia că persoanele i şi j sunt prietene,
pentru exemplul 3., are semnificaţia că ţările i şi j sunt vecine, iar pentru
exemplul 4. , are semnificaţia că triunghiurile i şi j sunt asemenea.

- fie afirmaţia: nodul i este izolat. Pentru exemplul 1., înseamnă că nu există nici
o şosea care leagă oraşul i cu alt oraş, pentru exemplul 2 ., înseamnă că
persoana i nu are nici un prieten, pentru exemplul 3 ., înseamnă că ţara i nu se
învecinează cu nici o ţară (este situată pe o in su lă), pentru exemplul 4., înseamnă
că nu există nici un triunghi dintre celelalte n-1 triunghiuri care să fie asemenea cu
triunghiul i.

~ Exerciţiu
Daţi un exemplu inspirat din viaţa reală, pentru care să găsiţi graful asociat.
Astfel, veţi răspunde la întrebările: ce semnificaţie a~ nodurile sau muchiile şi ce
înseamnă gradul unui nod.
242 Capitolul 9. Introducere în teoria grafurilor

9.1.3. Memorarea grafurilor

În acest paragraf, prezentăm principalele structuri de date prin care grafurile pot
fi memorate în vederea prelucrării lor. De la început, precizăm faptul că vom alege o
structură sau alta în funcţie de2 :
a) algoritmul care prelucrează datele referitoare la graf;

b) memoria internă pe care programul o are la dispoziţie;

c) dacă graful conţine multe muchii sau nu.

Pentru fiecare structură de date pe care o vom folosi, vom avea câte o
procedură (funcţie) care citeşte datele respective. Toate aceste subprograme se
găsesc grupate în unitatea de program grafuri .pas (pentru Pascal) şi în
grafuri. cpp (pentru C++). Vom fi astfel scutiţi ca, pentru fiecare program pe care îl
realizăm, să fim nevoiţi să adăugăm liniile de cod necesare citirii ş i ne permite să ne
concentrăm exclusiv asupra algoritmului.

. Toate subprogramele pe care le utilizăm citesc datele dintr-un fişier text, în


({~ • care, pe prima linie vom scrie numărul de noduri (n), iar pe următoarele linii,
· • câte o muchie ( i, j), ca în exemplul de mai jos, în care este prezentat un
graf şi liniile fişierului text care este citit pentru el:

0 Fişierul text:
1
1
1
3
5
2

2 3
3 4
Figura 9.3.
4 5
Exemplu de graf neorientat

Trecem la prezentarea structurilor prin care putem memora datele referitoare la


un graf.

A. Memorarea grafului prin matricea de adiacenţă

A.. ... - o matrice pătratică, unde elementele ei, ai,j au semnificaţia:

a .. =
I,J
{1,O, pentru {i, j) e E
pentru (i, j) ~ E

2
Modul de alegere a structurii il veţi inţelege pe parcursul studiului acestui capitol.
~ianual de informatică pentru clasa a Xl-a 243

Pentru graful din figura 9.3, matricea de adiacenţă este prezentată în


continuare:

o o 1
1 1 o
1 o 1 o o o
1 1 o 1 o o
A6.6 =
o o 1 o 1 o
1 o o 1 o o
o o o o o o
(:1, Observaţii

1 . Întrucât, din modul în care a fost definit graful, rezultă că nu există muchii de la un
nod la el însuşi, rezultă că elementele de pe diagonala principală reţin o:

a;,; =0, "r:/ie {1,2, ... ,n}.

2 • Matricea de adiacenţă este simetrică:

a;, 1 = a 1.;, "r:/i,j E {1,2, ...,n} .

Evident, deoarece muchia ( i, j >coincide cu muchia <j 1 i >.


3 • Suma elementelor de pe linia i, ie { 1, 2, . .• 1 n}, are ca rezultat gradul nod~ lui
i , d ( i), pentru că astfel se obţine suma nodurilor j, j e <1, 2, ••• , n}, pentru care
există muchie de la i la j, adică suma muchiilor incidente lai.

De exemplu, pentru graful de mai sus, suma elementelor de pe linia 1 este 3,


adică gradul nodului 1, d ( 1).

4. Tot aşa, suma elementelor de pe coloana j, j e {1 1 2, ... , n}, are ca rezultat


gradul nodului j, d(j).

1 Raţionamentul este asemănător cu cel de la observaţia precedentă.

s . Suma tuturor elementelor matricei de adiacenţă este, de fapt, suma gradelor


tuturor nodurilor, adică dublul numărului de muchii (2m).

6 . Dacă graful citit are un număr mic de muchii, atunci matricea de adiacenţă este o
formă ineficientă de memorare a lui, pentru că ea va reţine o mulţime de o.

Subprogramele pe care le vom utiliza pentru această modalitate de memorare


sunt prezentate în continuare:
244 Capitolul 9. Introducere în teoria grafurilor

type mat_ad=array[l.. 50, 1.. 50] of void CitireN


integer; (char Nume_fis[20],
procedura CitireN int A[SO] [50], int& n)
(NUme_Fis:string; {
var A:Mat_ad; var n:integer); int i,j;
var f:text; fstreamf (Nume_fis, ios:: in);
i,j:byte; f>>n;
begin while ( f»i»j)
Assign(f,Nume_Fis); A[i] [j]=A[j] [i]=1;
Reset(f); Readln(f,n); f.close() 1
while(not eof(f)) do }
begin
readln(f,i,j);
A[i,j]:=l; A[j,i] :=1;
end;
close(f);
end;

Programul următor citeşte datele referitoare la un graf şi afişează matricea


sa de adiacenţă:

#include •grafuri.cpp"
int A[50] [50],n;
n,i , j:integer; main()
begin {
CitireN( • Graf. txt • ,A, n); CitireN("Graf . txt",A,n);
for i:=1 to n do for (int i=1;i<-n;i++)
begin { for (int ja1;j<• n;j++)
for j:=1 ton do cout<<A[i] [j]<<• •;
write (A[i,j],• ') ; cout<<endl;
writeln; }
end; }
end.

B. Memorarea grafului prin liste de adiacenţă


Listele de adiacenţă reprezintă o altă formă de memorare a grafurilor, în ca:e
pentru fiecare nod se cunoaşte lista nodurilor adiacente cu el. De exemplu, pentr_
graful din figura 9.4., listele de adiacenţă sunt:

0 1 -> 2,3,5
2 -> 1 , 3
3 -> 1,2,4
4 - ~ 3,5
5 -> 1,4
6 ->
Figura 9.4.
Manual de informatică pentru clasa a Xl-a ~45

În acest caz, se uti lizează un vector cu n componente, pe care îl vom numi


Start şi o matrice T cu 2 linii şi 2m coloane. Semnificaţii le sunt:
Start - pentru fiecare nod i , start [i] specifică coloana din T unde
începe lista nodurilor adiacente cu i. Dacă reţine o, înseam nă că nodul i nu
are noduri adiacente.
• T (o, i > - reprezintă un nod al listei nodurilor adiacente.
• T <1 , i) - reprezintă indicele coloanei din T unde se găseşte următorul
element din listă. Dacă reţine o, atunci acesta este ultimul elerment din lista
succesorilor.
1 2 3 4 5 6

Start 15 17 19 l11l12l O 1
1 2 3 4 5 6 7 8 9 10 11 12
T[O] 2 1 3 1 5 1 3 2 4 3 5 4
T [1] o o 1 o 3 o 2 4 8 o 10 6

4 Exemplu de utilizare

Dorim să vedem care este lista nodurilor adiacente cu nodul 3. Start [3] =9,
adică lista începe în coloana 9 a matricei T. Primul nod adiacent cu nodul 3 este 4.
Următorul se gc':!lseşte la indicele a. Următorul nod adiacent cu nodul 3 este nodul 2.
Următorul se găseşte la indicele 4. Acesta este nodul 1 . El este ultimul din listă,
pentruT(1,4)=0 (T[l] [4)).

În continuare, vom arăta modul în care se construiesc listele de adiacenţă .


Pentru fiecare muchie (i, j) se completează două coloane ale matricei T, pentru că
trebuie înregistrat faptul că i este adiacent cu j şi j este adiacent cu i. Astfel,
vom pune pe j ca prim element în lista nodurilor adiacente lui i şi pe j ca prim
element in lista nodurilor adiacente cu i.

Con siderăm primul caz, în care vom pune pe j, ca prim element în lista
nodurilor adiacente cu i. Variabila k, care are iniţial valoarea o, va reţine indicele
ultimei coloane din T completată. O astfel de operaţie se realizează în patru paşi:

1 . Pentru că trebuie completată o nouă coloană înT, se incrementează k;


2. T (o , k) va reţine j, pentru că succesorul lui i este j;
3 • T ( 1, k > va reţine Start [ i], pentru că indicele coloanei în care se găseşte
primul nod adiacent (până la această nouă introducere, când devine al doilea din listă)
este în Start [ i 1 ;
4. Start [il va reţine k, pentru că, de acum, primul nod din lista nodurilor
adiacente cu i, este j, care se găseşte în coloana de indice k.
246 Capitolul 9. Introducere în teoria grafurilor

Mai jos, puteţi observa subprogramele care construiesc listele de adiacenţă


(funcţiile au fost adăugate în grafuri .pas şi respectiv, grafuri . cpp):

type void Citire_~static


lista=array[O •• l , l •• SO]of integer; (char Nume_fis[20],
pornire=array(l •• SO] of integer; int T[2] [50],
int Start[SO], int& n)
procedura Citire_LA~static {
(Nume_fis:string;var T:Lista; int i,j,k=O;
var Start:pornire;var n:integer); fstream
var i,j,k:integer; f(Nume_fis,ios :: in);
f1text; f>>n;
begin while (f»i»j)
k:=O; { k++;
Assign(f,Nume_Fis); Reset(f); T[O] (k] =j;
Readln(f,n); T[l] (k]=Start(i];
while(not eof(f)) do Start[i]•k1
begin k++;
readln(f,i,j); k:=k+l; T[O] [k]=i;
T[O , k]J•j; T[l,k] :•Start[i]; T[l] [k] =Start [j l 1
Start!il:=k; k:•k+l; Start[j]=k1
T[O,k]:=i; T[l,k] :=Start(j]; }
Start[j):=k; f .close () 1
end; }
close(f);
end.

.~ -
!
':- . ~~·
Programul următor citeşte datele despre un graf şi afişează, pentru fiecare
..... . nod, lista nodurilor adiacente:

uses grafuri; #include "grafuri.cpp"


var Start:pornire; T:lista1 int T[2] [50],Start(50],
n,i,man:integer; n,i,ma.n;
begin main()
Citire_~static {Citire_~static
('Graf.txt',T,Start,n); ("Graf.txt",T,Start,n);
for i1=l to n do for (int i=l1i<• n1i++)
begin {
writeln ('Noduri adiac. cu •,i); cout<<"Noduri adiac. cu "<<
man: ...start[i]; i«endl;
while (man<>O)do man•Start(i]1
begin while (man)
write(T[O,man],• '); { cout<<T (0] (man] <<" "1
man:aT(l,man] 1 ma.n=T(ll (man]; }
end; cout<<endl;
writeln }
end; }
end.
Manual de informatică pentru clasa a Xl-a 247

c. Memorarea grafului prin lista muchiilor

Se utilizează un vector cu m componente, unde m este numărul muchiilor.


Fiecare componentă va reţine cele două noduri la care muchia respectivă este
incidentă şi, în anumite cazuri alte informaţii referitoare la muchia respectivă.
Reţineţi că există algoritmi pentru grafuri care prelucrează datele pornind de la
această reprezentare (vedeţi arbori parţiali). Uneori, va fi necesar să sortăm
muchiile, fie după nodul de pornire, fie după altă informaţie asociată lor.

Mai jos, puteţi observa cum se descrie un vector (v) care reţine muchiile
unui graf:

type muchie=record struct muchie


x,y:integer; { int x,y;
end; };
var V:array[1 .. 50) of muchie; muchie V[50];

~Exerciţiu. Realizaţi un subprogram care citeşte şi memorează datele


referitoare la muchiile unui graf memorat prin lista muchiilor.

~~..i : Observaţie foarte Importantă


.~

Uneori, nodurilor unui graf li se asociază anumite informaţii. De exemplu,


dacă nodurile unui graf reprezintă oraşe, pentru fiecare astfel de nod se poate
memora numărul obiectivelor turistice. În astfel de cazuri, pe lângă una dintre
metodele de memorare prezentate mai sus, vom asocia grafului un vector cu n
(numărul de noduri ale grafului) componente, unde fiecare componentă va reţine
informaţiile referitoare la nodul respectiv. Pentru exemplul dat, fiecare componentă
a grafului reţine numărul de obiective turistice.

9.1 .4. Graf complet

Să considerăm mulţimea elevilor unei clase. Teoretic, oricare doi elevi din
clasă se cunosc. Pentru a transpune în limbaj specific teoriei grafurilor această
situaţie, vom considera că fiecare elev este un nod al grafului. Pentru că oricare doi
elevi se cunosc, înseamnă că oricare două noduri sunt unite printr-o muchie.
Astfel, am obţinut un graf aparte, pe care-I vom numi graf complet.

Definiţia 9.4. Prin graf complet vom înţelege un graf neorientat în care
oricare două noduri sunt adiacente. Vom nota un graf complet prin K", unde
n este numărul de noduri ale grafului.
248 Capitolul 9. Introducere în teoria grafurilor

;3. ·,~.· Alăturat, aveţi un graf complet cu 4 noduri (K,):


~:;i
'
: ~~

Figura 9.5.
Exemplu de graf complet

Relaţii utile:

1. Într-un graf complet, gradul oricărui nod este n-1. Evident, din fiecare nod,
pleacă (sosesc) n-1 muchii.
n(n -1)
2. Într-un graf complet, avem relaţia: m = , unde m este numărul de
2
muchii, iar n, numărul de noduri.

)!. Demonstraţie: fiecare muchie uneşte 2 noduri. Numărul muchiilor va fi egal


cu numărul de submulţimi cu 2 elemente ale mulţimii celor n noduri.
Acest număr este C2 = n(n - J) .
n 2
n(n - 1)
3. Avem 2- 2- grafuri neorientate cu n noduri.

··· )!. Demonstraţie: am văzut că numărul maxim de muchii pe care le poate avea
~ un graf neorientat este:

n(n - 1)
., 2
.r
şi corespunde unui graf complet. Se ştie că, fiind dată o mu~ime A cun elemente,
avem 2n submulţimi disjuncte ale acesteia (aici este inclusă şi submulţimea vidă
şi A). Prin urmare, avem:
n(n - 1)
2- 2-
submulţimi ale numărului maxim de muchii. Ori, fiecărei submulţimi de muchii îi
corespunde un graf nemientat, pentru că nodurile sunt aceleaşi.

9.1.5. Graf parţial, subgraf

Definiţia9.5. Un graf parţial al unui graf neorientat dat G= (V1 E) este un


graf G1=(V1 E1), unde E1!::E·
Manual de informatică pentru clasa a Xl-a 249

f, Un graf parţial al unui graf dat, este el însuşi sau se obţine din G prin
• suprimarea anumitor muchii. Priviţi exemplul de mai jos:

Figura 9.6.
Obţinerea unui
graf parţial

--->
rezultă

G={V,E)

4 Exemple din viaţa reală


1. Fiind date n persoane, între unele dintre ele există o relaţie de prietenie. Asociem
acestei situaţii un graf G. După un timp, unele persoane se ceartă. În teoria grafurilor,
aceasta înseamnă că în G se suprimă anumite muchii şi astfel, se obţine un graf
parţial G1.

2. Fiind date n oraşe, unele dintre ele sunt unite printr-o şosea directă (care nu mai
trece prin alte oraşe). Asociem situaţiei date un graf G. Datorită precipitaţiilor, anumite
şosele se inundă şi nu mai pot fi utilizate. Aceasta înseamnă că 1'n G se suprimă
anumite muchii şi se obţine un graf parţial G •.

Intrebare: câte grafuri parţiale are un graf neorientat cu n noduri? Indicaţie:


?• Câte submulţimi are mulţimea muchiilor {1 , 2, ••• ,m}?

lli· Definiţia 9.6. Un subgraf al unui graf neorientat G.., .<v, E) este un graf
:.I!.) G1 .. (V11 E1), unde v1cv. E1c::E, iar muchiile din E 1 sunt toate muchiile din
' E care sunt incidente numai la noduri din mulţimea v 1 .

( ., Un s~bgraf al unui graf G este el însuşi sau se obţine din G prin suprimarea
• anumttor noduri şi a tuturor muchiilor incidente cu acestea. Priviţi exemplul de
mai jos:

Figura 9.7.
Obţinerea unui
subgraf

--->
rezultă

G= (V,E)
250 Capitolul 9. Introducere în teoria grafurilor

4 Exemple din realitate

1. Se dau n firme. între unele din acestea se stabilesc relaţii de colaborare. Asociem
situaţiei date un graf G. intre timp, anumite firme se desfiinţează . Aceasta inseamna
că în G vom elimina anum ite noduri şi muchiile incidente lor, obţinând un subgraf al
luiG, G1.

2 • Mai multe calculatoare (n) sunt legate în reţea cu ajutorul unor cabluri. Asociem
situaţiei date un graf G. Între timp, anumite calculatoare se defectează. Astfel, se
obţine un subgraf al lui G, G1 .

? Întrebare: câte subgrafuri are un graf neorientat cu n nodu~i? lnd.icaţle: care


·• este numarul de submulţimi ale mulţimii {1, 2, ••. , n}? lntrucat mulţimea
nodurilor, v, este nevidă, vom face abstracţie de mulţimea vidă.

9.1 .6. Parcurgerea grafurilor neorientate

Prin parcurgerea grafurllor înţelegem o modalitate de vizitare a nodurilor


3

acestuia. Parcurgerea eficientă a grafurilor este esenţială în teoria grafurilor,


deoarece o mulţime de algoritmi consacraţi au la bază o astfel de parcurgere.
Din acest motiv, în acest paragraf nu vom insista pe aplicaţ~le parcurgerilor şi ne vom
mărgini numai la prezentarea algoritmilor de parcurgere. In paragrafele următoare,
veţi gasi multe exemple utile, în care parcurgerea grafurilor joacă un rol fundamental.

Există două modalităţi generale de parcurgere şi anume: parcurgerea în


lăţime (BF) şi parcurgerea în adâncime (DF). Acestea vor fi tratate separat.

9.1 .6.1. Parcurgerea în lăţime (BF - Breadth First)

-+ Parcurgerea în lăţime se face începând de la un anumit nod i, pe care îl


considerămvizitat.

-+ Vizităm apoi toate nodurile adiacente cu el • fie ele j 1, j 2, ... jk, vizitate în
această ordine.

-+ Vizităm toate nodurile adiacente cu j1, apoi cu j2 .... , apoi cu jk,

-+ Parcurgerea continuă în acest mod până când au fost vizitate toate nodurile
accesibile.

3
În acest paragraf vom exemplifica parcurgerile doar in cazul grafurilor conexe. Cum
noţiuneanu a fost prezentată până în acest moment. precizăm doar că vom exemplifica
parcurgerea grafurilor în care oricare două noduri sunt "legate• printr-o succesiune
de muchii.
Manual de informatică pentru clasa a Xl-a 251

Exemple de parcurgeri BF ale aceluiaşi graf, pornind de la noduri diferite:

Pentru graful alăturat avem:

Nod pornire 1: 1 3 6 2 7 5 4
Nod pornire 3: 3761254
Nod pornire 6: 6 3 1 7 2 5 4

Figura 9.8.

~ Parcurgerea BF se efectuează prin utilizarea structurii numită coadă, având grijă


ca un nod să fie vizitat o singură dată. Atunci când un nod a fost introdus în coadă
se marchează ca vizitat. Coada va fi alocată prin utilizarea unui vector.

Algoritmul este următorul:

Nodul de pornire este introdus în coadă şi este marcat ca vizitat.


Cât timp coada este nevidă
Pentru nodul aflat la începutul cozii:
• se trec în coadă toate nodurile adiacente cu el, care nu au fost vizitate şi
se marchează ca vizitate;
• se afişează;
• se extrage din coadă.

Vom utiliza următoarele notaţii:

i_c - indicele primei componente a cozii;


a_ c - indicele ultimei componente a cozii;
coada - vectorul care memorează coada propriu-zisă;
s - vector ce reţine nodurile vizitate:

0, noduli nu a fost vizitat


s[i] ={
1, noduli a fost vizitat

fn continuare, vor fi prezentate două exemple de implementări ale


parcurgerii în lăţime (BF) a unui graf, memorat prin liste de adiacenţă şi
matrice de adiacenţă:
252 Capitolul 9. Introducere în teoria grafu rilor

1. Programul următor parcurge BF un graf memorat prin liste de adiacenţă:

Varianta C++ "

uses grafuri; #incl ude "grafuri.cpp"


var n,i_c,sf_c,p:integer; int n, coada[50],s(50],i_c=1 ,
coada,s:array[1 •• 50] of sf_c=1,T[2] [50],Start[50],p;
integer;
T:lista; void bf ( int nod)
Start:pornire; { coada[i_ c]=nod;
s[nod]=1;
procedura bf(nod:integer); while (i_ c<=sf_ c)
begin { p=Start[coada[i_c]];
i_c : =1;sf_c:=1; while (p)
coada[i_c]:=nod; { i f (s[T[O] [p]]==Ol
s[nod] :=1; {sf_ c++;
while i_c<=sf_c do coada[sf_ c]=T[O] [p];
begin s [T [0] [p]] =1;
p:=Start[coada[i_ c]]; }
while p<>O do p=T [1] [p];
begin }
if s[T[O,p]J=O then cout<<coada[i_c]<<endl;
begin i _c++;
sf_c:=sf_ c+1; }
coada[sf_c]:=T[O,p]; }
s[T[O,p]) :=1;
end; main( l
p:=T[1,p]; {
end; Citire_LA_.Astatic("Graf. txt",T
writeln(coada[i_c]); ,Start,n) 1
i_c:=i_ c+1; bf(6);
end }
end;
begin
Citire_LA_Astatic('Gra f.txt',T,
Start,n);
bf(J);
end.

2. Programul următor parcurge BF un graf memorat prin matricea de adiacenţă:

'1 varlanta · ·'r,1, ,,''!i1il:i~:,


·: Pas~a. • ..:· l·i·
: .• ;j:ii~.:- Varianta C++ :
1, ;1:
.
uses grafuri; #include "grafuri.cpp"
var n,i_c,sf_c,i:integer;
coada,s:array[1 •• 50] of int n, coada[SO),s[SO],i_c=l,
integer; sf_ c=1,A[50] [50] ,i;
Azmat_ad;
void bf(int nod)
procedura bf(nod:integer); {
begin coada[i_c]=nod;
i _c:=1; sf_c:=l; s[nodl=l;
lanual de informatică pentru clasa a Xl-a 253

coada [i_c:) :-nod; while (i_c<=sf_c:)


s[~odl :=1; { i=l;
whi le (i_c<=sf_c:) do while (i<=n)
bagin {if (A[coada[i_c)] [i]s=l
i:=l; &&: s[iJ=",O)
while i<•n do { sf_c++;
bagin coad.a[sf_cJ=i;
if ((A[c:oada[i _c:] ,il=l) s[il=l;
and (s [i l =O)) }
then i++;
bag in }
sf_c::=sf_c:+l;; c:out<<coada [i_c:]<<endl;
c:oada[sf_c):=i; i_c:++;
a[i]:•l; )
end; }
i:•i+l;
end; main()
writeln(c:oada[i_c])l ( CitireN("Graf.txt",A,n);
i _c:ai_c+l; bf(l);
}
end
end;

bag in
CitireN('Graf.txt' , A, n);
bf(l);
end.
1

9.1.6.2. Parcurgerea în adâncime (OF - Depth First)

Parcurgerea în adâncime se face începând de la un anumit nod i, pe care îl


considerăm vizitat.
După vizitarea unui nod, se vizitează primul dintre nodurile adiacente,
nevizitate încă, apoi următorul nod adiacent, până când au fost vizitate toate
nodurile adiacente cu el.

Parcurgerea continuă în acest mod până când au fost vizitate toate nodurile
accesibile.

:x: Exemple de parcurgeri DF ale aceluiaşi graf, pornind de la noduri diferite:

Pentru graful alăturat, avem:


Nod pornire 1: 1 3 7 6 2 5 4
Nod pornire 3: 3 7 6 1 2 5 4
Nod pornire 6 : 6 3 7 1 2 5 4

Figura 9.9.
254 Capitolul 9. Introducere în teoria grafurilor

Exemple de implementări ale parcurgerii OF

1. Programul următor parcurge DF un graf memorat prin liste de adiacenţă:

uses grafuri; #include "grafuri . opp"


var n:integer; int s[SO],n;
s:array[l •• 50] of integer; T [2] [50] , Start [50];
T:lista;
void df (int nod)
Start:pornire;
{ int p;
prooedure df(nod:integer); cout<<nod<<" " 1
var p:integer; p=Start [nod] 1
begin s[nod]=1;
writeln(nod,' '); while (p)
p:=Start[nod]; { if (s[T[O] [p]J==O)
s[nod] :=1; df(T[O] [p] );
while p<>O do p=T [1] [p] 1
begin }
if s[T[O,p]]=O }
then df(T[O,p]);
p:=T[1,p]; main()
{
end
end; Citire_LA..JI,static ("Graf. txt",
T,Start,n) 1
begin df {1) i
Citire_LA_Astatio('Graf.txt',T, }
Start,n);
df(1);
end.

2. Programul următor parcurge DF un graf memorat prin matricea de adiacenţă:

usas grafuri; #include "grafuri.opp"


var n:integer; int s[50],A[50] [50],n;
s:array[l •• 50]of integer;
void df_ r{int nod)
A:mat_ad;
{ int k;
procedura df_ r(nod:intagar); oout<<nod<<" "1
var k:integar1 s[nod]=l;
bagin for (k• 1;k<=n;k++)
write (nod, • ') 1 if (A[nod] [k] ==1 &:&: s [kJ =•O)
s[nod]:=1; df_r(k) 1
for k:=1 to n do
if (A[nod,k]=l) and (s[k]=O)
then df_r(k); main()
end; { CitireN( "Graf. txt" ,A,n) 1
df_r(1);
begin }
CitireN( 'Graf.txt• ,A,n);
df_r(1);
end.
Manual de informatică pentru clasa a Xl-a 255

9.1.6.3. Estimarea timpului necesar parcurgerii grafurilor

~ Parcurgerea BF (sau DF) a grafurilor, pornind de la matricea de adiacenţă , se


face în O(n2 ). Practic, pornind de la un nod i, se caută pe linia ia matrice;
toate nodurile adiacente cu el şi pentru toate cele găsite se procedează în mod
analog.

~ Parcurgerea BF (sau DF) a grafurilor pornind de la listele de adiacenţă se face


în o (m). Pornind de la un nod i, se caută toate nodurile adiacente cu el, dar
acestea se găsesc deja grupate în lista asociată nodului respectiv şi numărul
lor corespunde numărului de muchii incidente acestuia. Algoritmul va selecta,
pe rând, toate muchiile, de unde rezultatul de mai sus.

9.1. 7. Lanţuri

Reluăm exemplele de la paragraful 9 .1.1.

Pentru exemplul 1, Întrebarea este: fiind date două oraşe a şi b, se poate


ajunge cu maşina din a în b?
Pentru exemplul 2. O persoană, a, află o informaţie importantă. Persoana
transmite informaţia tu~uror prietenilor, aceştia, la rândullor, transmit informaţia tuturor
prietenilor lor, ş.a.m.d. Intrebarea este: informaţia ajunge la persoana b?
Pentru exemplul 4. Fiind date două triunghi uri, a şi b, sunt ele asemenea? Să
observăm că nu este obligatoriu ca să se fi introdus de la început faptul că triunghiul a
este asemenea cu triunghiul b. Această informaţie poate fi dedusă, de exemplu, prin
faptul că a este asemenea cu k, k cu 1 şi 1 cu b.

Analizând aceste întrebări pe graful asociat fiecărei situaţii în parte, ajungem la


concluzia că în toate cazurile trebuie să existe o succesiune de noduri de la
nodul a la nodul b cu proprietatea că oricare două noduri sunt adiacente. Dacă
această succesiune nu există, răspunsul este negativ, altfel răspunsul este pozitiv.
Sau, exprimat în teoria grafurilor, aceasta înseamnă că răspunsul depinde de
existenţa unui lanţ de la a la b. De acum, putem prezenta definiţia lanţului.

Definiţia 9.7. Pentru graful neorientat G= (V, E), un lanţ L=[v1 ,v2 ...vp]
este o succesiune de noduri cu proprietatea că oricare două noduri vecine
sunt adiacente, adică (v1 ,va)EE, (v;r,v3)EE, ... , (vp_1 ,vp)EE. De
altfel, un lanţ poate fi definit prin succesiunea de muchii (v1 , va) EE, (v2 , v 3 ) EE, ... ,
(Vp-ltVp)EJ!!.

-+ Vârfurile v 1 şi vP se numesc extremităţile lanţului.


-+ Numărul p-1 se numeşte lungimea lanţului. Acesta este dat de numărul de
muchii ce unesc nodurile lanţului.
256 Capitolul 9. Introducere în teoria grafurilor

Definiţia 9.8. Se numeşte lanţ elementar un lanţ care conţine numai noduri
distincte.

Exemple: pentru graful din figura alăturată:

1. [1, 2, 5) este un lanţ elementar cu lungime 2 ,


între nodurile 1 şi 5 .
2. [1,3,4,1,2) este un lanţ (care nu este
elementar) de lungime 4, între nodurile 1 şi 2.

Figura 9.1 O.

O Problema 9.1. Fiind dat un graf şi două noduri ale sale a şi b, să se scrie un
program care decide dacă între ele există un lanţ sau nu, iar în caz că acest lanţ
există, se cere să se afişeze lanţul.

0 Rezolvare. Există un lanţ de la a la b, dacă şi numai dacă o parcurgere DF sau


BF, care porneşte de la nodul a , va ajunge să viziteze nodul b. Răm ân e de rezolvat
problema modului în care reţinem lanţul de la a la b . Să ob se rvăm că fiecare metodă
de parcurgere a grafului, pornind de la un nod j , se l ectează un altul i, dacă cele
două noduri sunt adiacente şi dacă nodul i este vizitat pentru prima dată. Pentru a
reţi ne selecţi ile astfel efectuate, vom utiliza un vector T, iar elementele acestuia au
semnificaţia următoare:

. {j,
T[1] =
dacă i este descendent al lui j
O, dacă i nu a fost selectat

mai observăm că un nod este selectat o singură dată, deci, în final, T va
reţine, pentru fiecare nod i, nodul j de la care a fost selectat. Pentru nodul de la care
a pornit parcurgerea (a) vom avea T (a) = O, pentru că acest nod nu a fost selectat de
algoritm. De aici, rezultă că drumul poate fi reconstituit, pornind de la T, astfel: se
afişează b, apoi T [b) , apoi T [T [b) ) ... până când se obţin e valoarea o. Pentru ca
drumul să nu fie afişat în ordine inve rsă faţă de modul în care a fost obţinut ,
subprogramul refac care îl reconstituie şi îl afişează este recursiv. Programul de mai
jos afişează drumul, pornind de la matricea de adiacenţă a grafului:

;i;:;,i!li:·liliiii1 1~:!Ya.r~a.nta Pascal ·•1:.-~d. ;: ''


' Varianta C++ ,,
uaes grafuri; #include "grafuri . cpp"
var n,al,bzinteger; int
a,Tzarray[l •• SO] of integer; s[SO] ,A[SO] [50] , n,T[SO] , a,b;
A:mat_ ad;
void refac (int nod)
procedura refac (nod:integer) ; {
begin i f (nodi =O)
if nod<>O then {
begin
refac (T[nod] );
write(nod,' 1 );
refac (T[nod]) ;
cout<<nod< <" 1 ...
}
end }
end;
\Aanual de informatică pentru clasa a Xl-a 257

procedure df_r(nod:integer); void df_r(int nod)


var k:integer; {
begin int k;
s[nod]:=l; s[nod]=l;
for k:=l to n do for (k=l;k<=n;k++)
if (A[nod,k]=l) and (s(k]=Ol if (A[nod] [k) ""''1 && s [k] :a• O)
then {
begin T[k]=nod;
T[k]:=nod; df_r(k);
df_r(k); }
end }
end;
main()
begin {
CitireN('Graf.txt',A,n); CitireN("Graf.txt",A,n);
write('a='); readln(al); cout<<"a="; cin>>a;
write('b='); readln(b); cout<<"b="; cin>>b;
df_ r(al); df_r(a);

,
if (T[b]<>O) then refac(b); if (T[b]taO) refac(b);
end. }

Observaţii foarte importante


1. Să observăm că vectorul T poate fi folosit pentru a obţine lanţuri de la nodul a la
oricare alt nod al grafului.
2• Dacă refacem rezolvarea prin utilizarea parcurgerii în lăţime, vom observa că
lanţu l obţinutare lungimea minimă . Prin natura ei, parcurgerea BF selectează nodurile
în ordinea "depăl1ării" lor faţă de nodul de la care a început parcurgerea. Astfel, la
inceput se vizitează primul nod (a), apoi nodurile pentru care lungimea lanţului de la a
la ele este 1, apoi nodurile pentru care lungimea lanţului de la a la ele este 2, ş.a.m.d.

Programul care urmează afişează un lanţ de lungime minimă intre nodurile a şi b:

.... ;t• "'


Varianta Pascal
uses grafuri; #include "grafuri.cpp"
var int n,coada[50],s[50],
n,al,b,i_c,sf_c,i:integer ; i_o=l, sf_ c ..l,
s,T,coada : array[l . • SO] of A(SO] [50] ,i,T[SO] ,a,b;
integer;
A:mat_ ad; void refac (int nod)
{ i f (nodi=O)
procedura refac (nod:integer); {refac (T[nod]);
begin cout<<nod<<" ";
if nod<>O then }
begin }
refac (T[nod]);
write (nod, ' ');
end
end;
258 Capitolul 9. Introducere în teoria grafurilor

procedura bf(nod:integer); void bf(int nod)


begin { coada[i_c)=nod;
i_c : =1; s[nod]=l;
sf_c·: =l; while (i_c<=sf_c)
coada[i_c] :=nod; { i=l;
s[nod] :=1; while (i<=n)
while i_c<=sf_c do { if (A[coada[i_c]] [iJ==l
begin && s[iJ==O>
i:=l; sf_c++;
while i<=n do coada[sf_c]=i;
begin s[i]=l;
if (A[coada[i_c},il=l) and T(i]=coada[i_c];
(s[iJ=O) }
then i++;
begin }
sf_c:=sf_c+l; i_c++;
coada[sf_c]:=i; }
s[i] :=1; }
T[i]:=coada[i_c];
main()
end;
{
i:=i+l;
end; CitireN("Graf .txt" ,A,n);
i_e:=i_c+l; cout<<"a=";
cin>>a;
end
end; cout<<"b=";
cin>>b;
begin bf(a);
if (T[b]I=O) refac(b);
CitireN( 'Graf.txt• ,A,n);
}
write ( 'aa' ) ;
readln(al);
wri te ( ' b= ' ) ;
readln(b);
bf (al);
if T[b] <>O then refac(b);
end.

Matricea lanţurllor. Întrebări referitoare la situaţiile prezentate la 9 .1.1:

a) pentru exemplul 1, întrebarea este: cum putem afla, pentru fiecare oraş în parte,
oraşele în care putem ajunge cu maşina?

b) pentru exemplul 4, întrebarea este: cum putem afla, pentru fiecare triunghi în
parte, care sunt triunghiurile asemenea cu el?

Revenind la graful asociat acestor situaţii, problema constă în a afla pentru


fieca·re nod i, nodurile j, pentru care există un lanţ de la i la j. Evident, rezultatele
pot fi reţinute într-o matrice cu n linii şi n coloane (matrice pătratică). Această
matrice se numeşte matricea lanţurllor, iar elementele ei au semn ificaţia:

{~·.
dacă există lanţ de la i la j
L(i,j) = 'in caz contrar
IAanual de informatică pentru clasa a Xl-a 259

:J Problema 9.2. Fiind dat un graf G, cum putem obţine matricea lanţurilor?

~ Răspunsul este uşor de dat. Parcurgem graful începând cu nodul1. Pentru toate
-odurile j vizitate, vom avea L <1, j ) =1, completând astfel prima linie a matricei.
;poi, vom parcurge din nou, graful, pornind de la nodul 2. Pentru toate nodurile j,
:izitate, vom avea L(2, j) =1, apoi parcurgem graful începând cu nodul 3 .... ş.a.m.d.
O anumită îmbunătăţire a algoritmului se obţine dacă ţinem cont de faptul că matricea
~ţurilor este simetrică (de ce?). Lăsăm ca exerciţiu scrierea acestui program.
1trebare: care este complexitatea acestui algoritm?

9.1 .8. Graf conex

Revenind la exemplele de la 9 .1 . 1, putem pune întrebările:


a) pentru exemplul !: se poate ajunge cu maşina din orice oraş în oricare altul?
b) pentru exemplul4: toate triunghiurile sunt asemenea între ele?

Dacă la ambele întrebări răspunsul este afirmativ, ce semnificaţie are el pentru


grafurile asociate? Aceasta înseamnă că pentru orice pereche de noduri, există un
lanţ care le are ca extremităţi. Sau, în limbajul specific teoriei grafurilor, cele două
grafuri sunt conexe.

Definiţia 9.9. Un graf neorientat G= (V, E) este conex, dacă pentru orice
pereche de noduri x, yeV, există un lanţ în care extremitatea iniţială este x
şi extremitatea finală este y.

Exemple Figura 9.11 .

1 . Graful alăturat este conex. De exemplu,


între nodurile 1 şi 5 există lanţul [ 1, 2, 3, 4, 5), dar
ş i lanţul [1 ,3,4,5]. Între nodurile 3 şi 5 există
lanţul [3 ,4, s), ş.a.m.d.

Oricum am alege două noduri, există lanţul cerut de


definiţie.

2 • Graful alăturat nu este conex. De exemplu, între Figura 9.12.


nodurile 1 şi 4 nu există nici un lanţ.

1,~,
,~j Un graf cu un singur nod este, prin definit,ie, conex. Aceasta pentru că nu există
• două noduri diferite pentru care să se pună problema existenţei unui lanţ.
260 Capitolul 9. Introducere în teoria grafurilo·

O Problema 9.3. Fiind dat un graf Go: (V, E >, să se scrie un program care să decidă
dacă graful dat este sau nu conex.

0 Rezolvare. Ţinând cont de cele învăţate, problema nu este grea. Putem utiliza
una din metodele de parcurgere învăţate, DF sau BF. Ideea este următoarea: dacă ,
pornind de la un nod, printr-una din metodele de parcurgere, ajungem să vizităm toate
celelalte noduri, atunci graful dat este conex. Cum putem şti dacă am vizitat toate
nodurile? Simplu, după parcurgere, toate componentele vectorului s reţin 1. Puteţ1
scrie acest program?

9.1 .9. Componente conexe


Analizăm, din nou, exemplele de la 9 .1.1.
a) Pentru exemplull: se cere o mulţime de oraşe, astfel încât să se poată circula cu
maşina între oricare două oraşe din mulţime, iar dacă un oraş nu aparţine acester
mulţim i , atunci nu putem ajunge cu maşina de la el ia oricare oraş din mulţime.
b) Pentru exemplul 4: se cere o mulţime de triunghiuri astfel încât oricare două
triunghiuri din această mulţime sunt asemenea, iar dacă un triunghi nu aparţine
acestei mulţimi, el nu este asemenea cu nici unul din mulţime.
Observăm că fiecare astfel de mulţime este maximală în raport cu relaţia de
incluziune. Dacă nu ar fi aşa, ar exista o altă mulţime care ar include-o.

Fie graful asociat unuia dintre cazurile prezentate. În termeni din teoria
grafurilor, problema se reduce la determinarea nodurilor unei componente conexe.

Definiţia 9.10. Fie G=(V,E) un graf neorientat şi Gl=(V1 ,Ed un subgraf


al său.Atunci G1 == (V1 , E 1 ) este o componentă conexă a grafului
G= (V. E) dacă sunt îndeplinite condiţiile de mai jos:

a) Oricare ar fi x,yev1. există un l anţ de la x la y.


b) Nu există un alt subgraf al lui G, ~= (V:z,E:~) cu V 1 C:V:z care
înden'ineşte condiţia a)
.

$i': Exemple

1. Graful alăturat este alcătuit din două componente


conexe. Prima este alcătuită din nodurile 1, 2, 3 şi
muchiile care le unesc pe acestea, a doua este
formată din nodurile 4 şi 5 şi muchia care le uneşte.

Figura 9.14.
2. Graful din figura 9.14. conţine 3 componente
conexe. Aşa cum un graf, cu un singur nod, este
conex, tot aşa un nod izolat alcătuieşte el singur o
G
componentă conexă.

0
Manual de informatică pentru clasa a Xl - a 261

.~ Observaţii
'")
'r>

1 . Câte componente conexe poate avea un graf neorientat cun noduri? Numărul
lor este cuprins între 1, pentru un graf conex şi n corespunzător unui graf cu toate
nodurile izolate.

2. în unele probleme, se dă un graf neorientat, care nu este conex şi se cere să


se adauge un număr minim de muchii, astfel încât graful să devină conex. În
astfel de cazuri, se determină componentele conexe, fie ele e1, e:~ .... ep. Fie p
numărul lor. Vom adăuga p - 1 muchii, prima uneşte un nod din e1 cu unul din e:~, a
doua uneşte un nod din e 2 cu unul din C 3 , ... , ultima uneşte un nod din Cp- 1 cu
unul din ep.

CJ Problema (de programare).9.4. Fiind dat un graf neorientat, se cere să se


afişeze nodurile fiecărei componente conexe.

0 Rezolvare. După cum uşor vă puteţi da seama, o parcurgere a grafului (DF sau
BF) pornind de la un anumit nod, vizitează toate nodurile componentei conexe care
îl conţin e . Pentru fiecare nod vizitat, s [il reţine 1. Dacă, după o parcurgere, mai
rămân noduri nevizitate, parcurgerea se reia începând de la primul nod nevizitat.
Evident, numărul componentelor conexe este egal cu numărul de parcurgeri
necesare pentru a fi vizitate toate nodurile.

va·rlanta P.asc~l •. ,::, ··1


i""

uses grafuri; #include •grafuri.cpp•


var n,i:integer; int s[50],A[50] [SO],n,i;
s:array [l . • SO]of integer;
void df_r{int nod)
A:mat_ad;
{ int k;
procedura d f_r(nod:integer); cout<<nod<<" ";
var k: integer; s[nod]=l;
beHin for (kal;k<=n;k++)
write (nod, ' '); if((A[nod] [kJ==l)&&
s[nod] :=1 ; (s[kl==O))
for k:=l to n do df_r(k) 1
if (A[nod,k]=l) and (s[k]=O) }
then df_r ( k) ;
main()
end;
{ CitireN("Graf.txt",A,n);
begin for (i=l;i<=n;i++)
CitireN( 'Graf. txt' ,A,n) 1 i f (s[i]==O)
for i:=l to n do { cout <<"CoJ!Q;>
if s[i]=O then conexa" < <endl;
begin df_r(i);
writeln('Comp conexa'); cout<<endl;
df_r(i); }
writeln;
end
end.
262 Capitolul 9. Introducere în teoria grafurilo·

9.1 .16. Cicluri


Revenim la exemplul cu oraşele legate prin şosele. Întrebarea este următoarea
există vreun oraş din care putem face o excursie cu maşina, să nu trecem decât o
singură dată pe o şosea, să vizităm mai multe oraşe şi să ne întoarcem de unde aJT
plecat?
Problema se reduce la a afla dacă graful asociat are sau nu cel puţin un ciclu.
Mai întâi, să definim noţiunea.

Definiţia 9.11 . Un lanţ L care conţine numai muchii distincte şi pentru


care nodul Initial coincide cu nodul final se numeşte ciclu. Dacă, cu
excepţia ultimului nod, care coincide cu primul, lanţul este elementar.
atunci ciclul este elementar (adică, cu excepţia ultimului nod, care
coincide cu primul, conţine numai noduri distincte) .
., . :···
Exemple: pentru graful neorientat din
~. .
·iit: . Figura 9.15.
figura 9.15. avem:

a. [ 1, 2, 3 , 1 J este ciclu elementar.


b. [1,2 , 11 nu este ciclu, pentru că (1,2) şi
(2 , 1) reprezintă o aceeaşi muchie, deci nu conţine
numai muchii distincte.
c. [1 , 2, 3, 2, 11 nu este ciclu, pentru că nu conţine numai muchii distincte.

O Problema 9.5. Fiind dat un graf conex, G= (V, E >. să se scrie un program care
decide dacă graful conţine cel puţin un ciclu.

0 Rezolvare. Începem prin a observa că dacă graful nu este conex, putem rezolva
problema verificând dacă există un ciclu într-o componentă conexă a sa. Pentru
simplitate, am preferat să considerăm că graful este conex. Şi aici, problema se poate
rezolva pornind de la o parcurgere DF. Graful conţine cel puţin un ciclu dacă, în
timpul parcurgerii, algoritmul va ajunge în situaţia de a vizita un nod de două ori
(tentativă oricum respinsă, pentru că algoritmul testează acest lucru, vedeţi rolul
vectorului s). Vom da un exemplu, cu graful de mai jos, pe care îl parcurgem DF.

Vizităm nodul1, apoi primul nod adiacent lui, fie el nodul


2, apoi primul nod adiacent al lui 2, fie el 3, apoi se Încearcă
vizitarea nodului adiacent cu 3, şi anume 1. Dar acest nod a mai
fost vizitat şi tentativa este respinsă. De ce s-a ajuns în situaţia
să se încerce vizitarea nodului 3 de două ori? Pentru că nodul
1, a fost vizitat ca nod de pornire şi pentru că se încearcă
vizitarea lui prin lanţul [ 1, 2 , 3 J .

Astfel, s-a obţinut ciclul [ 1, 2, 3, 11 .


'·!anual de informatică pentru clasa a Xl- a 263

Programul următor testează dacă un graf conţine sau nu cicluri. Să observăm


:ă, odată vizitat un nod, accesat prin intermediul muchiei (nod, k), se şterge din
-atricea de adiacenţă muchia (k, nod) , pentru că altfel ar fi selectată şi această
-uchie şi s-ar ajunge în situaţia să fie semnalat un ciclu fals.

uses grafuri,wincrt; #include "grafuri.cpp"


var n:integer; int s [50] ,A[50] [50] ,gasit,n;
s:array[1 •• 50]of integer;
void df ( int nod)
A:mat_ad ;
{ int k;
gasit:boolean;
s[nodl=l;
procedura df(nod: integer); for(k=l;k<=n;k++)
var k 1 integer; if (A[nod] [kl==l)
begin {
s[nod] :=1; A[k] [nod]aO;
for k:=l to n do if (s[k] ==O) df(k);
if A[nod,k]=l then else gasit=1;
begin }
A[k] [nod]:=O; }
if s(k]=o· then df(k)
main()
else gasit:=true;
{
end
CitireN(ttGraf . txt",A,n);
end;
df (1);
begin if (gasit) cout<<"Da";
CitireN( 'Graf.txt• ,A,n); else cout<<"Nu";
df (1) 1 }
if gasit then writeln('Da')
else writeln( 'Nu')
end.

f: Observaţie

Deşi parcurgerea se face în timp polinomial şi cu toate că programul este
simplu, se putea proceda într·un mod cu mult mai inteligent. Mai mult, aproape că nu
este cazul să facem un program. Graful fiind conex, este suficient să verificăm relaţia
m=n-1,

unde m este numărul de muchii, iar n este numărul de noduri. Dacă relaţia este
verificată, înseamnă că graful nu conţine cicluri, altfel, dacă m>n-1 înseamnă că
graful conţine cel puţin un ciclu, iar dacă m<n-1 înseamnă că nu este conex, şi ar
contrazice cerinţa.

De unde această observaţie? Pentru a o înţelege, trebuie să studiem arborii. ..


264 Capitolul 9. Introducere în teoria grafuri ::.

9.1.11. Arbori

9.1.11.1. Noţiunea de arbore

Să presupunem că o firmă doreşte să conecteze la TV, prin cablu, cele n case


ale unui sat. Cum vor fi conectate casele la cablu? Logic, va trebui ca fiecare casă ~
fie conectată. Apoi, la o casă va sosi cablul dintr-un singur loc şi, eventual, de la ea va
porni cablul către altă casă. Dacă analizăm situaţia prezentată prin prisma teorie
grafurilor, vom avea un graf conex (fiecare casă trebuie conectată), iar graful nu va
conţine cicluri. Dacă ar conţine un ciclu, atunci, evident, putem renunţa la cablul care
uneşte două case care aparţin ciclului respectiv. Astfel, obţinem un graf et.
proprietăţile de mai sus, numit arbore.

Definiţia 9.12. Se numeşte arbore un graf


neorientat care este conex şi nu conţine
cicluri.

în figura 9.17. aveţi un exemplu de arbore cu s


noduri.

O Problema 9.6. Se citeşte un graf. Să se scrie un


program care verifică dacă este arbore. Figura 9.17.

0 Rezolvare. Conexitatea ştim să o verificăm. Dacă într-o parcurgere, DF (BP') se


vizitează toate nodurile, atunci graful este conex. Dacă un graf are cicluri, este, din
nou, uşor de verificat, cu aceeaşi parcurgere DF(BF). Să ne amintim că în cazul în
care graful are cicluri, în timpul parcurgerii, va exista cel puţin o a doua tentativă de
vizitare a unui nod. Prin urmare, printr-o simplă parcurgere DF (BF) se poate stabilt
dacă graful este conex sau nu.

Varianta Pascal Va'r lanta C++ ,


.·· '
uses grafuri; #include "grafuri.cpp"
var n,i,suma:intoger; int s[SO] ,A[SO] [SOl ,gasit,n,i,
s:array[l •• SO]of integer; EIUIIIal
A:mat_ad;
gasit:boolean; void df(int nod)
{
procedura df(nodzinteger); int k1
var k:integer; s[nod]al;
begin for(kal;k<•nJk++)
s[nod]:=l; if (A[nod] [k] ==l)
for k:=l to n do {
if A[nod,kl=l then A[k] [nod]=O;
begin if (s[kl=•O) df(k);
A[k,nod] :=O; eloo gasit=l;
if s[k]aO then df(k) }
else gasit:=truet }
end
endt
anual de informatică pentru clasa a Xl-a 265

begin main()
CitireN( 'Graf.txt• ,A,n); { CitireN("Gra f.txt",A,n);
df(l); df(l);
suma:=O; suma= O;
for i:=l to n do for (i=l;i<=nJi++ )
suma:=suma+ s[i]; suma+=s[i];
if suma<>n if (sumal =n)
then writeln('Nu este conex') cout<<"Nu este conex";
el se el se
if gasit if (gasit)
then writeln('Are ciclu') cout<<"Are ciclu";
else writeln('Arb ore') ; else cout<<"Arbor e"t
end. }

Teorema 9.1. Fie G un graf neorientat cu n noduri. G este arbore dacă şi

numai dacă are n-l muchii şi nu conţine cicluri.

~ Demonstratie .
~ Fie G un arbore (graf neorientat, conex şi fără cicluri). Trebuie să demonstrăm
că are n-1 muchii. Vom demonstra prin inducţie. Dacă n=l, numărul muchiilor
este o (se verifică, are n-1 muchii). Vom presupune proprietatea adevărată
pentru arbori cun noduri (adică au n-1 muchii). FJe un arbore cu n+l noduri.
Există cel puţin un nod terminal (nod care are o sfngură muchie incidentă).
Dacă nu ar exista un astfel de nod, să considerăm un lanţ care porneşte
dintr-un nod oarecare. La fiecare pas, vom selecta o muchie. Până la urmă,
pentru că mulţimea nodurilor este finită şi pentru că nu există nod terminal,
lanţul va trece de două ori printr-un acelaşi nod. Asta înseamnă că arborele ar
conţine cicluri (absurd, se contrazice definiţia). Eliminăm nodul terminal şi
muchia care îi este incidenta. Obţinem un arbore cu n noduri. Conform ipotezei
făcute, acesta va avea n-1 muchii. Înseamnă că arborele cu n+1 noduri va
avea n muchii (n-1+1).

ţ::: Fie G un graf cu n-1 muchii, care nu conţine cicluri. Rămâne de dovedit că G
este conex. Vom demonstra prin reducere la absurd. Presupunem că G
nu este conex. Fie G~. G2, ... , Gp componentele conexe ale grafului. Fiecare
dintre ele îndeplineşte condiţiile:
a) este conexă (aşa a fost aleasă);

b) nu conţine cicluri (pentru că G nu conţine cicluri).

fiecare dintre ele este arbore. Fie 11\i numărul muchiilor şi n 1


Rezultă că
numărul nodurilor fiecărui arbore G1 . Avem Jt1i=n1 -1. Dar m1+m3 + ••• +lllp•n-1.
Rezultă: n1-l+n2-1+ ••• +np-l=n-1, deci n 1 +n2 + ••• +np=n+p-1. Dar G are n
noduri. Rezultă: n =n+p-1, deci p=l. În concluzie, există o singură componentă
conexă, care nu conţine cicluri. Rezultă că G este arbore:
266 Capitolul 9. Introducere în teoria grafurilor

9.1 .11.2. Noţiunea de arbore parţial

sa presupunem ca într-un judeţ se impune repararea tuturor şoselelor care


unesc diversele localităţi. Pentru aceasta s-a obţinut un credit extern care permite ca
toate şoselele să fie recondiţionate. Desigur, se doreşte ca repararea şoselelor să se
facă cât mai repede, dar, în acelaşi timp, trebuie ca în timpul reparaţiilor sa se poata
circula, astfel încât să nu ramână localităţi inaccesibile pentru traficul rutier. Se cere
un număr minim de şosele care să nu intre în reparaţii în prima fază, astfel încât
condiţia de mai sus să poată fi respectată.
Dacă considerăm graful în care nodurile sunt localităţile, iar muchiile sunt
şoselele, va trebui să păstrăm un număr minim de muchii, astfel încât graful să
rămână conex. Care este acel număr minim de muchii care conservă conexitatea?
Evident, n-l.. Cum graful rămâne conex, în urma eliminării anumitor muchii se obţine,
aşa cum rezultă din teorema 9.1. (data în paragraful anterior), un arbore.

O Problema 9.7. Se dă un graf conex,


G"' {V, E) . Se cere un graf parţial al său,
care este arbore. O astfel de structură se
numeşte arbore parţial. Evident, există
posibilitatea ca dintr-un graf conex să se
poată obţine mai mulţi arbori parţiali.

Alăturat, puteţi observa un graf


conex (stânga) şi un arbore parţial al său.

0 Pentru a rezolva problema, vom folosi, Figura 9.18.


din nou, o metodă de parcurgere a unui
graf, mai precis parcurgerea DF. Vom afişa numai muchiile selectate de algoritm,
adică cele care nu determină cicluri. Programul următor citeşte datele referitoare la un
graf conex şi afişează muchiile unui arbore parţial.

uses grafuri,wincrt; #include "grafuri.cpp"


var n: integer; int s[SO] ,A[SO] [50] ,n;
s:array[l •• SO]of integer;
A:mat_ ad; void df_ r(int nod)
procedura df_ r(nod:integer); (int k;
var k 1 integer1 s[nod]•l;
begin for (k•1Jk<=n;k++)
s[nod]: • lt if (A[nod] [kl==l && s[k]==O)
for k:=l to n do { cout<<nod<<" "<<k<<endl;
if (A[nod,k]•l) and (s[kl~O) df_r(k);
then begin }
}
writeln(nod,' ',k) 1
df_ r(k); main()
end; { CitireN("Graf.tx t",A,n);
end1 df_ r(l)t
begin }
CitireN( 'Graf. txt • ,A,n);
df_ r(l);
end.
Manual de i nformatică pentru clasa a Xl-a 267

9.2. Grafuri orientate

9.2.1. Noţiunea de graf orientat


Nu întotdeauna grafurile neorientate pot exprima "relaţiile" existente între
anumite elemente. Pentru a ne da seama de acest fapt, vom da câteva exemple.

1. Considerăm un grup de n persoane, unde fiecare persoană deţine un telefon


mobil. În agenda telefonului mobil al fiecărei persoane se găsesc numerele de telefon
ale altor ~o persoane din grup. Să observăm că dacă persoana i are în agenda
telefonică numărul de telefon al persoanei j, nu este obligatoriu ca persoana j să
al bă în agendă n umărul de telefon al persoanei i.

2 . Un autor de carte doreşte să prezinte anumite noţiuni. Pentru a obţine o lucrare


valoroasă, acesta doreşte ca orice noţiune pe care o introduce să fie precedată în
lucrare de noţiun ile pe care le presupune deja prezentate.
3• Un program este alcătuit din n instrucţiuni , atribuiri, apeluri de subprograme,
afişări, citiri sau instrucţiun i decizionala. Aici sunt excluse instrucţiunile repetitive.
Acestea rezultă în urma instrucţiunilor decizionala şi a ordinii de executare a
instrucţiunil or. Pentru a prezenta ordinea în care se execută instrucţi unile, unui
program i se poate asocia o schemă logică.

Elementele între care există anumite relaţii se numesc şi de această dată


noduri sau vârfuri. Două vârfuri pot fi unite printr-un arc. Arcul ( i, j ) este
reprezentat ca o săgeată de la i la j şi are semn ificaţia generală că există o relaţie
de lai la j (atenţie, nu şi de la j lai).
Pentru exemplul 1, arcul ( i, j > are
semnificaţia că i are pe agenda telefon ică
numărul lui j . Pentru exemplul 2, arcui ( i, j)
are semn ificaţia că este necesară cunoaşterea
noţiun ii i pentru a putea înţelege noţiunea j .
Pentru exemplul 3, arcul (i, j > are semnificaţia
că după executarea instrucţiunii i este posibil să !''·
~:~;t,.,~ ·.:
se efectueze Instrucţi unea j. 1

Procedând aşa cum am arătat, obţinem un Figura 9.19.


Exemplu de graf orientat
graf orientat aşa cum este cel alăturat.

~ Definiţia 9.13. Se numeşte graf orientat perechea ordonată G==(V, A),

~ unde:
-+ V={v1 , v 2 , ... , vn} este o m ulţime fin ită de elemente numite vârfuri sau
noduri.
-+ A este o mu lţi me de arce. Vom nota un arc prin perechea ordonată
(vi, Vj) cu i:;>~:j .
268 Capitolul 9. Introducere în teoria grafurilor

De exemplu, pentru graful din figura 9.19. , avem V={1,2,3,4,5,6} şi


E.x: A= { ( 1, 6 ) , ( 6 , 1 ) , ( 6 , 5 ) , ( 4 , 5 ) , ( 2 , 1 ) , ( 2 , 3 )} .

...li Observatii.
1. În cazul grafurilor orientate. dacă există arcul (vi, v:1 > nu înseamnă că există
automat şi arcul (v:~, vd. Din acest motiv, în definiţie, s-a precizat că un arc este o
pereche ordonată de forma (v1 ,v:~>·
2. Din definiţie , rezultă că nu există arce de la un nod la el însuşi. Astfel, un arc a
fost definit (v11 v:1) cu i;t:j.
3. Să observăm că mulţimea arcelor A, respectă relaţia Ac.vxv, unde vxv este
produsul cartezian al mulţimii v cu ea însăşi.
4. Grafurile neorientate sunt cazuri particulare
de grafuri orientate, mai precis acele grafuri
orientate în care pentru orice arc (vi, v:1) există
arcul (v:~,vd. Alăturat , puteţi observa un graf
neorientat (stânga) reprezentat ca un graf orientat
(dreapta).
Figura 9.20.
5. Grafurile orientate se mai numesc şi
dlgrafuri.

Definiţia 9.14. În graful orientat G= (V, A), vârfurile distincte v1 ,v:~e G sunt
adiacente dacă există cel puţin un arc care le uneşte.
Astfel, avem următoarele cazuri:
a) Există numai arcul (v11 v:~)EA- în acest caz spunem că arcul {v 1 , v:~ ) E A
este incident spre exterior cu v.1 şi spre interior cu v :1.
b) Există numai arcul <v:~ , vd e A- în acest caz spunem că arcul (v:~ , vt)eA
este incident spre interior cu v 1 şi spre exterior cu v :1.
c) Există arcul (v1 , v:~) E A şi arcul (v:~ ,vt> e A.

Definiţia 9.15. Într-un graf orientat, prin gradul exterior al unui vârf v vom
.~·
;;.,t:_;) înţelege numărul arcelor Incidente spre exterior cu v. Gradul exterior al
unui nod va fi notat cu d+ (v) .

Definiţia 9.16. Într-un graf orientat, prin gradul interior al unui nod v vom
înţelege numărul arcelor incidente spre interior cu v.
Gradul interior al unui nod va fi notat cu d - (v) .

e.
C ·x : Pentru vârful i din figura alăturată, avem:

d+(i)=3 şi
d-(i) =2. Figura 9.21,
\1anual de informatică pentru clasa a Xl-a 269

O relaţie utilă: fie un graf orientat cu n vârfuri şi m arce. Avem relaţia:

":!. Demonstraţie. Relaţia este adevărată, pentru că fiecare arc este incident spre
exterior cu un vârf şi fiecare arc este incident spre interior cu un vârf.

Revenim la exemplele prezentate la începutul acestui paragraf.


Pentru exemplul 1, dacă gradul exterior al nodului i este k, înseamnă că
persoana i are pe agenda telefonică numerele a k persoane din grup, iar dacă gradul
interior al nodului i este 1 înseamnă că numă rul persoanei i este pe agenda
telefonică a 1 persoane.

Pentru exemplul 2, dacă gradul exterior al nodului i este k, îhseamnă că


noţiunea i este necesară pentru înţelegerea a altor k noţiuni, iar dacă gradul interior
al nodului i este 1, înseamnă pentru a înţelege noţiunea i, este necesar ca alte 1
noţiuni să fie înţelese.

Pentru exemplul 3, dacă gradul exterior al nodului i este k, înseamnă că după


instrucţiunea i se pot efectua alte k instrucţiuni (i este instrucţiune decizională), iar
dacă gradul interior al nodului i este 1 , înseamnă că instrucţiunea i se poate executa
după executarea uneia din cele 1 instrucţiuni.

11 ( 11 - 1)

O relaţie utilă: avem 4 2 grafuri orientate cu n noduri.

":!. Demonstraţia se face prin Inducţie. Dacă n=l, avem 1 graf orientat. Dacă
n ..2, cele două noduri pot să nu fie sau să fie adiacente. În acest din urmă caz,
putem avea arcul (v1 ,v2 ) sau arcul (v2 ,vd sau putem avea ambele arce
(v11 v 2 ) şi (v2 , vd. În total, avem 4 grafuri orientate, valoare care rezultă şi din
formulă, dacă înlocuim n cu 1.

Presupunem fonnula adevărată, adică dacă sunt n vârfuri , avem


11{11 - 1)
4 2

grafuri orientate. Trebuie să demonstrăm că dacă sunt n+l vârfuri, avem


11(11+1)
4 2

grafuri orientate. Adăugăm vârful n+l. Acest vârf poate fi adiacent cu fiecare
dintre celelalte n vârfuri în exact 3 moduri (vedeţi adiacenţa) sau poate să nu fie
adiacent. Atunci, numărul de grafuri orientate cu n+l noduri este
11(11- 1) 11(11- 1) +11 11(11+1)

4 2 x4'1 =4 2 =4 2 .

Definiţia 9.17. Un graf orientat este complet dacă oricare două vârfuri i
şi j (i;t:j sunt adiacente.
)
270 Capitolul 9. Introducere în teoria grafurilor

~
n(n-1)

O relaţ.ie utilă: avem 3 2 grafuri complete. Demonstraţia se face prin inducţie!


Exerciţiu!

9.2.2. Memorarea grafurilor orientate

Memorarea grafurilor orientate se face la fel precum memorarea grafurilor


neorientate.

Pentru fiecare structură de date pe care o vom folosi, vom avea câte o
procedură (funcţie)care citeşte datele respective. Toate aceste subprograme se
găsesc grupate în unitatea de program grafuri.pas {pentru Pascal) şi în
grafuri.cpp (pentru C++).

Toate subprogramele pe care le utilizăm citesc datele dintr-un fişier text, în care
pe prima linie vom scrie număru l de noduri (n), iar pe următoarele linii câte o muchie
( i, j) ca în exemplul următor, în care este prezentat un graf şi liniile fişierului text
care este citit pentru el:

Fişierul 6

0 text:
1 2
1 3
1 5
2 3
3 4
4 5

Figura 9.22.

Trecem la prezentarea structurilor prin care putem memora datele referitoare


la un graf.

A. Memorarea grafului prin matricea de adiacenţă

An.n - o matrice pătratică, unde elementele ei, a 1 .:l au semnificaţia:

a .. =
•.J
{1,O, pentru (i, j) e A
pentru (i, j) li!: A
o o 1 o
gx:· Pentru graful din figura 9.22., matricea de
este prezentată alăturat.
adiacenţă
o o 1 o o o
o o o o o

l Observaţii
o o o o 1 o
1. Întrucât, dir. modul în care a fost definit graful, rezultă că nu o o o o o o
există arce de la un nod la el însuşi, rezultă că elementele de o o o o o o
Manual de informatică pentru clasa a Xl-a 271

pe diagonala principală reţin o (ai,i =O, oricare ar fi ie {1, 2, ••• , n} ).


2. Matricea de adiacenţă nu este în mod obligatoriu simetrică.
3. Suma elementelor de pe linia i , i e u,'2, . .. ,ri} are ca rezultat gradul
exterior al nodului i, d• ( i).
4. Tot aşa, suma elementelor de pe coloana i, i e { 1, 2, ••• , n} are ca rezultat
gradul interior al nodului i, d- ci).
s. Suma tuturor elementelor matricei de adiacenţă este, de fapt, suma gradelor
exterioare (sau interioare) adică suma arcelor, m.
6. Dacă graful citit are un număr mic de muchii, atunci matricea de adiacenţă este o
formă ineficientă de memorare a lui, pentru că ea va reţine o mu~ime de o.

Subprogramele pe care le utilizăm sunt:

procedura Citirea void CitireO(char Nume_fis(20],


(Nume_Fis:string; int A[SO) [50], int& n)
var A:Mat_ad; var n:integer); {
var f:text; int i,j;
i,j:byte; fstream f(Nume_fis,ios::i n);
begin f>>n;
Assign(f,Nume_Fis ); while (f>>i>>j ) A[i] [j] ~1;
Reset(f); Readln(f,n); f.close();
while(not eof(f)) do }
begin
read1n(f,i,j);
A[i,j] :=1;
end;
close(f);
end;

B • Memorarea grafului prin liste de adiacenţă

Se face la fel ca în cazul grafurilor neorientate, diferenţa este dată de faptul că


arcul <i , j ) este înregistrat o singură dată (nu ca în cazul grafurilor neorientate când
reţineam (i ,j) şi (j,i)).

Mai jos, puteţi observa subprogramele care construiesc listele de adiacenţă:

Varianta Pascal t-:" ~);•::::~j:lilÎ""'~' .Varianta C++ ,:il:i",, '"


)·... l.t'
procedura Citire_LA_Astatic o void Citire_~AstaticO
(NUme_fis :string;var T:Lista; (char NUme_fis[20],
var Start:pornire; int T[2) [50],
var n:integer) 1 int Start[SO), int& n)
var i,j,k:integer; {
f:text; int i,j,k=O;
272 Capitolul 9. Introducere în teoria grafurilor

begin fstream f(Nume_fis,ios::in);


k:-=0; f>>n;
Assign(f,Nume_Fis); Reset(f); while (f»i»j)
Readln(f,n); { k++;
while{not eof{f)) do T[OJ[k] ::j;
begin T[l] [k]=Start[i];
readln(f,i,j); Start[i]=k;
k:=k+l; )
T[O,k] :=jl f .close();
T[l,k] : =Start(i); )
Start[i) :=k;
end;
close{f);
end;

c. Memorarea grafului prin lista arcelor se face la fel ca în cazul grafurilor


neorientate.

9.2.3. Graf parţial, subgraf

~ Definiţia 9.18. Un graf parţial al unui graf orientat dat G=(V,A) este un
2:.J graf Gt= (V, A 1 ) • unde A10\.·

Un graf parţial al unui graf dat, este el însuşi, sau se obţine din G prin
suprimarea anumitor arce.

--->
rezulta 0
Figura 9.23.
Obţinerea unui
graf parţial G=(V,A)

Referitor la exemplul 1 din 9. 2 .1. unele persoane îşi şterg din agendă
numerele altor persoane din grup. Aceasta înseamnă că noul graf nu va mai avea
anumite arce, deci va deveni un graf parţial al grafului iniţial.

~ Exerciţiu! Câte grafuri parţiale are un graf cum arce?


Definiţia 9.19. Un subgraf al unui graf orientat G= (V, A) este un graf
G1=(Vl,Ad , unde v 1c:v, A1cA, iar arcele din A1 sunt toate arcele din A
care sunt incidente numai la vârfuri din mulţimea V 1 .
Manual de informatică pentru clasa a Xl- a 273

Un subgraf al unui graf G este graful G sau se obţine din G prin suprimarea
anumitor vârfuri şi a tuturor arcelor incidente cu acestea.

("] 1
cx:

--->
rezultă

Figura 9.24.
Obţinerea unui
subgraf G=(V,A)

Referitor la exemplele din paragraful 9. 2 .1, avem:


1. Pot exista persoane din grup care îşi pierd telefonul mobil. Astfel, numerele de
telefon ale respectivelor persoane aflate în agenda altora, pentru moment, nu mai
folosesc. De asemenea, ceilalti din grup nu mai păstrează numerele de telefon ale
acestora. În graful iniţial se renunţă la vârfurile respective şi la arcele adiacente lor.
Astfel, se obţine un subgraf al grafului iniţial.
2 • Autorul renunţă la prezentarea anumitor noţiuni. Din nou, se obţine un subgraf al
grafului iniţial.

~ Exerciţiul Câte subgrafuri are un graf cu n vârfuri?


9.2.4. Parcurgerea grafurilor. Drumuri. Circuite

-+ Parcurgerea grafurilor orientate se face la fel precum parcurgerea grafurilor


neorientate. Aceasta înseamnă că parcurgerea se poate face în două feluri, în
adâncime (DF) şi în lăţime (BF). Subprogramele sunt aceleaşi.
-+ Pentru graful orientat G= (V, A), un drum D='[v1 , v 2 , ••• , vp] este o succesiune
de vârfuri ( v 1 ,v2 )eA, (v2 ,v3 )eA, ..., (vp_1 ,vp)eA. Vârfurile v 1 şi vp se
numesc extremităţile drumului. Numărul p-1 se numeşte lungimea
drumului. Acesta este dat de numărul arcelor ce unesc vârfurile drumului.
Determinarea existenţei unui drum între două vârfuri şi determinarea unui drum
între două vârfuri date, eventual a unui drum de lungime minimă, se face la fel
ca în cazul grafurilor neorientate.
-+ Un drum D=[v1 ,v2 , ••• ,vp] este elementar dacă conţine numai vârfuri
distincte.
-+ Un circuit într-un graf orientat este un drum în care vârful iniţial coincide cu
vârful final şi care conţine numai arce distincte. Printr-o simplă parcurgere DF
putem determina, ca şi în cazul grafurilor neorientate, dacă un graf conţine sau
nu un circuit.
274 Capitolul 9. Introducere în teoria grafurilor

Anumite noţiuni prezentate în cazul grafurilor neorientate se regăsesc şi în


cazul grafurilor orientate.
-+ Pentru graful orientat G=(V,A), un lanţ D=[vl,v2, • •. ,vp] este o succesiune
de vârfuri astfel încât între oricare vârfuri distincte din vi, v 1 • 1 există fie arcul
(vi,vi•l>, fie arcul (vi+l,vd.
-+ Un lanţ L=[v11 v 2 , ••• ,vp] este elementar dacă conţine numai vârfuri distincte.

Reluăm exemplele din paragraful 9. 2 .1.


Pentru exemplul 1: persoana i are un mesaj de transmis persoanei j. Dacă
există posibilitatea ca ea să sune o persoană al cărei număr îl are în agendă, aceasta
o altă persoană ş.a.m.d până este sunată persoana j, înseamnă că există un drum
de la i la j. De asemenea, dacă există posibilitatea ca persoana i să ajungă să
primească mesajul pe care 1-a transmis, înseamnă că există un circuit de la i la j.

Pentru exemplul 2: dacă graful are un circuit înseamnă că există cel puţin o
noţiune care nu poate fi explicată decât prin
intermediul altora care, la rândul lor, ar trebui
explicate exact prin noţiunea care nu poate fi
explicată fără ele. Se mai întâmplă şi aşa. Un
exemplu de limbaj care nu poate fi predat în mod
clasic este Java. De exemplu, cel mai simplu
program utilizeaz~ din plin programarea orientată
pe obiecte, care se studiază după ce am învăţat să
scriem programe simple.
Pentru exemplul 3: dacă după execuţia
instrucţiunii i,pentru cel puţin un set de date de
intrare, se ajunge să se execute instrucţiunea j,
înseamnă că există un lanţ de la i la j. De
asemenea, dacă după ce se execută instrucţiunea
i, se ajunge să se execute din nou instrucţiunea i,
atunci programul conţine structuri repetitive, iar
graful asociat conţine circuite.
Aşa cum am definit pentru grafuri neorientate
matricea lanţurilor, similar, se poate forma pentru Figura 9.25.
grafuri orientate matricea drumurilor:

{~'.
dacă există drum de la i la j
DQ,j) = în caz contrar

Matricea drumurilor nu este, în cazul general, simetrică. Pentru a o determina,


pentru fiecare nod i , parcurgem graful şi aflăm toate nodurile pentru care există drum
de la i la j. Pentru toate nodurile atinse (mai puţin i), vom avea L <i, j) =l. Astfel,
se completează linia i.

~ Exerciţiu! Scrieţi programul care, pornind de la un graf, afişează matricea


drumurilor.
Manual de informatică pentru clasa a Xl-a 275

9.2.5. Graf tare conex . Componente tare conexe

Reluăm exemplul 1 din paragraful 9. 2 .1. Să presupunem că grupul celor n


persoane efectuează o excursie la munte şi, din păcate, s-au rătăcit. Întrebarea este:
există posibilitatea ca oricare membru al grupului, să propună un loc de întânire şi
să-şi anunţe telefonic prietenii din agendă, aceştia să-i sune pe alţii ş.a.m.d., astfel
încât toţi membrii grupului să afle de acest loc?

Judecând după graful orientat asociat. ar trebui ca de la oricare membru al


grupului să existe un drum către oricare alt membru al grupului. Aceasta înseamnă că
oricare ar fi nodurile i şi j, există un drum de la i la j şi există şi un drum de j la i.
Un graf cu această proprietate se numeşte graf tare conex.

Definiţia 9.20. Graful orientat G= (V, A) este tare conex dacă \/x,yev,
3 drum de la x la y şi drum de la y la x.

Definiţia 9.21. Subgraful G1 =(V1 ,Ad al grafului G=(V,A) este o


componentă tare conexă dacă:
1. \1 x, y ev1 , 3 drum de lax la y şi drum de la y lax.
2. Nu există un alt subgraf al lui G, G:~= (V2 , A 2 ) cu V 1C:V2 care
.' îndeplineşte condiţia 1.

&%: ·
. !\:
~

Graful alăturat are patru componente


tare conexe:
subgraful care conţin e vârfurile:
1 2 3
subgraful care conţine vârfurile:
5 7
subgraful care conţine vârful 4
subgraful care conţine vârful 6

Figura 9.26.

O Problema 9.8. Fie un graf orientat G= (V, A), memorat prin intermediul matricei de
adiacenţă . Se cere să se determine vârfurile fiecărei componente tare conexă.

0 Rezolvare
a) Vom numi succesori ai vârfului i, toate nodurile j, pentru care există drum de la
i la j, la care se adaugă i. De exemplu, pentru graful dat., succesorii vârfului 1 sunt
vârfurile 1, 2, 3 şi 4. Pentru a determina toţi succesorii vârfului i, vom efectua o
parcurgere DF a grafului pornind de la acest vârf. Succesorii nodului i vor fi reţinuţi în
vectorul suc.
276 Capitolul 9. Introducere în teoria grafurilc·

b) Fie i un vârf al grafului. Vom numi predecesori ai vârfului i, toate vârfurile j


pentru care există drum de la j la i , la care se adaugă i. Pentru graful dat
predecesorii vârfului 1 sunt: 1, 2 şi 3.

c) Dacă un vârf este simultan succesor şi predecesor al lui i, atunci el va face parte
din componenta tare conexă a vârfului i. Mulţimea nodurilor cu această proprietate va
fi o componentă tare conexă a grafului. De ce? Pentru că între două vârfuri k şi 1
există atât drum de la k la 1 (de la k lai şi de lai la 1) cât şi drum de la 11a k (de la
1 la i şi de la i la k). Mulţimea nodurilor cu această proprietate este maximală î~
rap.ort cu relaţia de incluziune. Dacă, prin absurd, ar mai exista un vârf cu această
proprietate, care nu aparţine acestei mulţimi, atunci ar trebui să existe drum de lai la
el, şi de la el la i, caz în care acesta ar fi fost găsit prin procedeul dat.

d) De acum, putem redacta algoritmul. Variabila nrc, cu valoarea iniţială 1, va reţine


numărul curent al componentei tare conexe care urmează să fie identificată. Fiecare
componentă a vectorilor suc şi pred reţine, iniţial, valoarea o.

pentru fiecare vârf i


dacă suc[i]=O
toţi succesorii lui i, inclusiv i, vor reţine nrc;
toţi predecesorii lui i, Inclusiv i, vor reţine nrc;
toate componentele i, pentru care suc [il rţ.pred [il vor reţine o;
se incrementează nrc.

se afişează vârfurile fiecărei componente conexe.

Mai jos, puteţi observa evoluţia vectorilor suc şi pred:

1234567 1234567

suc 1 1 1 1 o o o suc 1 1 1 o o o o
pred 1 o o o o pred
1 1 1 1 1 o o o o
1234567 1234567

suc 1 1 1 2 o o o suc 1 1 1 2 o o o
pred 2 2 o pred 1
1 1 1 2 1 1 2 o o o

1234567 1234567

suc l. 1 1 2 3 o 3 suc 1 1 1 2 3 4 3
pred 1 1 1 2 3 o 3 pred 1 1 1 2 3 4 3
Manual de informatică pentru clasa a Xl- a

Programul este prezentat în continuare:

.... Varianta Pascal


.;·· 1
Varianta C++
1,.,

uses grafuri; #include "grafuri.cpp•


var suc,pred:array[l .. 50] of int suc[50], pred[SO],
byte; A[SO] [50], n,nrc,i,j;
A:mat_ ad;
n,nrc,i,j:integer; void df_rl(int nod)
{ int k;
procedura df_ rl(nod:byte); suc [nod] =nrc;
var k:byte; for (k=l;k<=nlk++)
begin if (A[nod] [k]••l &&
suc[nod) :=nrc; suc [k] ==0)
for k : cl to n do df_rl(k);
if (A[nod,kl=l) and (suc(k]=O) }
then df_ rl (k);
end; void df_r2(int nod)
procedura df_ r2(nod:byte); { int k;
var k:byte; pred[nod)=nrc;
begin for (k=l;k<=n;k++)
pred[nod):=<nrc; if (A[k] [nodl==l &&
for k:=l to n do pred[k]==O)
if (A[k,nod]=l) and (pred[k)=O) df_r2 (k) 1
then df_ r2(k); }
end;
main()
begin (
CitireO( o Graf. txt o ,A, n); Ci tireO ("Graf. txt", A, n) 1
nrc:=l; nrc<=l;
for i:=l to n do for (i=l;i<=n;i++)
if suc[i)=O if (suc[i]==O)
then { suc[i]•nrc;
begin df_rl(i);
suc[i] :=nrc; df_r2 (i);
df_ rl(i); for (j=l;j<cn;j++)
df_ r2(i); if(suc[j]l•pred[j])
for j:=l ton do suc[j]apred[j]•O;
if suc[j]<>pred[j] then nrc++;
begin }
suc[j] :=0; for (i=l;i<nrc;i++)
pred[j] :=0; {
end; cout<<"Componenta"<<i<<en dl;
nrc:=nrc+l for (j=l;j<=n;j++)
end; if (suc[j]a..i)
for i: =l to nrc-1 do cout<<j<<" •;
begin cout<<endl;
wri teln ( o Componenta • , i) ; }
for j: =l ton do }
if suc[j]=i
then write (j, • •);
writeln;
end;
end.
278 Capitolul 9. Introducere în teoria grafurilor

Şi în cazul grafurilor orientate se păstrează noţiunea de graf conex şi noţiunea


de componentă conexă.

Definiţia 9.22. Graful orientat G=(V,A) este conex dacă Vx,yev,


3 lanţ de lax lay.

Definiţia 9.23. Subgraful G1 ==(V1 ,A1 ) al grafului G=(V, A) este o


componentă conexă dacă:

1 . V x, y ev1 , 3 lanţ de lax la y.


2. Nu există un alt subgraf al lui G, G2 =(V2 , A 2 ) cu V 1C:.V2 care îndepli-
neşte condiţia 1 .

Probleme propuse )
'-....
1. O cunoştinţă mi-a zis: la mine în birou suntem s persoane. Fiecare dintre noi
colaborează cu exact 3 persoane. A zis adevărul?
2. Demonstraţi că într-un graf neorientat numărul nodurilor de grad impar este par.
3. Fiind date n persoane şi :m relaţii de prietenie între ele de forma: persoana i este
prietenă cu persoana j, se cere să se stabilească corespondenţele între afirmaţiile
din stânga şi cele din dreapta.

1. În grupul celor n persoane, fiecare a. Graful asociat are noduri


persoană are cel puţin un prieten. terminale.
2 • Fiecare persoană este prietenă cu b. Graful asociat conţine un subgraf
oricare alta din grup. complet cu k noduri.
3. Există persoane care nu au decât c . Graful asociat este complet.
un singur prieten.
d . Graful asociat nu are noduri
4.. Există k persoane din grup astfel izolate.
încât oricare dintre ele este prietenă
cu toate celelalte k-1.

4. Se dau n drepte şi m relaţii de forma: d 1 11 dJ -dreapta i este paralelă cu dreapta


j . Se ştie că graful asociat este conex. Care dintre afirmaţiile de mai jos este falsă?

a) Pentru orice dreaptă i, există o dreaptă j care este paralelă cu ea.


b) Toate dreptele sunt paralele între ele.
c) Mulţimea punctelor de intersecţie ale acestor drepte este nevidă.

5. Între n firme există relaţii de colaborare. O relaţie de colaborare este dată ca o


pereche de forma i<=>j şi are semnificaţia că firma i colaborează cu firma j.
Stabiliţi corespondenţa între afirmaţiile din stânga şi cele din dreapta.
Manual de informatică pentru clasa a Xl-a 279

1 • Orice firmă colaborează cu toate a. Există un lanţ de la i la j.


celelalte.
b. Graful asociat are un nod de
2. Anumite firme işi întrerup relaţia grad n-1.
de colaborare.
c • Graful asociat se transformă
3 . Anumite firme dau faliment şi nu într-un graf parţial al său.
mai funcţionează.
d. Graful asociat este complet.
4. O firmă colaborează cu toate
celelalte. e. Graful asociat se transformă
într-un subgraf al său .
5 . Cu ajutorul unor firme interme-
diare dintre cel n firme, firma i poate
face afaceri cu firma j.

6. La un ştrand există 6 bazine. Unele dintre ele sunt unite printr-o ţeavă prin care
poate circula apa. Astfel, bazinul 1 este unit cu bazinul 2, bazinul 4 cu bazinul 5 şi
bazinul 2 cu bazinul 3.
6.1. Ştiind că fiecare bazin poate fi dotat cu un robinet, se cere num ăru l minim de
robinete care asigură umplerea tuturor bazinelor.
6.2. Care este numărul minim de ţevi prin care se pot uni două bazine, astfel încât să
se poată umple toate bazinele cu un singur robinet. Daţi exemple de bazlne unite care
asigură cerinţa problemei.

7. Fiind dat un grup de n persoane, în care dintre situaţiile de mai jos se poate
folosi pentru modelare un graf neorientat?
a) Unele persoane din grup cunosc alte persoane din grup;
b) Unele persoane din grup simpatizează alte persoane din grup;

o) În cazul în care toate persoanele lucrează într-o firmă, unei~ persoane din grup
sunt şefi pentru alte persoane din grup;
d) Unele persoane din grup sunt prietene cu
alte persoane din grup.

Exerciţiile a şi 9 se referă la graful din


figura 9.27.

8. Care este matricea de adiacenţă a


0
grafului? Figura 9.27.

1 1 1 1 o 1 o o 1 1 1o 1 o o 1 1 1o 1 o· o 1 1 1 o 1 o
1 1 ooo oo 1oo oo oo 1o o oo o o 1 ooo o oo
1 o 1 1 o oo 1 o o 1 o oo 1 o o l o o o 1 oo 1 o oo
1 o 1 1 o o 1 1 o 1 oo o 1 1 o 1 o o o 1 1 o 1 ooo 1
oo oo 1 oo ooo oo oo oo ooo 1 o 1 1 1 1 1 1 1
1 o ooo 1 1 l o o oo o 1 1 o oo o o 1 1 o
o o 1 oo
a) b) c} d}
280 Capitolul 9. Introducere în teoria grafurilor

9. Care este valoarea de adev~r a afirmaţiilor de mai jos (A, adev~rat, iar F, fals):

9.1. Graful este alc~tuit din 2 componente conexe.


9.2. [1, 7, 4] este un lanţ.

9.3. [2,1.,4,3,1] esteunciclu.


9.4. Nodul 2 este izolat.
9.5. Graful din problemă are ca graf parţial graful
din figura 9.28.
9.6. Graful din problemă are ca subgraf graful din
Figura 9.28.
figura 9.28.
9.7. Nodul1 are gradul 2.
9.8. Nodul 4 are gradul 3.
9.9. Graful din problem~ conţine un
ciclu de lungime 3.
9.10. Graful alăturat, reprezentat cu
ajutorul listelor de adiacenţă, este Figura 9.29.
subgraf al grafului din problemă.

10. Care dintre matricele de mai jos ar putea fi matricea de


adiacenţă a grafului alăturat?

o 1 o o o 1 o1o o 1 ooo
1 o o 1 o 1
1 o 1 o o 1 o 1 o o 1 o 1 o o 1 o 1 o
o1 o 1 o 1 o1 1 o 1o o o 1 o 1 1
1 o o 1 1o oo 1o1 1 oo o 1
oo o oo oo ooo o o o Figura 9.30.

a) b) c) d)

11. Care dintre matricele de mai jos poate fi matricea de ad iacenţă a unui graf
neorientat?

o 1 o 1o o ooo
1 o 1 o 1o
1 o l oo 1 o 1 o o l o 1 oo
o o o 1 o l o 1 o 1
1 o 1 1 o o 1 1o o 1
oo o oo o oo o
a) b) c) d)
Manual de i n formatică pentru clasa a Xl-a 281

12. Câte muchii are graful neorientat reprezentat de matricea de o o


adiacenţă alăturată? 1 o o
o o o
13. Care este numărul maxim de componente conexe pe care le
poate avea un graf neorientat cu 5 noduri şi 4 muchii?
o
o
14. Care dintre matricele de mai jos este matricea de
adiacenţă a unui subgraf al grafului din figura 9.31?

o o o
o o o
o o o
o
o
o
o
1
0000
1 o OI
o o o li
ro 1
1000
o
o o o
1
o
o
o
l

:l d 0
o o o o 0001 0000 o) Figura 9.31.
a) b) c) d)

15. Care este numărul minim şi care este numărul maxim de componente conexe
pe care le poate avea un graf neorientat cu 8 noduri şi 6 muchii?
16. Care este numărul de cifre o pe care îl reţine matricea de adiacenţă a unul graf
neorientat cu n noduri şi m muchii?
17. Care este numărul minim şi numărul maxim de noduri izolate pe care il poa.te
avea un graf neorientat cu 10 noduri şi 10 muctlii?
18. Care este num ă rul de grafuri neorientate cu 5 noduri?
19. Precizaţi care dintre afirmaţi ile urmă­
toare sunt adevărate ş i care sunt false.
Toate afirm aţiile se referă la graful din
figura 9.32.

19.1. "6 1 2 3 s 4 7" reprezintă o


parcurgere în adâncime a grafului.
19.2. "3 2 s 1 4 6 7" reprezintă o
parcurgere în lăţime a grafului. Figura 9.32.

19.3. Există două noduri din graf pentru care nu există un lanţ care le uneşte.
19.4. "6 1 2 3 5 4 7" este un lanţ în graful dat.
19.5. "3 2 5 1 4 6 7" este un lanţ în graful dat.
19.6. Numărul minim de muchii care trebuie eliminate pentru a obţine un arbore
parţial este 2.

19.7. Numă rul maxim de muchii care pot fi eliminate astfel încât graful să rămână
conex este 3 .
19.8. Numărul minim de muchii care pot fi eliminate pentru ca graful să nu conţină
cicluri este 3 .
19.9. Un arbore parţial al grafului dat are 7 muchii.
282 Capitolul 9. Introducere în teoria grafurilor

20. Precizaţi dacă afirmaţiile de mai jos sunt adevărate sau false.

20.1. Cu ajutorul parcurgerii în adâncime se poate determina dacă un graf neorientat


are cel puţin un ciclu.
20.2. Orice graf neorientat are un graf parţial care este arbore.

20.3. Cu ajutorul parcurgerii în lăţime se poate determina dacă un graf este conex.

20.4. Orice graf neorientat cu 10 noduri şi 9 muchii este un arbore.

20.5. Un arbore conţine exact o componentă conexă.

20.6. Un graf este alcătuit din două componente conexe. Pentru ca graful să devină
conex, este suficient să eliminăm o anumită muchie.
20.7. Un graf este alcătuit din două componente conexe. Fiecare dintre ele
alcătuieşte un graf parţial al grafului dat.

20.8. Un graf este alcătuit din două componente conexe. Fiecare dintre ele
alcătuieşte un subgraf al grafului dat.

20.9. Cu ajutorul parcurgerii în lăţime se poate determina, dacă există, un lanţ între
două noduri ale grafului.

20.10. Cu ajutorul parcurgerii în adâncime se poate determina, dacă există, un lanţ


între două noduri ale grafului.

20.11. Exista graf complet cu n>2 noduri care nu conţine cicluri.

20.12. Orice graf complet este alcătuit dintr-o singură componentă conexă.

20.13. Din orice graf complet, prin eliminarea anumitor muchii se poate obţine un
arbore.
20.14. Orice graf complet are un subgraf care este arbore.

21. Se da un graf neorientat memorat sub forma matricei de adiacenţa. Să se afişeze


toate nodurile care au gradul maxim.
22. Să se scrie un subprogram care transformă matricea de adiacenţ~ a unui graf în
liste de adiacenţe.

23. Să se scrie scrie un subprogram care transformă listele de adiacenţlj în matrice


de adiacenţlj.

24. Se dă un graf neorientat şi o succesiune de noduri ale lui. Se cere să se scrie un


subprogram care decide dacă succesiunea dată este sau nu un lanţ.

25. Se d ă un graf neorientat memorat prin liste de adiacenţă. Să se scrie un


subprogram care decide dacă graful dat conţine sau nu cicluri.

26. Se dă un graf memorat prin matricea de adiacenţ~ şi un nod al său, v. Se cere


să se parcurgă graful în lăţime, pornind de la nodul v. Algoritmul va utiliza coada
creată ca listlj liniară simplu inl~nţuitlj implementa~ static.
Manual de informatică pentru clasa a Xl-a 283

27. Se dă un graf memorat sub forma matricei de adiacenţă. Se cere să se


afişeze matricea drumurilor. Algoritmul va utiliza parcurgerea in adâncime.

28. Fiind dată matricea drumurilor unui graf, se cere să se scrie programul care
afişează componentele conexe.

29. Partiţia determinată de o relaţie de echivalenţă


Se consideră o mulţime A. O relaţie oarecare R între elementele acestei mulţi mi,
este o relaţie de echivalenţă dacă respectă următoarele trei condiţii:
• orica,re ar fi xeA, x R x (x este echivalent cu x), proprietate numită reflexivitate;
• oricare ar fi x,yeA, din x R y, rezultă y R x, proprietate numită simetrie;
• oricare ar fix, y, ze mulţimii A, din x R y şi y R z, rezultă x R z , proprietate
numită tranzltlvltate.

Se citeşte o mulţime de numere între o şi 255 prin citirea an perechi (x,y)


de numere de acest tip. Printr-o astfel de pereche se înţelege că x este echivalent
cu y. Se cere să se determine partiţia generată de relaţia de echivalenţă
considerată pe mulţime.

Exemplu: citim <1 2) , <4 s) , ( 2 3) , ( 6 7) , ( 7 1) .


Se obţine partiţia: {1,2,3,6,7} {4,5) a mulţimii {1,2, ••• ,7}.

30. Se dau n puncte distincte în plan: Pdx1 ,ytl cu OSxt,Yi~200, pentru orice
i=1, 2, ... , n. Considerăm că fiecare punct este unit cu cel mai apropiat punct
diferit de el (dacă există mai multe puncte la distanţă minimă, se uneşte cu fiecare
dintre acestea). Numim regiune o mulţime maximală de puncte cu proprietatea că
oricare dintre ele sunt unite printr-un lanţ. Să se determine numărul de regiuni şi să
se vizuallzeze regiunile (punctele şi legăturile dintre ele).
31. Se dă matricea de adiacenţă a unui graf neorientat. Se cere să se afişeze toate
ciclurile de lungime k. Un ciclu se va afişa o singură dată.
32. Se dă matricea de adiacenţă a unui graf neorientat. Se cere să se afişeze
toate ciclurile de lungime 4 . Se cere un algoritm eficient.
33. Într-un grup de n persoane, anumite persoane, împrumută altor persoane diverse
sume de bani! Modelând problema cu ajutorul grafurilor orientate, se cere să stabiliţi
corespondenţa dintre afirmaţiile din stânga şi cele din dreapta.

Observaţie: dacă persoana i împrumută cu bani persoana j, atunci există un arc de


lai la j.

1. Persoana i nu a împrumutat cu a) gradul interior al nodului i este o.


bani alte persoane din grup.
b) gradul exterior al nodului i este o.
2. Persoana i nu a împrumutat bani
de la alte persoane din grup.
284 Capitolul 9. Introducere în teoria grafurilor

34. Se dau n mulţimi de numere naturale: A 1 , A2 ... A". Acestor mulţimi li se asociază
un graf orientat astfel: dacă mulţimea A 1 este inclusă în mulţimea A:~, în graful asociat
vom avea arcul (A1 , A:~). Nu vom considera cazul de incluziune a unei mulţimi în ea
lnsăşi. Stabiliţi corespondenţa dintre operaţiile din stânga şi cele din dreapta.

1. AiCA:JCAlt. a) De la A 1 la An există un lanţ de


lungime n-1.
2. AtCA;~; A~tCA:I.
b) De la A~t la A1 există un lanţ de
3. A1.CA2C• •• A..-1c:A",. lungime 2.
4 • A1."'A2"' •• • A.:.-1 =A.,. c) Graful este tare conex.
d) De la Ai la Alt există un drum de
lungime 2.

35. Refaceţi problema anterioară în cazul în care se consideră n


numere naturale şi relaţia de divizibilitate. De asemenea, încercaţi
să adăugaţi noi situaţii în care se cere corespondenţa.

36. Câte componente conexe şi câte componente tare conexe


conţine graful din figura 9.33?

a) 1 1; b) 1 3; c) 1 o; d) o o.

Figura 9.33.

37. În graful din figura 9.34, care este lungimea celui mai
lung lanţ elementar şi care este lungimea celui mai lung
drum elementar?

a) 3 2; b) 2 2; c) 2 3; d) l 2.

Figura 9.34.

Problemele de la 38 la 41 se referă la graful alăturat:


38. Câte circuite conţine?
a) 3; b) 2; c) 1; d) 4.

39. Câte componente tare conexe conţine?


a) 4; b) 3; c) 2; d) l.

40. Care este nodul cu grad interior maxim şi care


este nodul cu grad exterior minim?
a) 1 1; b) 1 2; c) 2 2; d) 2 5. Figura 9.35.
Manual de informatică pentru clasa a Xl-a 285

41. Care este numărul minim de arce care trebuie adăugate pentru ca graful să
devină
tare conex?
a) 1 ; b) 2; c) 3; d) 4.
42. Se dă un graf orientat. Se cere să se afişeze, pentru fiecare vârf în parte, gradul
interior şi gradul exterior. Problema se va rezolva în cazul în care graful este dat prin
matricea de ad iacenţă şi în cazul în care el este dat prin liste de adiacenţă.
43. Fiind date un graf orientat şi o succesiune de vârfuri să se decidă dacă
succesiunea este drum, iar în caz afirmativ se va preciza dacă este sau nu un drum
elementar. Problema se va rezolva în cazul în care graful este dat prin mat.ricea de
ad iacenţă şi în cazul în care el este dat prin liste de adiacenţă .

44. La fel ca mai sus, dar se cere să se determine dacă succesiunea respectivă este
sau nu lanţ (lanţ elementar).
45. Se dă un graf prin lista muchiilor. Programul va decide dacă graful este
neorlentat.
46. Se dau listele de adi acenţe ale unui graf orientat. Programul va afişa matricea de
adiacenţă.

47. Se dă matricea de adiacenţă a unui graf orientat. Programul va afişa listele de


adiacenţe ale acestuia.
46. Se dă matricea de adiacenţă a unui graf orientat. Se cere să se listeze toate
circuitele de lungime 3.
49. Se dă matricea de adiacenţă a unui graf orientat. Se cere să se listeze toate
ciclurile de lungime 3.
50'. Algoritmul lui Lee. ' Se dă un labirint sub forma unei matrice pătratice, L.
L(i , j) =-1 dacă prin camera respectivă nu se poate trece şi o în caz contrar. Să
se afişeze distanţele minime de la camera de coordonate ( 1, c) la toate camerele
accesibile din camera iniţială.
51'. La fel ca la problema anterioară. Se cere drumul care trece printr-un număr
minim de camere între o cameră iniţială şi una finală.
52·. Pe o tablă de şah de dimensiuni nxn se poate deplasa un nebun conform
regulilor obişnuite ale şahului. În plus, pe tablă se pot afla obstacole la diferite
coordonate; nebunul nu poate trece peste aceste obstacole. Să se indice dacă
există vreun drum între două puncte A(X 1 , Yd şi B( X 2 , Y 2 ) de pe tablă şi, în caz
afirmativ, să se tipărească numărul minim de mută ri necesare. Se citesc: N, x 1 ,
Y1, x2. Y2, apoi perechile de coordonate ale obstacolelor.

53'. Sortare în limita posibilităţilor. Se consideră că într-un vector v cu n


componente se pot inversa numai conţinuturile anumitor componente dintre cele n. O
pereche de componente de indice i şi j ale căror conţinuturi se pot inversa este dată
de perechea i şi j. Fiind date m astfel de perechi şi ştiind că vectorul conţine
numerele 1, 2, ... , n într-o ordine oarecare, se cere ca vectorul să conţină numerele
1, 2, ... , n sortate. Pentru sortare se inversează numai conţinuturile componentelor
care se pot inversa (care sunt perechi dintre cele m). Dacă sortarea este posibilă, se
286 Capitolul 9. Introducere în teoria grafurilor

vor afişa indicii componentelor care se inversează, iar dacă sortarea nu este posibilă,
se afişează Nu. Datele de intrare se gc':isesc în fişierul text date. in astfel:

Linia 1 n
Linia 2 1, ... , n într-o ordine oarecare;
Linia 3 m
următoarele m linii conţin fiecare câte o pereche de indici i, j.

Exemplu:

3 Programul va afişa:
3 1 2 1 2
2 2 3
2 3
1 2

54·. Lucrare în echipă. Se doreşte scrierea unei aplicaţii de informare a călătorilor


privind transportul în comun într-un oraş. Se cunosc cele n staţii de autobuz din oraşul
respectiv. De asemenea, se ştie traseul a k linii de autobuz (staţiile prin care acestea
trec). Se cere ca aplicaţia să furnizeze modul în care o persoană se poate deplasa cu
autobuzul între două staţii date, în ipotezele:
a) În număr minim de staţii.
b) Prin utilizarea unui număr minim de linii de autobuz.

1Este sarcina dvs. să organizaţi intrările şi ieşirile de date.

Răspunsuri

1. Nu. Ar rezulta un graf cu 5 noduri. Cum fiecare persoană colaborează cu exact 3


persoane, înseamnă că fiecare nod are gradul 3. De aici, rezultă că suma gradelor
este 15. Aceasta înseamnă că 2m=15, deci m nu ar fi număr întreg.
2. Dacă ar fi impar, suma gradelor impare ar fi un număr impar. Cum suma gradelor
pare este un număr par, rezultă că suma tuturor gradelor este un număr impar. Ori,
acesta trebuie să fie un număr par, pentru că ea este egală cu dublul numărului de
m uchli. Absurd.
3. 1-d, 2-c, 3-a, 4-b.
4. c) Faptul că graful este conex, înseamnă că între oricare două noduri există un
lanţ care le are ca extremităţi. Dar din d 1 11 dj şi dj 11 dk => d 1 11 dk rezultă că toate
dreptele sunt paralele între ele.
5.1-d,2-e,3- c,4-b,S-a.
6. 6.1. Graful conţine 3 componente conexe. Pentru fiecare componentă conexă
este necesar un robinet. 6.2. Pentru a folosi un singur robinet, este necesar ca gr~f\Jl
să fie conex. Cum are 3 componente conexe, sunt suficiente 2 ţevi.
Manual de informatică pentru clasa a Xl- a 287

7. Pentru a putea modela anumite relaţii cu ajutorul unui graf neorientat trebuie ca
relaţia existentă între i şi j să fie reciprocă, pentru că muchia (i, j) presupune căi
este in relaţie cu j şi j este în relaţie cu i. Dacă i cunoaşte pe j, nu este obligatoriu
ca j să cunoască pe i, dacă i simpatizează pe j, nu este obligatoriu ca j să
simpatizeze pe i , dacă i este şeful lui j, j nu poate fi şeful lui i. În concluzie,
răspunsul este d) pentru că relaţia de prietenie este reciprocă.

8. b).
9. 9.1. A; 9.2. F; 9.3. F; 9.4. Ft 9.5. F; 9.6. A; 9.7. F; 9.8. Al 9.9. A;
9.10. A.
10. b) Dacă matricea este de adiacenţă, atunci vă puteţi orienta după gradele
vârfurilor. Evident, graful reprezentat de matricea de adiacenţă trebuie să aibă
vârfurile cu aceleaşi grade cu vârfurile grafului reprezentat în desen.
11. d) Desigur, puteţi desena graful, dar, mai uşor, elim inaţi variantele în care
aveţi 1 pe doagonala principală, sau acelea în care matricea nu este simetrică.
12. 8. Dacă matricea este dată corect, nu este nevoie să desenaţi graful pentru
ca, apoi, să-i număraţi muchiile. Se ştie că suma gradelor tuturor nodurilor este
egală cu dublul numărului de muchii. Prin urmare, este suficient să însumaţi
elementele reţinute de matrice si să împărţiţi rezultatul la 2 .
13. 2.
14. a) Dacă matricea are 4 linii şi 4 coloane, este clar că subgraful ar rezulta prin
eliminarea unui singur nod şi a muchiilor incidente lui. Dacă eliminăm nodul din
centru, se obţin 4 noduri izolate. Oricare alt nod am elimina, rămân 1 nod cu
gradul 3 şi 3 noduri cu gradul 1.
15. 2 componente conexe şi 5 componente conexe.
3
16. n -2m. Matricea de adiacenţă are n 3 elemente. Am văzut faptul că suma
tuturor cifrelor de 1 (adică a gradelor vârfurilor) este 2m (unde m este număru l de
muchii).
17. o şi 5.
10
18. 2 •

19. 19.1. A, 19.2. A, 19.3. F, 19.4. A, 19.5. F, 19.6. F, 19.7. A,


19.8. A, 19.9. F.
20. 20.1. A, 20.2. F, 20.3. A, 20.4. F, 20.5. A, 20.6. F, 20.7. F,
20.8. A, 20.9. A, 20.10. A, 20.11. F, 20.12. A, 20.13. A, 20.14. A
(lăsăm un singur nod).

29., 30. Descompunerea unui graf în componente conexe.


31. Backtracking. O soluţie are lungimea k.
32. Fie i<j<k<l, 4 noduri care formează un ciclu. Avem:
A(i,j)=l A(i,l)=l
A(k,j)=l A (k,l)=l
288 Capitolul 9. Introducere în teoria grafurilor

Astfel, în matricea de adiacenţă se formează un dreptunghi. Trebuie


identificate toate dreptunghiurile astfel formate.
33. 1-b, 2-a.
34.1-d, 2-b, 3-a, 4-c.
36. b).
37. a).

38. c).

39. b).
40. c).

41. a).

50. Se poate lucra direct pe matricea L. Idee: pentru camera iniţială vom avea
L(i,j)=l. Apoi, pentru toate camerele accesibile vecine cu ea vom avea
L(i ,j)=2, apoi pentru toate camerele accesibile cu ele vom avea L(i,j)=3 ...
ş.a.m.d. Pentru a obţine această marcare vom parcurge în lăţime graful asociat.
Putem evita memorarea acestuia. Vom introduce în coadă coordonatele camerei
iniţiale. Vom încărca în coadă coordonatele tuturor camerelor vecine pentru care
L ( i, j) =1. Pentru fiecare astfel de cameră, pentru care, iniţial , L <i, j) =O, vom avea
L(i, j )=2. Se trece apoi la următorul element din coadă cu care se procedează
asemănător. Se ştie că, prin parcurgerea în lăţime, se vizitează nodurile în
ordinea lungimii drumului, de la ele la nodul iniţial. Deducen:!, astfel, că marcarea
este corectă. Algoritmul se termină când coada este vidă. In final, se afişează
matricea L.

51. Se procedează ca la problema anterioară. Imediat ce a fost vizitată camera finală,


se reface drumul de la camera iniţială către ea. Astfel, se pleacă de la camera finală,
marcată cu k. Printre vecinele acestei camere se caută una care este marcată cu k-1.
Printre camerele vecine cu ea se caută una care este marcată cu k-2. Se procedează
în mod asemănător până se ajunge la camera iniţială marcată cu 1. Drumul se
âfişează în ordinea inversă găsirii lui, de la camera finală la cea iniţială .

52. Algoritmul lui Lee.

53. Asociem problemei un graf neorientat. Nodurile sunt indicii elementelor vectorului,
de la 1 la n. Când conţinuturile a două elemente se pot inversa, nodurile
corespunzătoare sunt unite printr-o muchie. Dacă nodurile i1, i 2, ... , ik sunt unite
printr-un drum: atunci intersch imbărlle (i1, i2), (i:~, i3), ... , (ik_1 , ik),
( ik-1• ik-2), ... , ( i:~, i 1 > inversează conţinuturi le elementelor de indice i 1 şi ik,
lăsând conţinuturile celorlalte elemente de indici i 2 , ... , ik-l nemodificate. O
parcurgere în lăţime determină distanţa minimă între două noduri.
Anexa 1
Memento

A.l. Limbajul Pascal

A.l.l . Tipuri standard

A) Tipuri întregi

' .. , . '' . ~ .,. " . ' . . ocupă · • .:::.!; \.•' ; •, ' .~! + ,,\:'~::l:·'·"'l~dfl.;
Numetlp
. ' · J· ·, ~.emnlflcaţle ·,. ·;?;:;Valori admlse '·;;·..··ifir' ·'i
., ;:. ' (blti) ' ' ,, '!* ;:; !;t• '•:: ' '.. + .... :·:·.~>::;;· l

shortint întreg scurt 8 de la -128 la 127


integer întreg 16 de la -32768 la 32767

l.ongint de la -2147483648 la
întreg lung 32
2147483647
byte număr natural scurt 8 de la o la 255
word cuvânt 16 de la o la 65535

B > Tipul caracter - un caracter se notează între apostrofuri.


Exemple: 1
a 1
,
1
A 1 , 1 1 1 (caracterul '1' nu trebuie confundat cu n umă ru l1).

c) Tipuri reale
.
1 :~ v~torl aâ.mis~ (în·:m~dy;).'Hi!l
' ' · ocupă
., ·Nume. tip · :S~innlfi~atle.... i : •: (biti) ' • • 11 , ~ .. 'h ,11 t:.; ~~~:·,,,
' ' !'

vi rgu lă mobilă , [-1, 7X1038 , -2, 9x10-29] v


real 48
s implă precizie [2, 9x10" 29 , 1, 7X10 38)

vi rgu lă mobilă, [-3,4X10 38 , -1, 5X10" 45 ] V


single 32
s implă precizie [1. 5x 10" 45 , 3, 4X10 38)

double virgu lă m obilă, (-1,7X10308 , - sx10"324) V


64
dublă precizie (5x10"324 , 1, 7X10300)

vi rgu lă mobilă, [ - 1, 1X104932, -3,4X10" 49 H)


extended 80
format lung v [3, 4x10-4932 , 1, 1X1 04932)
comp virgulă mobi lă 64 ( -9, 2X10 18 , 9, 2X10 18)

D) Tipul logic - boolean- poate reţi ne doar două valori true şi false.
290 Anexa 1. Memento

A.l .2. Constante


A) Constante întregi. Sunt alcătuite dintr-o submulţime a numerelor întregi care
pot fi reprezentate în memoria calculatorului. Se pot reprezenta numere întregi
cuprinse în intervalul: [-2.147.483.648, 2.147.483.647).

B) Constante reale. Sunt alcătuite dintr-o submulţime a numerelor reale (mai


precis, a numerelor raţionale) care pot fi reprezentate în calculator. Modulul
4352
numerelor reale se găseşte în intervalul [3.4*10 ,1.1•104932). În locul virgulei se
foloseşte punctul.

Exemple: 2.34, - 45.26, 512E+23, -45.1E-3.


Ultimele două numere folosesc o scriere neîntâlnită în matematică. Ele reprezintă
numerele 512•1023 şi -45.1*10'3 .
C) Constante şir de caractere. După cum reiese şi din denumire, cu ajutorul lor
se reprezintă şiruri de caractere. Caracterele din şir pot fi specificate enumerându-le
între apostrofuri.
Exemplu: 'abc •.

O) Constantele simbolice. Acestea sunt constante care au în program un anumit


nume. Definirea constantelor simbolice precede definirea tipurilor utilizator şi se
realizează astfel:

const identificator = e xpresie;


identificator = expresie;

f. Expresiile care intervin în definirea constantelor trebuie să poată fi evaluate


1~'~· la momentul compilării programului.
Exemplu:
const NrMaxvalori • JOJ
Dim a NrMaXValori*2-1 J
Mesaj a 'NU exista solutie'#10#13J
PI = 3.141

A.l.3 . Operatori
A.l.3.1. Prioritatea operatorilor
lată cele 4 grupe de prioritate.

Grupa 1 (prioritate maximă): .


NOT, + (operator unar),- (operator unar).

Grupa 2 (operatorii din grupa doi se mai numesc şi operatori multiplicativi):


AND, *, /,DIV,MOD.
Manual de informatică pentru clasa a Xl-a 291

Grupa 3 (operatorii din această grupă se numesc şi operatori adltlvl):


OR, XOR, +, -.

Grupa 4 (operatori cu cea mai mică prioritate- cuprinde şi operatorii relaţionali):


<, <=, >, >=, =,<>.

A. 1 .3.2. Operatori aritmetici

Operatorii aritmetici sunt de două feluri:

• operatori unari (+ şi - );
• operatori binarl ( +, -, •, /, DIV, MOD).

Operatorul +. Are de adunare. Operanzii săi sunt de tip întreg


semnificaţia
sau real. Se poate ca unul să fie de tip întreg şi celălalt de tip real. Dacă cel
puţin unul din operanzi este, real rezultatul este de tip real, altfel este de
tip întreg.

.t•• Operatorul + apare şi ca operator unar. De asemenea, apare ca sumă de


şiruri (caz pe care nu-l discutăm acum) .

Operatorul -.Are semnificaţia de scădere. Operanzii sunt de tip întreg sau


real. Dacă cel puţin un operand este real, rezultatul este de tip real, altfel
este de tip întreg.

Operatorul •. Are semn ificaţia de înmulţire. Operanzii sunt de tip intreg sau
real. Dacă cel puţin unul din operanzi esti de tip real, rezultatul este de tip
real, altfel rezultatul este de tip întreg.

Operatorul /. Are semnificaţia de împă~ire. Operanzii pot fi de tip întreg sau


real dar, intotdeauna rezultatul este de t(p real.

Operatorul DIV. Are semnificaţia de împărţire întreagă. Operanzii sunt în


mod obligatoriu de tip intreg. Este pbligatoriu ca fiecare operand să fie
separat cu cel puţin un spaţiu de operator. Rezultatul este de tip întreg.

·'~·'
Operatorul DJ:V furnizează rezultat corect numai dacă ambele valori sunt
• numere întregi pozitive .

Rezultatul pentru operatorul DIV se obţine astfel:

• se face împărţirea întreagă a celor două numere care sunt considerate


pozitive (de exemplu, 13 div 4=3);

• semnul câtului se stabileşte după regula semnelor(+ cu + rezultat +, +


cu - rezultat -, - cu- rezultat + şi -cu+ rezultat - ).
292 Anexa l . Memento

-+ Operatorul MOD. Are semnificaţia de rest al împărţi rii pentru numere întregi.
Operanzii sunt în mod obligatoriu de tip întreg, iar rezultatul va fi întotdeauna
de tip întreg. Operanzii trebuie separaţi de operator prin cel puţin un spaţiu.

~;.. Ca şi în cazul operatorului DJ:V, rezultatul este corect numai dacă ambii
~~· operanzi sunt pozitivi. Fie a MOD b. Rezultatul se obţine astfel:
a - a DIV b (R=D-ÎxC).

A.l.3.3. Operatori relaţionali

-+ Operatorul < (mai mic). Fiind daţi doi operanzi a ş i b, operatorul < arată
dacă este adevărată sau nu rel aţia a<b. Dacă relaţia este adevărată
rezultatul va fi TRUE, altfel rezultatul va fi FALSE.

-+ Operatorul <= (mai mic sau egal). Fiind daţi doi operanzi a şi b, operatorul
<= arată dacă este adevărată sau nu relaţia a<=b. Dacă relaţia este
adevărată rezultatul va fi TRUE, altfel rezultatul va fi FALSE.

-+ Operatorul > (mai mare). Fiind daţi doi operanzi a şi b, operatorul > arată
dacă este adevărată sau nu relaţia a>b. Dacă relaţia este adevărată
rezultatul va fi TRUE, altfel rezultatul va fi FALSE.

-+ Operatorul >=(mai mare sau egal). Fiind daţi doi operanzi a şi b, operatorul
>= arată dacă este adevă rată sau nu relaţia a>=b. Dacă relaţia este
adevărată rezultatul va fi TRUE, altfel rezultatul va fi FALSE.

-+ Operatorul= (egal}. Fiind daţi doi operanzi a şi b, operatorul = arată dacă


este adevărată sau nu relaţia a:::b. Dacă relaţia este adevărată rezultatul va
fi TRUE, altfel rezultatul va fi FAilSE.

A.l .3.4. Operatori logici

-+ Operatorul NOT (negare). Modul de acţiune se poate observa mai jos:


NOT (TRUE)=FALSE;
NOT (FALSE)=TRUE.

-+ Operatorul AND (şi). Regula de obţinere a rezultatului este foarte s implă :


rezultatul este TRUE numai dacă ambii operanzi au valoarea TRUE (altfel
rezultatul este FALSE).

-+ Operatorul OR (sau). Regula este s impl ă : dacă unul dintre operanzi este
TRUE, rezultatul este TRUE, altfel rezultatul este FALSE.

-+ Operatorul XOR (sau exclusiv). Ş i aici, regula este foarte simplă: dacă
argumentele sunt diferite rezultatul este TRUE, contrar el este FALSE.
Manual de informati că pentru clasa a Xl- a
----------------------------~

Dacă operanzii sunt de tip întreg, se pleacă de la reprezenta rea :::r.


numerelor.

-+ Operator ul NOT (negare) este unar. Transformă toţi biţii 1 în o şi invers


-+ Operator ul AND (şi) este binar. Se tace ŞI logic pentru toate perechile de
biţi aflaţi pe aceeaşi poziţie a celor doi operatori. Dacă ambii biţi sunt
1,
rezultatul este 1, iar în orice alt caz, rezultatul este o.
-+ Operatoru l OR (SAU) este binar. Se face SAU logic pentru toate perechile de
biţi aflaţi pe aceeaşi poziţie a celor doi operatori. Dacă cel puţin un bit este
1
rezultatul este 1, altfel, rezultatul este o.

-+ Operatoru l xoR (sAu EXCLUSIV ) este binar. Se face XOR pentru toate
perechile de biţi aflaţi pe aceeaşi poziţie a celor doi operatori. Dacă biţii sunt
diferiţi rezultatul este 1, altfel, rezultatul este o.

A.l .4. In strucţiu ni

1. Instrucţiunea vidă. Nu se trece nimic, dar totuşi există. O succesiune de


separatori '; 'indică prezenţa mai multor instrucţiuni vide.

2. Instrucţiunea de atribuire este de forma: v:=expre sie, unde veste numele


unei variabile.

Principiul de executare este u rmătorul:

• se evaluează expresia;
• variabila v ia această valoare.

Regula fundamentală este următoarea: tipul expresiei trebuie să coincidă cu


tipul variabilei v.

3. Instrucţiu nea IF. Există două forme ale acestei instrucţiuni:

Forma 1.

IF expresie logică THEN instrucţiune1


ELSE lnstrucţlune 2

Principiul de executare este următorul:

• se evaluează expresia logică;

• dacăaceasta ia valoarea TRUE, se execută instrucţiunea plasată după


THEN, în caz contrar se execută instrucţiunea plasată după ELSE.
294 Anexa 1 . Memento

Forma 2.

IF expresie logică THEN instrucţiune.

Principiul de executare este următorul:

• se evaluează expresia logică;

• în situaţia în care aceasta are valoarea TRUE, se execută instrucţiunea


aflată după THEN, în caz contrar se trece la instrucţiunea următoare.

4. lnstructiunea compusă. Se utilizează pentru a putea scrie mai multe


instrucţiuni 'care vor fi interpretate de compilator ca una singură. Instrucţiunile se
scriu între begin şi end.

5. Instrucţiunea CASE. Corespunde structurii alternative multiple.

Forma generală a instrucţiunii CASE este:


CASE expresie ordinală OF
c1,[c2, ...,cn1]: instrucţiune1;
Q'~Q>t, ... ,{kt\: \ns\mc~un~;
Z1,lZ2, ...,z"p): \nstrucţiunep
[ELSE Instructiune;
ENO

Aici, c1, ... ,cnl•····znp reprezintă constante de acelaşi tip ca şi expresia ordinală.

Principiul de executare este următorul:

• se evaluează expresia ordinală;


• se execută acea instrucţiune care are în faţă constanta obţinută în
evaluarea expresiei;
• în situaţia în care nici una din instrucţiunile 1 .. . p nu este precedată
de acea constantă, se execută instrucţiunea plasată după ELSE;
• dacă şi clauza ELSE este absentă, se trece la instrucţiunea următoare .

6. Instrucţiunea WHILE. Reproduce structura cât timp. . . execută.

Forma generală este:

WHILJ,!! expresie logică DO instrucţiune

Principiul de executare este următorul:

• se evaluează expresia logică şi în caz că aceasta are valoarea TRUE


se execută Instrucţiunea, se evaluează din nou ex;::>resia, dacă aceasta
are valoarea TRUE se execută din nou instrucţiunea; procedeul
continuă până când, la evaluarea expresiei, se obţine FALSE.
Manual de Informatică pentru clasa a Xl-a

7. Instrucţiunea REPEAT. Această instrucţiune reproo..;ce s.-::s:::::..::: ~

UNT:rL şi are forma generală:

REPEAT
Îti
Î2i

In
UNTIL expresie logică

Aici, i 1, i 2 , ••• , in reprezintă instrucţiuni.

Principiul de executare este următorul:

• se execută secvenţa de instrucţiuni ;


• se evaluează expresia logică;
• dacă aceasta ia valoarea FALSE se execută din nou secvenţa de
instrucţiuni, contrar se trece mai departe.

8. Instrucţiunea FOR . Atunci când cunoaştem de câte ori se execută o secvenţă


este bine să se utilizeze instrucţiunea FOR. Ea are două forme, prezentate în
continuare.

Forma 1.
FOR variabilă := expresie 1 TO expresle2 DO instrucţiune
unde:
- variabila poate fi de orice tip ordinal (de exemplu, de tip integer, char
sau boolean, dar în nici un caz de tipul real);
- expresie1 , expresie2 sunt expresii de acelaşi tip cu variabila.

Principiul de executare este următorul :

Pasul 1. Se evaluează cele două expresii.


Pasul2.
Pasul 2.1.
• Dacă valoarea obţinută în urma evaluării expresiei 1 este strict mai
mare decât valoarea obţinută în urma evaluării expresiei 2,
executarea FOR este încheiată ;
• Dacă valoarea obţinută în urma evaluării expresiei 1 este egală cu
valoarea obţinută in urma evaluării expresiei 2, se atribuie variabilei
de ciclare valoarea obţinută în urma evaluării expresiei 1, se execută
instrucţiunea subordonată şi executarea FOR este încheiată;

• Dacă valoarea obţinută în urma evaluării expresiei 1 este strict mai


mică decât valoarea obţinută în urma evaluării expresiei 2, se
atribuie variabilei de ciclare valoarea obţinută în urma evaluării
expresiei 1 şi se trece la 2.2.
296 Anexa 1. Memento

Pasul 2.2. Se execută instrucţiunea subordonată.

Pasu/2.3.
• Dacă valoarea reţinută de variabila de ciclare este strict mai mică
decât valoarea obţinută în urma evaluării expresiei 2 (evaluare
efectuată la început) se adună 1 variabilei de ciclare şi se trece
la2.2.
• Dacă valoarea reţinută de variabila de ciclare este egală cu valoarea
obţinută în urma evaluării expresiei 2, executarea instrucţiu nii FOR
se încheie.

Forma 2.

FOR variabilă := expresie1 DOWNTO expresie2 DO instrucţiune

~ ft În acest caz, variabila de ciclare scade la fiecare pas .



A.l.S. Câteva funcţ i i utile

)> sinus:9t~[-1,1]; are forma generală:

function Sin(X: Real) : Real ;

)> cosinus:~>l~f-1,1); are forma generală:

funct ion Cos(X: Real): Re al; {cosinus (x)}

)> arctangent:9t~(-n/2,n/2); are forma generală:

function ArcTan(X: Re al): Real; {arctangent (x)}

Atenţie: argumentul trebuie exprimat in radiani. Celelalte funcţii uzuale


se obţin prin aplicarea formulelor trigonometrice.

F.uncţia exponenţială f:9t~9t+, unde f(x)=e>< (e este un num ăr iraţional,


e=2. 71} are forma generală:

function Exp(X: Re al) : Real;

)> Funcţia logaritmică f:9t+~9t, unde f(x)=ln (x) este funcţia inversă funcţiei
exponenţiale şi are forma generală:

function Ln(X: Real) : Real ;

Funcţia logaritmică are următoarele proprietăţi:

pentru A, B >O, avem : In(A· B) = In(A)+ Jn(B); In( A8 ) = B ·ln(A).


Manual de informatică pentru clasa a Xl- a 297

Din faptul că funcţia logaritmică este inversa funcţiei exponenţiale şi din a


doua de mai sus, deducem pentru x>O, y oarecare:
relaţie

xY =eln(~Y) =eY·In(x}.
Aceasta înseamnă că putem calcula xY - pentru că funcţi a putere nu există
în Pascal: exp(y*ln(x) ).
2
)> Funcţia pătratică f:9t~9t+, unde f(x)=x :

function Sqr(X: Real): Real;

unde x este o valoare întreagă sau reală.

)> Funcţia radical f:9t+~9t+, unde: f(x) =./X, are forma generală:

function Sqrt(X: Real): Real;

)> Funcţia "parte întreagă"


function Int(X: Real): Real;

returnează partea întreagă a lui x.

~ Atenţie: dacă
x este negativ, rezultatul este eronat.
• Exemple: int ( 2. 75) returnează 2 . o, int ( -2.75) returnează -2. o.

> Funcţia de trunchiere


function Trunc(X: Real): Longint;

returnează valoarea trunchiată a argumentului.

)> Funcţia "parte fracţlonară"


function Frac(X: Real): Real;

returnează X-Int (X).

)> Funcţia de rotunjire


function Round(X: Real): Longint;

returnează valoarea rotunjită a lui x.

)> Funcţia "valoare absolută"


function Abs(X);

returnează 1-tl.
298 Anexa 1. Memento

A.2. Limbajul C++


A.2.1. Tipuri standard
Limbajul C++, în varianta Borland C++, admite tipurile de mai jos:

A) Tipuri întregi
'" ' ':fi': ..•• ,. ,..• ·'
~:i;Fl, 1 · ·· ·l'fl'" ",'1;! rl• · ~~;:!.~!:J •. ;:!!;· i :.J \~~JJ:!~:Jj . ;.;:;l: ~·~ i!,j.:t~. ~t~i!.,:~~~~-,·'
s;ffiriWibaţ~e ·
:t·1.!J•tr-l·,''
·. Qcup~ : !''P
'· •Nu.pe·tlp 1
1.
,.1
• ' . '• ;., ' '4 ' (biţi)
' '":'''•Valori admise
-!.: ;," .., :,., '
.t;:~
.
.
unsigned
char caracter fără semn 8 de la o la 255
char caracter 8 dela -128 la 121
unsigned
int întreg fără semn 16 dela o la 65535
int întreg 16 de la -32768 la 32767
unsigned Tntreg lung fără
long 32 dela o la4.294.967.295
semn
long de la -2.147.483.648 la
întreg lung cu semn 32
2 . 147.483.647

B) Tipuri reale
'.
~~me tip
'
l;l' , Ocupă '~
Se~nlflcaţle 'j'.!;(biţi).. ' ' Valori adml~e (in modul) ".
"'':·· !.!:!•!i.,,, " ,;: •' 1;11 'ii.:l··· ·;.,

float virgulă mobilă,


32 [3. 4 x1o - 38
, 3. 4x10 39 J
simplă precizie

double virgulă mobilă,


64 [1. 7x1o- 308 ,1. 7X10. 308 ]
dublă precizie

long virgulă mobilă,


double dublă precizie 80 [3.4X10- 4932, 1.1 x1o•nnl
format lung

A.2.2. Constante
1. Constante întregi. Acestea se clasifică astfel:
• zecimale (în baza 10). Exemple: 23, 1239, 56.
• octale (în baza 8). O constantă în baza 8 se declară precedată de un o
nesemnificativ. Exemplu: 0123 . Se reţine numărul intreg 123(8) .
• hexazecimale (în baza 16). Acestea sunt precedate de ox sau ox.
Exemplu: pentru OX1A2 adică 1A2 116> sau Ox1a2, adică 1A2 1161 •
Manual de informatică pentru clasa a Xl-a 299

2. Constante caracter. Acestea se trec între două caractere apostrof ( • ).

Exemple: 'A', '1', 'a'.

Secvenţe escape. O secvenţă escape începe prin caracterul 1 1


3. (backslash). \

Să considerăm o constantă caracter • a Codul său este 97 <1o>=141<a>=61<16 >.


1

Printr-o secvenţă escape, constanta se introduce prin codul său într-una din bazele
8 sau 16. De exemplu, constanta a poate fi scrisă (echivalent) astfel: \141'
1 1 1

sau · \x61 În cazul când se foloseşte codul scris în baza 16, acesta este
1

precedat de caracterul X Uneori, pentru anumite caractere, se pot utiliza şi semne


1 1

speciale, aşa cum rezultă din exemplele următoare.


• backslash: • \\ • 1 • \134. • 1 • \xSc •;
• newline: • \n •, • \12 •, • \xa •;
• apostrof: '\''~'\4.7','\x27';
• bel: • \a • 1 ' \7 ' 1 • \x7 ';
• cr: • \r. 15., • \xd'.
1 '\

4.. Caractere albe (whltespaces). Au un rol special în cadrul operaţiilor de


citire/scriere. Acestea sunt:

• blank ( • • );
• tab orizontal ( • \ t • );
• newline ( • \n • );
• tab vertical ( • \ v •);
• cr (• \r•).

5. Constante reale

Exemple: -45.66, 1., • 2, o. 3, -2. s:a:-12, adică, 2. sx10- 12 .

6. Constante şir de caractere


Exemplu: "acesta este u~ text".

Pentru a da un nume constantelor folosim const. Forma generală a unei


astfel de declaraţii este (construcţia dintre paranteze drepte este opţională):

const [tip] nume=valoare;


unde:

• tip- reprezintă tipul constantei (dacă este absent, tipul este int);
• nume - reprezintă numele constantei;
• valoare - reprezintă valoarea constantei.
300 Anexa 1 . Memento

A.2.3. Operatori

A.2.3.l. Prioritatea operatorilor (în ordine descrescătoare)

Priviţi următorul tabel:

prioritate operator asociativitate

1 ()[] - >::. s ~ d
2 !- +- + +- - * ( typecast ) sizeof new de/ere d ~ s
3 . *- > * s ~ d
4 */% s ~ d
5 +- ,\' ~ d
6 << >> s ~ d
7 <<= > >= s ~ d
8 = != s ~ d
9 & s ~ d
1o 1\
s ~ d
Il s ~ d
12 & & s - ) d
13 11 s ~ d
14 ? : d ~ s
15 = * = 1 = + = - = & = 1\ = 1= <<= >>= d ~ s
16 s -~ d

A.2.3.2. Operatori aritmetici

În C++ există următorii operatori aritmetici:

• - minus (unar, adică acţionează asupra unui singur operand);


• + plus (unar);
• + (binar), pentru adunare;
• - (binar), pentru scădere;

• • (binar), are semnificaţia de înmulţire;


• 1 (binar), pentru împărţire;

• % (binar), restul împărţirii intregi.


Manual de informatică pentru clasa a Xl-a 301

~ Observaţii

1. Operatorul "/" (împărţire) acţionează în mod diferit in funcţie de operanzi:

a) dacă ambii sunt de tip


intreg, rezultatul este intreg şi are semnificaţia de
împă rţire întreagă . Cu
toate acestea, rezultatul este corect (din punct de
vedere matematic) numai dacă valorile care se împart sunt pozitive.

b) dacă cel puţin un operand este de unul din tipurile reale, rezultatul este
real (se efectuează împărţirea obişnuită).
2. Operatorul "%" acţionează numai asupra operanzilor de tip întreg. Rezultatul
obţinut este corect din punct de vedere matematic numai dacă ambii
operanzi sunt numere naturale.
3. În cazul în care se împart două valori intregi, se procedează astfel:
a) se face împărţirea intreagă a celor două valori care sunt considerate in
modul;
b) semnul câtului se stabileşte după regula semnelor (+ cu + rezultat +,
+cu-, rezultat-}, etc.

A.2.3.3. Operatori relaţionali

În C++ există următorii operatori relaţionali:

• <(mai mic);
• <=(mai mic sau egal);
• >(mai mare);
• >=(mai mare sau egal).
Rezultatul unei operaţii logice este 1, în cazul în care inegalitatea este
respectată şio, în caz contrar.

A.2.3.4. Operatori de egalitate

Aceştia sunt:

• == pentru egalitate;
• 1 = pentru inegalitate.

În cazul în care relatia indicată de operator este respectată, expresia


returnează 1 , altfel returnează o.
302 Anexa 1. Memento

A.2.3.5. Operatori de incrementare şi decrementare

Aceşti operatori sunt unari şi au rolul de a incrementa (adună 1) sau


decrementa (scad 1) conţinutul unei variabile. Operatorii sunt:
• ++ pentru incrementare;
• -- pentru decrementare.

Operatorii pot fi prefixaţi (aplicaţi Tn faţa operandului) sau postflxaţi (aplicaţi după
operand).

-+ Dacă operatorul este prefixat, variabila este incrementată (decrementată )


inainte ca valoarea reţinută de ea să intre Tn calcul.

-+ Dacă operatorul este postfixat, variabila este incrementată (decrementată)


după ce valoarea reţinută de ea intră in calcul.

A.2.3.6. Operatori logici

Există trei operatori logici:

• 1 - negare logică ;

• && - şi logic;
• 11 - sau logic.

Operatorul negare logică acţionează astfel: dacă operandul este o valoare


d iferităde o, rezultatul este o, altfel rezultatul este 1.

Operatorul şi logic (binar) acţionează astfel: dacă ambii operanzi sunt diferiţ i
de o, rezultatul este 1, altfel el este o.

Operatorul sau logic (binar) acţionează astfel: dacă cel puţin unul din
operanzi este o valoare diferită de o, rezultatul este 1, altfel rezultatul este o.

A.2.3.7. Operatori logici pe biţi


limbajul C++ este dotat cu un set de operatori care permit accesul la bit.
Aceştia sunt:
• <<, >> operatori de deplasare;
• &şi pe biţi;
• 1 sau pe biţi;
• A sau exclusiv pe biţi;

• - negare pe biţi (operator unar).

'~'.: Aceşti operatori acţionează numai asupra operanzilor de tip intreg .



Manual de informatică pentru clasa a Xl-a 303

Operatorul "<<' este binar. El are rolul de a deplasa către s~-,ga ::::....-:--...-:..
tuturor biţilor operandului din stanga sa, cu un număr de pozi~t ega cu :a :;a:;.:
reţinută de al doilea operand. Poziţiile rămase libere (în dreapta) vor reţine va10area o

Operatorul ">> " este binar. El are rolul de a deplasa către dreapta con~nutu
tuturor biţilor operandului din stanga cu un număr de poziţii egal cu valoarea reţ inută
de al doilea operand. Dacă operandul din stânga este de un tip întreg fără semn,
poziţiile rămase libere (în stanga) vor reţine valoarea o. Dacă al doilea operand
reţine valoarea m, o astfel de deplasare este echivalentă cu împărţirea întreagă cu
2"'. În cazul în care primul operand este un întreg cu semn, fiecare poziţie din
stânga rămasă liberă se completează cu valoarea reţinută de bitul de semn.

În cazul operatorilor binari "& ", "1", """, rezultatul se obţine aplicând pentru
fiecare pereche de biţi aflaţi pe aceeaşi poziţie regulile din tabelul următor. Atunci
când cei doi operanzi nu au aceeaşi lungime (dar numai atunci - de exemplu, dacă
ambii operanzi sunt de tip char şi rezultatul este de tip char), se aplică regulile de
conversie pentru expresii aritmetice.

"· of'.· 1
,.,OPz . OP',1&OP''::
.2 ,
. 0PtAOP2 , 11 • :;t:of.IOPz
.... '
1
. il, '' ;oljij

o o o o o
1 o o 1 1
o 1 o 1 1
1 1 1 o 1

Operatorul "-" (negare pe biţi) are rolul de a inversa conţinutul biţilor (dacă
un bit conţine o, va conţine 1 şi invers).

A.2.3.8. Operatori de atribuire

În C++ atribuirea este operator. În plus, în C++ avem mai mulţi operatori de
atribuire. Operatorul "=" se foloseşte intr-o expresie de forma:
v•expresie
Aici, v este o variabilă.

Principiul de executare este următorul:

• se evaluează expresia;

• variabilei v i se atribuie valoarea obţinută (dacă este cazul, se


efectuează conversia respectivă).

Se pot efectua şi atribuiri multiple de forma:

unde v, v 1 , •• • , vn sunt variabile.


304 Anexa 1. Memento

În acest caz, principiul de executare este următorul:

• se evaluează expresia;
·• valoarea obţinută este atribu ită variabilei vn (eventual convertită -dacă
este cazul);
• conţinutul variabilei vn este atribuit variabilei V 0 _1 (eventual, se
efectuează conversia necesară);

• conţinutul variabilei v 1 este atribuit variabilei v (eventual, se efectuează
conversia necesară).

Pentru atribuiri se mai pot utiliza şi operatorii: "*=", "!=", "%=", "+=", "-="
"<<=", ">>=", "&>", ""="sau "1=n.

O atribuire de f,orma: v op expresie, are acelaşi rezultat ca


v=v op expresie
(diferenţa este că, in primul caz, se generează un cod maşină eficient).

A.2 .3.9. Operatorul',' (virgulă)

C++ permite programatorilor să scrie mai multe expresii separa ~e prin


virgulă,
ca mai jos:

Întrucât, după cum rezultă din tabel, operatorul virgulă se asociază de la


stânga la dreapta, expresiile se eva luează în ordinea exp1, exp2, ... , exPn. S-a
convenit ca întreaga expresie (care cuprinde cele n expresii separate prin virgulă)
să producă ca rezultat valoarea obţi nută în urma evalui:irii ultimei expresii (evident,
tipul acestei valori este şi tipul expresiei).

A.2.3 .1O. Operatorul cond i ţional

Se foloseşte în expresii de genul:

Principit..f de executare este următorul :


• se evaluează exp1;
• dacă aceasta produce o valoare diferită de o, se evalu ează exp 2 ş i
exp3 este ignorată (nu se evaluează);

• altfel, se evaluează exp 3 şi exp2 este i gnorată.

În ansamblu, expresia este de tipul lui exp 2 sau exp3 şi produce valoarea
exp2 sau exp3 (în funcţie de cea care se eva lu ează).
Manual de informatică pentru clasa a Xl-a 305

A.2.3.11. Operatorul sizeof

Are rolul de a returna numărul de octeţi utilizaţi pentru memorarea unei


valori. Operatorul sizeof poate fi utilizat într-una din cele două forme prezentate
în continuare:
sizeof (expresie)
sizeof (tip)

A.2.3.12. Operatorul de conversie explicită

De multe ori, dorim ca unul sau mai mulţi operanzi să intre în calcul convertiţi
aşa cum dorim (nu implicit). Pentru aceasta, înaintea operandului se trece între
paranteze tipul său.
Exemplu: fie declaraţia: float x= -1.9;. Atunci: ( int )x•-1 (se face
conversia din float în int prin trunchiere).

A.2.4. Instrucţiuni

1. Instrucţiunea expresie este de forma:


expresie,.
La întâlnirea unei astfel de instrucţiuni, se evaluează expresia. În limbajul
C++ şio atribuire este o expresie.

2. Instrucţiunea "if" se poate utiliza în următoarele două forme:

Forma 1.
i f (expresie) instrucţiune 1 el se instrucţlune2

Principiul de executare este următorul:

• se evaluează
expresia;
• dacă valoarea produsă de aceasta este diferită de o, se execută
instrucţiune1;
• dacă valoarea produsă este o se execută lnstrucţiune2•

Forma 2.
if (expresie) instrucţiune

Principiul de executare este următorul:

• se evaluează expresia;
• dacă valoarea produsă de aceasta este diferită de o, se execută
instrucţiunea subordonată.
306 Anexa 1. Memento

3. Instrucţiunea compusă. Se uti l izează în cazul în care se doreşte ca mai multe


instrucţiun i să fie tratate de compilator ca o singură instrucţiune. Este de forma de
mai jos, unde i 1, i 2 , ... , i., sunt instrucţiun i :

4. Instrucţiunea "switch" are forma generală:

swi tch (expresie)


{case exp 1: secvenţă instrucţiun/ 1 ; break;
case exp2: secvenţă instrucţiun/2; break;

case expn: secvenţă instrucţlunln; break;


[de f aul t : secvenţă instrucţiunin+1J ;
}
unde:
expresie are semnificaţia: expresie de tip întreg;
exp1 sunt expresii constante de tip întreg;
instrucţiuni, reprezintă o secvenţă oarecare de instrucţiuni.

Principiul de executare:

• se evaluează expresia;
• dacă aceasta produce o valoare egală cu cea produsă de exp1, se
execută, în ordine, instrucţiuni, şi se trece la instrucţiunea următoare,
altfel se execută numai secvenţa instrucţlunim. t·

Alternativa default este facultativă. În absenţă, în cazul în care nu există


coincidenţ~ de valori, se trece la instrucţi unea următoare.

5. Instrucţiunea "while"

Această instrucţiune reproduce structura de tip Cât timp . . . execută.

Forma generală este:


while (expresie) instrucţiune

Principiul de executare este următorul:

• pasul 1: se evaluează expresia;


• pasul 2: dacă valoarea produsă de aceasta este diferită de o, se
execută i nstrucţiunea subordonată , apoi se revine la pasul 1, altfel se
trece la i nstrucţiunea următoare.
Manual de informatică pentru clasa a Xl-a 307

6. Instrucţiunea "do while". Traduce în limbaj structura Execută ... cât


timp. Forma generală a acestei instrucţiuni este următoarea:

do
instrucţiune
while(expresie);

Principiul de executare este următorul:

• pasul 1: se execută instrucţiunea subordonată;

• pasul 2: se evaluează expresia. În cazul în care valoarea produsă la


evaluare este o, execuţia instrucţiunii do se termină, altfel se trece
la pasul1.

7. Instrucţiunea "for" are forma generală:

for (expresielnlţlallzare; expresietest; expreslelncrementare) instrucţiune

După cum se observă, între paranteze se găsesc trei expresii:


expresie1n111auzare se foloseşte de regulă, pentru iniţializarea variabilei de
ciclare. Este de remarcat faptul că în cadrul acestei expresii (cu rol special)
este posibil chiar să declarăm variabila de ciclare (cu valoare iniţială).
expresie1est se foloseşte pentru a testa dacă se execută instrucţiunea
subordonată - dacă expresia produce la evaluare o valoare diferită de o,
instrucţiunea subordonată for se execută.
expresieinar-ntaro se foloseşte pentru incrementarea variabilei de ciclare.

Principiul de executare:

• pasul 1: se evaluează expresiE!Jnlpauzaro (un caz special este acela în


care aceasta conţine şi declaraţia variabilei de ciclare);
• pasul 2: se evaluează expresia1051• În cazul în care aceasta produce o
valoare diferită de o, se execută instrucţiunea subordonată for; apoi se
trece la pasul 3, altfel se trece la instrucţiunea următoare (se termină
execuţia instrucţiunii for).

• pasul 3: se evaluează expresia de incrementare şi se revine la pasul 2.

A.2.5. Câteva funcţii utile

Pentru a le utiliza, includeţi fişierul math . h: #include <math.h>.

~ Funcţia abs are forma generală : int abs ( int x) ; Rolul ei este de a
întoarce lx 1 (modulul lui x).
~ Funcţia fabs are forma generală double fabs(double x) ; are acelaşi
rol cu abs, numai că întoarce valoarea unui număr real (chiar double).
308 Anexa 1. Memento

-+ Funcţia labs are forma generală long int labs <long int x) ; şi
acelaşi rol cu abs, numai că întoarce valoarea unui întreg lung.
~ Funcţia acos are forma generală: double acos(double x); şi
calculează valoarea funcţiei arccos(x): [-1,1] -7 [O,n].

-+ Funcţia asin are forma generală: double asin(double x); şi

calculează valoarea funcţiei arcsin(x): [-1,1] -7 [- ~, ~].

-+ Funcţia atan are forma generală: double atan(double x); şi

calculează valoarea functiei arct<~(x): 9\ -7 ( - 7r , 7r ).


' o 2 2

-+ Funcţia atan2 are forma generală: double atan2 (double y, double x)


şi calculează arctg(Z). Rezultatul este în intervalul (- n ,n). Motivul?
X

~ Funcţia floor are forma generală double floor(double x); şi


calculează valoarea rotunjită a lui x (rotunjirea se face in minus).

Exempl~floor (123.78)=123,floor (-23 , 34)=-24.


~ Funcţia ceil are forma generală double ceil (douhle x); şi calculează
valoarea rotunjită a lui x (rotunjirea se face în plus).
Exemple: ceil ( 123 . 78)=124, ceil(-23,34)=-23.
-+ Funcţia cos are forma generală double cos (double x) 1 şi calculează
valoarea funcţiei cos(x): 9\ -7 [-1,1].

-+ Funcţia sin are forma generală double sin(douhle x) 1 şi calculează


valoarea funcţiei sin(x) : 9~ - >[- 1,1].

-+ Funcţia tan are forma generală double tan(douhle x) ; şi calculează

valoarea funcţiei tg( x ): 9\- {k ·7r + ~ lk E z} -7 9t


-+ Funcţia exp are forma generală douhle exp(douhle x); şi calculează
funcţia ex : 9\ -7 9\: .

-+ Funcţia log are forma generală douhle log(double x); şi calculează

funcţia ln(x): 9\: -7 9\, unde ln(x) =loge (x).

-+ Funcţia loglO are forma generală douhle loglO (double x) 1 şi


calculează funcţia lg(x): 9\: -7 9\, un.de lg(x) =Jog 10 (x).

-+ Funcţia pow are forma generală douhle pow(douhle x, douhle y);


şi calculează xY.
Anexa 2
Aplicaţii practice ale grafurilor

Cu siguranţă, unii dintre voi v-aţi pus o serie de întrebări referitoare la


aplicabilitatea teoriei grafurilor în problemele reale:

• unde pot utiliza grafurile şi de ce?


• există aplicaţii din alte domenii, in afară de Informatică, ce pot fi
rezolvate cu ajutorul teoriei grafurilor?
În fapt, după cum veţi vedea în continuare, grafurile sunt foarte utile într-o
multitudine de aplicaţii din diverse domenii, iar prin utilizarea lor, se poate obţine
o bună optimizare a resurselor (umane sau materiale) sau a timpului.

A.l. Reţele de comunicaţie

Comunicaţia între diversele dispozitive electronice din zilele noastre reprezintă


poate cea mai răspandită aplicaţie practică a teoriei grafurilor. Spre exemplu, dacă
ne referim la reţelele de calculatoare sau la Internet şi dacă considerăm ·fiecare
calculator ca fiind un nod, atunci vom avea un graf extrem de complex şi foarte
diversificat din punct de vedere al structurii. fn continuare, vom prezenta o schemă
de principiu care descrie o reţea de calculatoare, legată la Internet:

Switch 1 Switch 2

Subreteaua 1 Subreteaua 2
Figura A.1 . Exemplu de reţea de calculatoare legată la Internet
310 Anexa 2 -Aplicaţii practice ale grafuril or

~~ Observaţii

de
Structura anterioară este de tip arbore. Pe fiecare nivel însă, protocoalele
rea transmi siei
comunicaţie efectuează operaţii specifice pentru asigura
bidirecţionale între fiecare dispozitiv terminal (calculator).

pe
Router- ul este un dispozitiv electronic care decide calea (drumul optim)
eaua către un
care vor fi trimise informaţii l e de la un calculator din Subreţ 1,
la n~ndul său, în
altul din Subrete aua 2. La nivel local, Switch-ul decide
Control , identific ator unic pe glob) a
functie de adresa MAC (Media Access
fiedrci plăci de reţea, cărui destinatar îi este dedicat blocul de date. Pentru
a se conecta la reţeaua Internet, Router-ul are o legătură cu un
ISP
(Internet Service Provider).

Există o întreagă teorie legată de reţelele de calculatoare, dar ceea ce este


însă de reţinut este faptul că din punct de vedere
topologic, o reţea de
graf. Comunicaţia optimă
calculatoare se poate reprezenta sub forma unui
este realizat ă cu ajutorul
(calea cea mal scurtă între două noduri)
, cum ar fi: IP (Interne t Protoco ~, NAT
protocoalelor specializate de routare
(Routin g lnforma tion Protoco ~. etc.
(Network Address Translation), RIP

Protoco ale de routare


ile
Un protocol de routare are rolul de a obţine şi de a trimite informaţi
a să ia decizii la nivel
topologice ale reţelei către Router-e, permiţându -le acestor
care sunt
local. Fiecare Router deţine o serie de liste, numite tabele de routare, în
nodurilo r care au legătură fizică directă
memorate adresele (fizice şi logice) tuturor
cunoscu te şi parcurs e. Aceste liste trebuie
cu el ş i drumurile optime deja
reţelei.
reactualizata frecvent pentru a preveni anumite modificări topologice ale
i
Router-ele utilizează protocoalele de comunicaţie care au la bază algoritm
să determi r.e cea mai bună cale. Când ne referim la
de optimiz are ce trebuie
"hops") pe
drumul cel mai bun, avem în vedere numărul de "hopuri" (din engleză,
punct interme diar
care trebuie să le parcurgă datele până la destinaţie sau un alt
sau durata/viteza de trimitere a informaţiilor.
tipuri de algoritmi de routare mai importante, utilizate in funcţie
Există două
ale ale
de modalitatea router-ulul de a reţine şi de a analiza informaţiile structur
reţelei:
iile
• Algoritmi de routare globali. Fiecare router reţine toate informaţ
despre celelalte router-e existente Tn reţea şi despre trafic. Când se
porneşte un astfel de router, el trimite un mesaj către
toate celelalte
router-e din reţea, fără a cunoaşte în prealabil destina tarii (mesaj de tip
broadcast). Fiecare router îi va răspunde cu un mesaj in care va ataşa
adresa IP a sa, identificându-se astfel. Se face apoi un test prin care se
analizează timpul de răspuns , trimiţân du-se un mesaj de tip echo
("ecou") către router-ele determi nate anterior. Răspuns ul primit de la
Manual de informatică pentru clasa a Xl-a 311

fiecare este reţinut pentru a fi utilizat în continuare. Algoritmul de


determinare a drumului minim între oricare două noduri ale reţelei (de
exemplu, se poate utiliza Dijkstra) este apoi aplicat, considerându-se
pentru fiecare legătură un cost ce depinde de timpul de răspuns, media
traficului sau, mai simplu, numărul de noduri intermediare. Astfel,
dispozitivul obţine o "hartă" a reţelei pe care o reţine apoi în tabelul său
de routare. fn cazul unei reţele de dimensiuni foarte mari, un algoritm de
acest tip funcţionează corect, dar poate încetini traficul, scăzând astfel
eficienţa reţelei.

• Algoritmi de routare descentralizaţi. Router-ele ce au implementate


un astfel de algoritm reţin informaţiile doar despre nodurile legate in mod
direct (adiacente). Astfel, router-ul memorează costul fiecărei legături
directe şi la o anumită perioadă de timp, face schimb de tabele cu
celelalte router-e, reactualizându -şi astfel informaţiile. De exemplu, dacă
avem trei router-e legate în serie:

L1 l2

Router 1 Router 2 Router 3

Figura A.2. Exemplu de reţea

în cazul în care Router 1 trebuie să trimită date către Router 3,


informaţiile vor trece automat prin Router 2. Când pachetele de date
ajung la Router 2, el verifică lista sa de routare şi decide cum să trimită
pachetele de date spre destinaţie.

Problemele reale pe care le întâmpină reţelele de calculatoare se datorează


numărului mare de dispozitive (noduri) din reţea. Cu cât această valoare este mai
mare, cu atât numărul de calcule efectuate la nivel de router este mai mare. Astfel,
se poate implementa virtual o ierarhizare a reţelei, impărţindu-se pe regiuni.
Fiecare router deţine informaţii doar despre toate router-ele din regiunea sa.
Legătura cu celelalte regiuni se face prin anumite router-e, ca un fel de "porţi" de
ieşire spre exterior. Astfel, un router dintr-o regiune nu reţine nici o informaţie
despre un altul dintr-o altă regiune, ci doar calea către acea regiune.

A.2 . Instrumente de management economic

Proiectele şi situaţiile economice determinate de punerea în


practică a acestora, presupun efectuarea unor activităţi interco-
nectate, care pot fi modelate prin intermediul grafurilor.
Managementul informatic al proiectelor permite gestiunea, coordonarea,
planificarea şi controlul resurselor astfel incât obiectivele propuse să se atingă în
mod optim şi la timp.
312 Anexa 2 - Aplicaţii practice ale grafurilor

O aplicaţie foarte răspândită a grafurilor orientate o constituie simularea


proiectelor complexe ce presupun o multitudine de activităţi distincte, efectuate rn
serie sau în paralel. Teoria grafurilor vine în ajutorul oricărui analist de proiect prin
modelarea acestor activităţi, prin structurarea grafică a dependenţelor dintre ele şi
prin determinarea timpului necesar de realizare a proiectului.
în evaluarea oricărui proiect este necesară cunoaşterea timpului maxim de
cel mai lung de la faza iniţială
execuţie a întregii lucrări. Acesta reprezintă drumul
la faza finală a proiectului şi este numit drum critic.

Un graf de activităţi este un graf asociat unei lucrări complexe a cărei


realizare presupune desfăşurarea mai multor acţiuni (procese, activităţi). Un astfel
de graf presupune două tipuri de componente:

• arcele - reprezintă activităţile sau etapele elementare ale lucrării, iar


lungimea asociată unui arc semnifică timpul de desfăşurare al activită~i.
Exemple: proiectarea unei componente, implementarea unui algoritm, etc. In
cadrul unui proiect, activităţile se pot efectua:

-în serie: o activitate nu poate începe până când alta nu a fost terminată;
-în parale!: mai multe activităţi desfăşurate fn acelaşi timp.

• nodurile - reprezintă evenimente care pot fi interpretate ca indicând


realizarea unor obiective parţiale ale lucrării; ele sunt un punct de verificare
al evoluţiei lucrării. Exemple: terminarea etapei de analiză, sosirea
materialelor de construcţie, terminarea unor teste, etc.

Proiectul este format dintr-o serie de activităţi (şi evenimente), efectuate


într-o anumită perioadă de timp (cu un început şi un sfârşit definit). La final,
rezultatul este scopul pentru care a fost dezvoltat acel proiect.

Numim drum critic al unui graf de activităţi un drum de lungime maximă


care leagă nodul iniţial de cel final. Drumul critic reuneşte activităţi a căror
întârziere duce la întârzierea realizării întregului proiect, de aceea trebuie
supravegheate cu mare atenţie. Activităţile şi evenimentele ce formează drumul
critic poartă şi ele denumirea de critice.

:•. ,1.·1
. . . .''

Figura A.3. Exemplu de graf de activităţi


Manual de informatică pentru clasa a Xl -a

În figura A.3, drumul critic este format din nodurile: 1 , 2 , 7 , s ş i 6 . r.mo~. de


terminare al proiectului este de 21 de unităţi (măsura de unitate a costului). De
altfel, nu există în mod obligatoriu un singur drum critic. Sunt cazuri în care graful
conţine mai multe drumuri critice, însă cu suma ponderilor arcelor egală. în
Capitolul 9 aţi studiat grafurile şi algoritmul lui Roy-Floyd, metodă ce permite
determinarea drumului maxim într-un graf. Această tehnică se poate implementa
cu succes pentru a detecta drumul critic într-un graf de activităţi.

Având cunoscut drumul critic pentru un graf asociat unui proiect, se pot
analiza în detaliu anumite aspecte particulare ale fiecărui eveniment sau activitate.
Dorim să cunoaştem cum se pot derula celelalte activităţi, care nu sunt critice, in
funcţie de durata drumului critic. Astfel, au fost introduse câteva noţiuni teoretice,
ce vor fi prezentate în continuare.
Se consideră un graf de activităţi, pentru care notăm cu v~. (vârfurile)
evenimentele şi cu A[i, j J (arcul de la v~. la V:~) activităţile. Vom defini:

t1 - data aşteptată a unui eveniment V 1 ca fiind drumul cel mai lung de la


V1 la v~. (cea mai mare distanţă);

t~.• - data limită a unui eveniment v 1 ca fiind diferenţa între t., (data
aşteptată a lui Va) şi drumul maxim de la v~. la Va·
Sărevenim la exemplul din figura A.3. Pentru evenimentul 4, vom avea data
aşteptată egală cu 10 (5+2+3) unităţi, iar data limită, egală cu 16 (21-5) unităţi.
Putem astfel considera că evenimentul 4 trebuie să fie atins după 10 unităţi
temporale, iar ln cazul unei întârzieri, atingerea sa nu poate să dureze cu mai mult
de 6 (16-10) unităţi faţă de data sa aşteptată de terminare.

Celedouă valori asociate evenimentului v 1 determină un Interval de


notat cu [t1 , t 1 ·1. ce specifică perioada de timp în care poate avea loc
fluctuaţie,
evenimentul vi, fără a schimba timpul total asociat proiectului (drumul critic).

f Tn urma unor calcule uşoare, se poate observa că pentru toate evenimentele


e' ' ce aparţin drumului critic, ti "' ti·.

Considerând cunoscute toate datele aşteptate şi cele limită pentru (lraf,


definim fn continuare două noţiuni privitoare la arce:

r
ML i, j) - marginea fiberă a unei activităţi, ca fiind t~- t 1 -d (A [ i, j J ) , ce
semnifică durata cu care se poate intârzia începerea activităţii A [i, j J. fără a
modifica data de aşteptare a evenimentului v;~;

MT [i,j] -marginea totală a unei activităţi , ca fiind t/-t 1 -d(A[i,j) ) ,


ce semnifică durata cu care se poate întârzia începerea activităţii A [ i, j], fără a
modifica data limită a evenimentului v:J.

Arcele ce formează drumul critic au aceste două valori nule (nu le este
permisă nici o întârziere).
314 Anexa 2 - Aplicaţii practice ale grafurilor

Intervalul de fluctuaţie permite managerului de proiect să utilizeze resursele,


echipamentele şi utilajele rămase libere pentru a ajuta alte activităţi şi implicit
pentru a micşora durata de efectuare a întregului proiect (în cazul în care se poate
realiza acest lucru).

Grafurile de activităţi sunt extrem de utile în evaluarea l ucrărilor complexe,


iar reprezentarea lor permite analistului de proiect o viziune de ansamblu şi
totodată, o modalitate prin care poate testa o multitudine de variante, înainte de a
o alege pe cea considerată optimă. De asemenea, soft-urile specializate ce oferă
metode complexe de analiză, utilizează cu succes metode de optimizare ca cea
a "Drumului Critic".

A.3. Chimie moleculară

Ştiinţa care se ocupă cu studiul moleculelor se


numeşte chimie moleculară. Presupunându-se cunoscute ~·
elementele teoretice de bază, considerăm că o moleculă
reprezi ntă cea mai mică particulă a unei substanţe chimice
ce reţine toate proprietăţile sale chimice şi de compoziţie. O
moleculă este formată din cel puţin doi atomi şi este neutră
din punct de vedere electric. Formula chi mică şi structura unei molecule reprezintă
cei mai importanţi factori care-i determină proprietăţile.

În chimie, grafurile ce descriu topologia moleculară se numesc grafuri


moleculare. După cum era de aşteptat, nodurile reprezintă atomii, Iar arcele
semnifică legfJturile dintre atomi.

Mai jos, este prezentat un exemplu de graf molecular neorientat pentru o


hidrocarbură (lipC4):

Figura A.4. Exemplu de graf molecular asociat

Pentru graful neorientat prezentat anterior, se pot asocia următoarele trei matrice:
315
Manual de informatică pentru clasa a Xl-a

o1 o1 1 oo o1 2 1 1 2 2 o3 2 3 1 2 2

1 o 1 o o o o 1 o1 2 2 3 3 3 o3 2 4 5 5
2 1 o1 2 3 o 3 3 4 4
o1 o1 ooo 3 4 4
2 1 o 2 3 3 2 3 o4 5 5
1 o1 ooo o 1 3

1 o ooo 1 1 1 2 3 2 o1 1 1 4 3 4 o1 1
3 4 3 1 o2 2 5 4 5 1 o2
oooo1 oo 2
oooo1 oo 2 3 4 3 1 2 o 2 5 4 5 1 2 o

adiacenţă matricea drumurilor matricea drumurilor


matricea de
minime (distanţa) maxime (Detour)

it în
l , Observăm faptul că acest graf asociat conţine ciclur i, lucru obişnu
sunt determinate, cu
totuşi
• rile moleculare. Drumurile maxime
structu
precizarea că in programele specializate se evită ciclarea algoritmului prin
utilizarea unei condiţii de stop.
teristi ci
După ce au fost determinate aceste matrice, mai multe carac
două dintre ele:
topolo gice importante pot fi obţinute direct. Prezentăm doar
rilor maxime:
- indice le de drum (Deto ur)- se obţine din matricea drumu

unde (~)u este un element al matricei Detour.


Weiner pentru
- indice le Weine r - introdus în anul1 947 de chimistul Harry
a studia structura molecu lară:

1 Il 11

W(G) =-· I.I<d )ij'


2 i= l J=l
e.
unde (d) u reprezintă un element al matricei drumurilor minim
fo l osiţi în
mai mult de 400 de astfel de indici topolo giei şi sunt
Există
ule, extrem de utile în analiza
determinarea similarităţilor structurale între molec
moleculară .
-vă cât de uşor
Exemplul prezentat anterio r este foarte simplu, dar imag inaţi
eze aceste date, pentru o formul ă chimică
poate fi pentru un chimist să analiz
complexă, simulând totul direct pe calculator
... Teoria grafurilor este folosită cu
lor cu ajutoru l unul PC
succes în chimie şi genetică, iar simularea experimente
diminuează considerabil timpul necesar de lucru.
Anexa 3
Tabela codu rilor ASCII
Cod Caracter Cod Caracter Cod Ca racter Cod Caracter Cod Caracter Cod Ca.-a:::B"
000 (nul) 022 • (syn) 044, 066 8 0 88 X Il O n
001 o (soh) 023 V (etb) 045- 067 C 089 y II I o
002 e (stx) 024 f (can) 046 . 068 0 090 Z 112 p
003 . (etX) 025 t (em) 047/ 069 E 091 [ 113 q
004 • (cot) 026-+ (eof) 048 0 070F 092\ 114 r
005 • (cnq) 027 +- (esc) 049 1 071 G 093 1 115 s
006 + (ack) 028 ~ (fs) 050 2 072 H 094 ... 116 t
007 . (bel) 029 ..... (gs) 05 1 3 0731 095 117 u
008 (bs) 030 Â (rs) 0524 074J 096 ' 11 8 \'
009 o (lab) 03 1 • (us) 0535 075 K 09711 119 w
010. (lf) 032 (spatiu) 0546 076 1, 098 b 120 x
o
OII (vt) 033 ! 055 7 077M 099 c 121 y
0 12 ~ (np) 034 10 0568 078 N IOOd 122 z
01 3 / (cr) 035 # 057 9 0790 101 c 123 {
0 14 Jl(so) 036$ 058 : 080P 102f 1241
0 15 U(si) 037% 059 . 081 Q 103 g 125}
016 ..,.. (dlc) 038 & 060 < 082 R 104 h 126-
0 17 ~ (dc l) 039 . 061 = 083 S 105 i 127
0 18!(dc2) 040 ( 062 > 0841' 106 j
0 19 !! (dc3) 041 ) 063 ? 085 u 107 k
020 11 (dc4) 042. 064 @ 086V 1081
021 § (nak) 043+ 065 A 087 W 109m

Codul ASCII extins


Cod Curnl1cr Cod Caracter Cod Caracter Cod Caracter Cod Curactcr Cod Carxtc
128 c; 149o 170 .... 191 , 2 12 1: 233 e
129 U 150 il 17 1 111 197 1.. 2 J:l F 234 0
130 e 151 il 172 'A 193..1. 2 14 2350
13 1 ii 152 173 ) 194 T 2 15 236 00
132 ii 153 o 174 « 195 ~ 2 16:P 2370
ma 154 u 175,. 196 - 2 17 238 e
1346 155 t 176 i:l: 197 2 18 r 239 ("\
135 ~ 156t 177 ~ ~ 198 f 2 19 . 240 •
136c 157Y 17811 199 220 . 241 ±
137 il 158 _ 179 200 !: 22 11 242 ~
138e 159 o 180 201 li' 222 1 243S
139'i 160ă 181 . 202.!!: 223 • 244 f
140i 161 i 182 203 ii' 224 a 245 J
141 1 1626 183,. 204 1~ 225 Il 246 +
142 A 163 u 1 84 ~ 205= 226 r 247 ..
143 A 164 ii 185 ~1 206~~ 227 lt 248°
144 E 165 N 186 Il 207 "!" 228l: 249 .
145 re 166" 187 'i1 208 .U. 229 o 250-
146/•: 167. 188 :!J 209 =r 230 u. 251 _-.J_
1470 168;, 189.11 2 10'11' 23 1 't 252 11
148 o 169 190d 2 11 u. 2324> 253 1
254. 255
MANUALDE \J

IN FOProfilul
RIVIATlCA
real

PASCAL & C+ +
o INJ@[Ji]{]ll@0@ ~®0&1[7@ @[plf®llil@~@ !Nlrn©~g ®rnO®riJ®!f~ @J@ lJl!f®lliJO®!ftiD@g
®@OM (Q]@ !J@llil®lf~®!f51 ©!ID®g [ft)OO[Jl~~c~"'n[Ji]il®!lilil@\k!f®

o m~llilOn®~@@@ IID~oon~o~ o1@ o[Ji]il®!ffiiiD®~n®~ 661TOO!ID®rn ®®rno~PPg


Gil~[JJ~~cnooil®lliln~c!f®

o !1@!M®~®[f(!!]0 \Yln!fUoo@O @J@ 0[]]{1®m@Un®~ rs TI1J©g


Gil~[Jl~!1J&1llilcn[Ji]il®llilnl1@c~®

. COMPETENTE
'
DIGITALE

TEHNO LOGIA INFO R MAŢIEI TEHNOLOGIA I NFORMAŢIE I


...
RINIIIIIA RIF8RIATIEI
Cll.a'fll lll
$1A COMUNICATIILOR ŞI A COMUNICAŢIILOR
C:.tll ~t•--flil1ffi'Midtii!UI1 "t. ll!lll-.I W, IIM1.41fltJ).01 2CI01,
ftll.tlbfl, llf'lfllld ....!IIMIMitlt-"tb"\lilult,

- - - - ·- -
următoarele 1
Pentru a efectua o comandă, utilizaţi
date de contact:
http://www.ls-infomat.ro (

JUU1J tel: 0722.57.37.01; 0749.99.77.07


e-mail: comenzi@ls-infomat.ro

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