doru popescu anastasiu · vlad huţanu tudor sorin. informaticĂ (filiera teoretică, profilul...

316
Vlad Huţanu 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

Upload: others

Post on 08-Sep-2019

22 views

Category:

Documents


1 download

TRANSCRIPT

Page 1: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Vlad Huţanu 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

Page 2: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Copyright 2006-2016 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ţiei şi Cercetării nr. 4446 din 19.06.2006 în urma evaluării calitative organizate de către Consiliul Naţional pentru Evaluarea şi Difuzarea Manualelor şi este realizat în 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 I Valiana Petrişor, Colegiul Naţional Bilingv George Coşbuc

Tiparul executat la S.C. LUMINATIPO s.r.l. Str. Luigi Galvani nr. 20 bis, sector 2, Bucureşti

Descrierea CIP a Bibliotecii Naţionale a României

HUŢANU, VLAD Informatică : manual pentru ciclul superior al liceului : clasa a XI-a - (filiera teoretică, profilul real, specializarea matematică-informatică) şi (filiera vocaţională, profil militar MApN, specializarea matematică-informatică) / Vlad Huţanu, Tudor Sorin. - Bucureşti : Editura L & S Soft, 2006 ISBN (10) 973-88037-1-3; ISBN (13) 978-973-88037-1-8 I. Tudor, Sorin 004(075.35)

Editura L&S SOFT:

Adresa: Str. Stânjeneilor nr. 6, Sector 4, Bucureşti; Telefon: 0722-573701; 0727.731.947; E-mail: [email protected] Web Site: www.ls-infomat.ro

Page 3: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

3

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 trimiş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. Şiruri de caractere …………………………………………… 73

3.1. Generalităţi …………………………………………………………………………. 73 3.2. Şiruri de caractere în Pascal……………………………………………………….74

3.2.1. Noţiuni introductive............................................................................. 74 3.2.2. Concatenarea şirurilor........................................................................ 76

Page 4: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

4 Cuprins

3.2.3. Compararea şirurilor........................................................................... 77 3.2.4. Lungimea şirurilor 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ăţi………............................................................................... 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.10. 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.11.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……………………………………………………………………… 110 Capitolul 4. Structuri de date neomogene …………………………… 112

4.1. Noţiuni introductive……………………………………………………………….. 112 4.2. Structuri neomogene î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 înregistră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. Înregistră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

Page 5: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 5

Capitolul 6. Introducere în recursivitate ……………………………… 141

6.1. Prezentare generală ………………………………………………………………141 6.2. Modul în care se realizează autoapelul….………………………………………141

6.2.1. Realizarea autoapelului în Pascal..................................................... 141 6.2.2. Realizarea autoapelului în C++......................................................... 142

6.3. Mecanismul recursivităţii….……………………………………………………… 143 6.4. Cum gândim un algoritm recursiv?……...……………………………………….147 6.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

Probleme propuse……………………………………………………………………… 159 Indicaţii / Rezolvări…………….………………………………………………………. 166 Capitolul 7. Metoda Divide et Impera ………………………………… 172

7.1. Prezentare generală ………………………………………………………………172 7.2. Aplicaţ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ăieturilor.......................................................................... 180

7.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ăspunsuri…………….……………………………………………………………….. 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

Page 6: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

6 Cuprins

8.4.5. Generarea aranjamentelor................................................................ 219 8.4.6. Generarea tuturor partiţiilor mulţimii 1,2, ..., n................................ 221

8.5. Alte tipuri de probleme care se rezolvă prin utilizarea metodei backtracking…. 223 8.5.1. Generalităţi....................................................................................... 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 în lăţime (BF – bredth first)........................... 250 9.1.6.2. Parcurgerea în adâncime (DF – 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.10. 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

Page 7: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

7

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, ai,j, 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.

1. Un magazin oferă spre vânzare n produse. Se doreşte să se 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 ai,j semnifică valoarea încasată din vânzarea produslui i în luna j a anului - prin urmare, elementele tabloului sunt de tip real.

Intrebări posibile

a) Dacă magazinul vinde 5 produse, câte elemente are tabloul?

b) Care sunt indicii de adresare în tablou pentru a afla vânzările din produsul 5 în luna septembrie?

c) Cum se poate calcula suma încasată în luna mai?

=

−−−

nmmm

nmmm

n

n

aaaaaa

aaaaa

aaa

A

,2,1,

,12,11,1

,32,31,3

2,21,2

,12,11,1

..

............

.....

..

Page 8: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

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 încasat 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 ai,j reprezintă media pe care o are elevul i la materia 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 în 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 să lucrăm cu masive unidimensionale. Pentru a adresa un element al unui vector se utilizează un singur indice. În cazul matricelor, vom utiliza doi indici. Mai jos, puteţi observa cum se declară o matrice cu 10 linii şi 9 coloane, cu elemente de tip întreg:

Varianta Pascal Varianta C++

type tablou = array[1..10,1..9] of real; ... var a:tablou;

sau direct var a:array [1..10,1..9] of real;

int a[10][9];

Î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].

Î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 începând de la 1. În aceste condiţii se pierde o linie şi o coloană, fiecare de indice 0. Considerăm acest fapt neesenţial.

Page 9: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 9

Figura 1.1. Exemplu de tablou

De multe ori nu ştim câte linii şi câte coloane va trebui să aibă tabloul. În acest caz, tabloul se declară cu un număr maxim de linii şi un număr maxim de coloane, în aşa fel încât acesta să corespundă oricărui set de date de intrare. Evident, într-un astfel de caz există o risipă de memorie internă, 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, utilizăm numai primele 4 linii şi primele 5 coloane.

Programul care-l utilizează va funcţiona corect dacă avem cel mult 8 linii şi cel mult 8 coloane.

În programul următor se citeşte şi se afişează un tablou. Iniţial se citesc numărul de linii şi de coloane ale tabloului (m şi n). Observaţi modul în care am afişat tabloul - de aşa natură încât şi pe ecran să arate ca o

matrice, adică fiecare linie să fie scrisă pe un rând.

Varianta Pascal Varianta C++

var m,n,i,j:integer; a:array[1..10,1..9] of integer; begin write ('m='); readln(m); write ('n='); readln(n); for i:=1 to m do for j:=1 to n do begin write ('A[',i,',',j,']='); readln(a[i,j]); end;

for i:=1 to m do begin for j:=1 to n do write (a[i,j],' '); writeln; end end.

#include <iostream.h> main() int m,n,i,j,a[10][9]; cout<<"m="; cin>>m; cout<<"n="; cin>>n; for (i=0;i<m;i++) for(j=0;j<n;j++) cout<<"a["<<i+1<<',' <<j+1<<"]=";

cin>>a[i][j]; for (i=0;i<m;i++) for (j=0;j<n;j++)

cout<<a[i][j]<<' '; cout<<endl;

În memorie, tablourile sunt reţinute pe linii. Aceasta înseamnă că la început

este memorată prima linie, apoi a doua, ş.a.m.d.

Page 10: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

10 Capitolul 1. Tablouri

1.3. Aplicaţii cu tablouri bidimensionale

Aplicaţia 1.1. Interschimbare de linii. Se citeşte un tablou cu m 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 interschimbarea liniilor x şi y. Exemplu: Considerăm tabloul de mai jos, cu m=3, n=3 şi liniile x=2, y=3:

987654321

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

654987321

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ă linii. Am fi tentaţi ca manevra să fie un vector cu n componente. Cu puţină atenţie ne dăm seama că putem folosi ca manevră o singură variabilă de acelaşi tip cu cel al componentelor de bază ale tabloului. În rest, analizaţi programul de mai jos:

Varianta Pascal Varianta C++

type matrice = array[1..10, 1..10] of byte; var mat: matrice; m, n, i, j, x, y, man: integer; begin -citirea datelor- write ('m= '); readln(m); write ('n= '); readln(n); for i := 1 to m do for j := 1 to n do begin write('mat[',i,',',j,']='); readln(mat[i,j]); end; write('x= '); readln(x); write('y= '); readln(y); - tiparesc tabloul initial- for i := 1 to m do begin for j := 1 to n do write (mat[i,j],' '); writeln; end;

#include <iostream.h> main() int mat[10][10],m,n,i, j,x,y,man; cout<<"m="; cin>>m; cout<<"n="; cin>>n; for(i=0;i<m;i++) for(j=0;j<n;j++) cout<<"mat["<<i+1<<',' <<j+1<<"]="; cin>>mat[i][j]; ; cout<<"x="; cin>>x; cout<<"y="; cin>>y; cout<<endl; for (i=0;i<m;i++) for (j=0;j<n;j++) cout<<mat[i][j]<<' '; cout<<endl;

Page 11: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 11

-interschimbare linii- for j:=1 to n do begin man := mat[x,j]; mat[x,j] := mat[y,j]; mat[y,j] := man; end; writeln;

-tiparesc tabloul inversat- for i := 1 to m do begin for j := 1 to n do write(mat[i,j],' '); writeln; end; end.

for(j=0;j<n;j++) man=mat[x-1][j]; mat[x-1][j]=mat[y-1][j]; mat[y-1][j]=man; cout<<endl; for (i=0;i<m;i++) for (j=0;j<n;j++) cout<<mat[i][j]<<' '; cout<<endl;

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ă, începând cu primul element din linia 1, în sensul acelor de ceas.

Exemplu. Fie tabloul:

Elementele vor fi afişate în ordinea:

1 2 3 6 9 8 7 4 5.

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 în ordinea cerută, având grijă ca elementele aflate în colţuri să nu fie afişate de două ori.

*************************

987654321

Page 12: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

12 Capitolul 1. Tablouri

Aplicaţia 1.3. Pe o tablă cu n linii şi m coloane (n, m numere naturale, 1≤n,m≤30), sunt plasate pe unele poziţii jetoane cu litere, conform unui joc corect de SCRABBLE. Ştiind că vocalele au câte un punct, iar consoanele câte două, stabiliţi valoarea totală a cuvintelor de pe tablă. Valorile 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

ALTI.

..A..

se afişează 20

(DA=3, STA=5, ALTI=6, SAU=4, AI=2).

Rezolvare. Căutarea cuvintelor pe tablă şi cumularea punctelor lor este, de obicei, prima soluţie la care ne gândim. Algoritmul corespunzător acestei soluţii pare însă destul de complicat. Simpla parcurgere a matricei de caractere şi adunarea valorilor corespunzătoare literelor nu este o strategie bună, deoarece se pierde

Varianta Pascal Varianta C++

type matrice = array[1..10, 1..10] of integer; var mat: matrice; n, i, j, k: integer; begin -citirea datelor- write ('n= '); readln(n); for i := 1 to n do for j := 1 to n do begin write('mat[',i,',',j,']='); readln(mat[i,j]); end; -listare- for k := 1 to n div 2 + 1 do begin for i := k to n-k+1 do writeln(mat[k,i]); for i := k+1 to n-k+1 do writeln(mat[i,n-k+1]); for i := n-k downto k do writeln(mat[n-k+1,i]); for i := n-k downto k+1 do writeln(mat[i,k]) end; end.

#include <iostream.h> main() int mat[10][10],n,i,j,k; cout<<"n="; cin>>n; for(i=1;i<=n;i++) for (j=1;j<=n;j++) cout<<"mat["<<i<<',' <<j<<"]="; cin>>mat[i][j]; for(k=1;k<=n/2+1;k++) for(i=k;i<=n-k+1;i++) cout<<mat[k][i]<<endl; for(i=k+1;i<=n-k+1;i++) cout<<mat[i][n-k+1]<<endl; for(i=n-k;i>=k;i--) cout<<mat[n-k+1][i]<<endl; for(i=n-k;i>=k+1;i--) cout<<mat[i][k]<<endl;

Page 13: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 13

punctajul literelor care apar într-un cuvânt pe orizontală şi unul pe verticală şi care ar trebui luate de două ori în calcul. De aceea, vom analiza în plus, pentru fiecare literă, dacă ea face parte dintr-un singur cuvânt (are litere vecine doar pe orizontală sau doar pe verticală) şi se punctează obişnuit, sau face parte din două cuvinte (are litere vecine şi pe orizontală şi pe verticală) şi atunci se punctează dublu.

Varianta Pascal Varianta C++

var a:array[1..30,1..30]of char; n,m,i,j,dir:byte; p,pt:integer; f:text; begin assign(f,'tabla.txt'); reset(f); readln(f,n,m); for i:=1 to n do begin for j:=1 to m do read(f,a[i,j]); readln(f) end; pt:=0; for i:=1 to n do for j:=1 to m do if a[i,j]<>'.' then begin

if (a[i,j]='A') or (a[i,j]='E') or (a[i,j]='I') or (a[i,j]='O') or (a[i,j]='U') then p:=1 else p:=2; dir:=0; if (i>1) and (a[i-1,j]<>'.') or (i<n) and (a[i+1,j]<>'.') then dir:=dir+1; if (j>1) and (a[i,j-1]<>'.') or (j<m) and (a[i,j+1]<>'.') then dir:=dir+1; pt:=pt+p*dir

end; writeln(pt) end.

#include <fstream.h> char a[31][31]; int n,m,i,j,dir,p,pt; ifstream f("tabla.txt"); void main() f>>n>>m; for(i=1;i<=n;i++) for(j=1;j<=m;j++) f>>a[i][j]; pt=0; for(i=1;i<=n;i++) for(j=1;j<=m;j++) if (a[i][j]!='.') if(a[i][j]=='A'|| a[i][j]=='E'|| a[i][j]=='I'|| a[i][j]=='O'|| a[i][j]=='U') p=1; else p=2; dir=0; if (i>1 && a[i-1][j]!='.' || i<n &&

a[i+1][j]!='.') dir++;

if (j>1 && a[i][j-1]!='.' || j<m &&

a[i][j+1]!='.') dir++;

pt+=p*dir; cout<<pt<<endl;

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

pentru i=0,n+1 execută

a0,i'.'; an+1,i'.'

ai,0'.'; ai,n+1'.'

Page 14: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

14 Capitolul 1. Tablouri

atunci testarea vecinilor elementelor de pe margine se realizează întocmai ca şi în cazul celor din interiorul matricei:

dacă ai-1,j≠'.' sau ai+1,j≠'.'

dirdir+1 (cel puţin un vecin pe direcţia verticală)

dacă ai,j-1≠'.' sau ai,j+1≠'.'

dirdir+1 (cel puţin un vecin pe direcţia orizontală)

Se mai poate analiza şi cazul în care o literă nu are nici un vecin pe niciuna dintre direcţiile orizontală sau verticală (caz în 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.

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

Vom prezenta algoritmul pe un exemplu în care se sortează crescător 6 numere naturale: 6, 36, 41, 25, 40 şi 30.

Iniţial, şirul celor n numere se împarte în 10 clase: prima clasă conţine numerele care se termină cu 0, 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, în care prima linie are indicele 0. Elementele din linia 0 reţin numărul de elemente din şir care se găsesc pe coloana respectivă:

Obţinem din nou şirul de numere, aşezându-le în ordinea coloanelor şi în ordinea în care le-am pus în fiecare coloană: 40, 30, 41, 25, 6, 36. Împărţim din nou şirul în 10 clase, după a 2-a cifră a numerelor:

2 1 0 0 0 1 2 0 0 0

40 41 25 6

30 36

0 1 2 3 4 5 6 7 8 9

1 0 1 2 2 0 0 0 0 0

6 25 30 40

36 41

0 1 2 3 4 5 6 7 8 9

Page 15: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 15

Obţinem din nou şirul de numere, aşezându-le în 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!

Varianta Pascal Varianta C++

var A:array [1..100] of integer; Mat:array[0..100,0..100] of integer; n,NrCif,i,j,k,Cif,Zece: integer; begin write('n= '); readln(n); for i := 1 to n do begin write('A[',i,']= '); readln(A[i]) end; Zece:=1; for NrCif:=1 to 4 do begin if NrCif>1 then Zece:=Zece*10; for i:=1 to n do begin Cif:=(A[i] div Zece) mod 10; Mat[0,Cif]:=Mat[0,Cif]+1; Mat[Mat[0,Cif],Cif]:=A[i] end; Extrag din mat. in vector k:=0; for i:=0 to 9 do if Mat[0,i]<> 0 then for j:=1 to Mat[0,i] do begin k:=k+1; A[k]:=Mat[j,i] end; for i:=0 to 9 do Mat[0,i]:=0; end; for i:=1 to n do writeln(A[i]); end.

#include <iostream.h> int A[100],Mat[100][100],n, NrCif,i,j,k,Cif,Zece;

main() cout<<"n="; cin>>n; for (i=1;i<=n;i++) cout<<"A["<<i<<"]="; cin>>A[i]; Zece=1; for(NrCif=1;NrCif<=4;NrCif++) if (NrCif>1) Zece*=10; for (i=1;i<=n;i++) Cif= (A[i] / Zece) % 10; Mat[0][Cif]++; Mat[Mat[0][Cif]][Cif]=A[i]; //Extrag din mat. in vector k=0; for (i=0;i<=9;i++) if (Mat[0][i]) for (j=1;j<=Mat[0][i];j++) k++; A[k]=Mat[j][i]; for (i=0;i<=9;i++) Mat[0][i]=0; for (i=1;i<=n;i++) cout<<A[i]<<endl;

Page 16: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

16 Capitolul 1. Tablouri

Probleme propuse

1. Se citeşte un tablou cu n linii şi n coloane, numere întregi. Un astfel de tablou, în care numărul liniilor coincide cu numărul coloanelor, se numeşte tablou pătratic.

a) Pentru un tablou pătratic A, numim diagonală principală, elementele aflate pe "linia" care uneşte A[1,1] cu A[n,n]. Exemplu. Pentru tabloul de mai jos

1 2 34 5 67 8 9

,

elementele sunt: 1, 5 şi 9.

Se cere:

a1) suma elementelor aflate pe diagonala principală; a2) suma elementelor aflate deasupra diagonalei principale; a3) suma elementelor aflate sub diagonala principală.

b) Pentru un tablou pătratic A, numim diagonală secundară, elementele aflate pe "linia" care uneşte A[n,1] cu A[1,n]. Exemplu. Pentru tabloul

1 2 34 5 67 8 9

,

elementele sunt: 7, 5 şi 3.

Se cere:

b1) suma elementelor aflate pe diagonala secundară; b2) suma elementelor aflate deasupra diagonalei secundare; b3) suma elementelor aflate sub diagonala secundară.

Cerinţe suplimentare: pentru fiecare cerinţă se va face un program separat;

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

2. Interschimbaţi coloanele unei matrice cu m linii şi n coloane astfel încât în linia k, elementele să fie în ordine crescătoare.

Page 17: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 17

3. Un teren este dat sub forma unui tablou A cu n linii şi m coloane. Elementul A[i,j] 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 ”mat1.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 corespunzător.

8. Pentru 5 cifre n,a,b,c,d citite de la tastatură, se cere să se creeze fişierul ”mat5.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 necesară construirea în prealabil a matricei î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:

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

Figura 1.2. Exemplu

Page 18: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

18 Capitolul 1. Tablouri

11. Fiind dată o matrice cu m linii şi n coloane se cere să se afişeze elementele în ordinea sugerată în imaginea alăturată. 12. Fiind dată o matrice cu n linii şi n coloane (pătratică) cu numere naturale şi fiind date două elemente ale matricei de coordonate (x1,y1) ş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ă)?

Varianta Pascal Varianta C++

a) if x1-x2=y1-y2 then writeln('Da') else writeln('Nu');

b) if x1-y1=x2-y2 then writeln('Da') else writeln('Nu');

c) if abs(x1-y1)=abs(x2-y2) then writeln('Da') else writeln('Nu');

d) if abs(x1-x2)=abs(y1-y2) then writeln('Da') else writeln('Nu');

a) if (x1-x2==y1-y2) cout<<"Da"; else cout<<"Nu";

b) if (x1-y1==x2-y2) cout<<"Da" else cout<<"Nu";

c) if (abs(x1-y1)==abs(x2-y2)) cout<<"Da"; else cout<<"Nu";

d) if (abs(x1-x2)==abs(y1-y2)) cout<<"Da"; else cout<<"Nu";

13. Se dă un vector V cu m*n componente numere întregi. Care dintre secvenţele de mai jos copiază vectorul 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!

Varianta Pascal Varianta C++

a) for k:=1 to m*n do Mat[1+k div n,1+k mod m]:=V[k]; b) for k:=1 to m*n do Mat[1+(k-1) div m,1+(k-1) mod n]:=V[k]; c) for k:=1 to m*n do Mat[1+(k-1) div n,1+(k-1) mod n]:=V[k]; d) for k:=1 to m*n do Mat[1+k div n,1+k mod n]:=V[k];

a) for (k=1;k<=m*n;k++) Mat[1+(k-1)/n][1+(k-1)% m]=V[k]; b) for (k=1;k<=m*n;k++) Mat[1+(k-1)/m][1+(k-1)% n]=V[k]; c) for (k=1;k<=m*n;k++) Mat[1+(k-1)/n][1+(k-1)% n]=V[k]; d) for (k=1;k<=m*n;k++) Mat[1+k/n][1+k%n]=V[k];

Figura 1.3. Exemplu

Page 19: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 19

14. Se dă un vector V cu m*n componente numere întregi şi o matrice Mat cu m linii şi n coloane. Care dintre secvenţele de mai jos copiază matricea Mat în vectorul V? Copierea se realizează astfel: mai întâi se copiază linia 1, apoi linia 2, ..., iar la sfârşit, linia m.

Varianta Pascal Varianta C++

a) for i:=1 to n do for j:=1 to m do

V[j+(i-1)*m]:=Mat[i,j];

b) for i:=1 to m do for j:=1 to n do

V[j+(i-1)*n]:=Mat[i,j];

c) for i:=1 to m do for j:=1 to n do

V[i+(j-1)*n]:=Mat[i,j];

d) for i:=1 to m do for j:=1 to n do

V[j+(i-1)*n]:=Mat[j,i];

a) for (i=1;i<=n;i++) for (j=1;j<=m;j++)

V[j+(i-1)*m]=Mat[i][j];

b) for (i=1;i<=m;i++) for (j=1;j<=n;j++)

V[j+(i-1)*n]=Mat[i][j];

c) for (i=1;i<=m;i++) for (j=1;j<=n;j++)

V[i+(j-1)*n]=Mat[i][j];

d) for (i=1;i<=m;i++) for (j=1;j<=n;j++)

V[j+(i-1)*n]=Mat[i,j];

Răspunsurile la testele grilă

12. d); 13. c); 14. b).

Page 20: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

20

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ă în prezent am fost doar utilizatori de subprograme. Exemple de astfel

de subprograme folosite sunt:

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

de manipulare a fişierelor: close (Pascal) / .close() (C++).

În acest capitol învăţăm să lucrăm cu subprograme. Pentru a înţelege noţiunea de subprogram, vom porni de la două exemple.

1. Se consideră funcţia:

∞∈+

∞∈+

∈++

=

).( pentru ,

,-1);(- pentru

[-1,1]; pentru ,2

,11

6,1

11

)(

xx

xx

xx

x

xf .

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 în 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ă în 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.

Page 21: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 21

Oare nu se poate lucra şi altfel? Răspunsul este afirmativ. Se scrie un subprogram care calculează valoarea funcţiei într-un punct x oarecare şi se apelează subprogramul: o dată pentru x=a şi încă o dată pentru x=b. Valorile calculate 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 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ă

ştim să memorăm un şir de valori (folosind un vector) şi am studiat mai multe metode prin care se poate obţine ordonarea crescătoare a unui şir de valori (folosind algoritmi de sortare). De această dată, vom implementa o metodă cunoscută, cea de sortare, dar vom utiliza subprogramele.

Vom scrie un subprogram care citeşte un vector, unul care tipăreşte un vector ş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ă în trei

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

În plus, dacă într-un alt program este necesară sortarea altui vector de

numere reale, metoda clasică ne permite să alegem din secvenţa de instrucţiuni ce formează programul pe cele ce realizează sortarea, să le copiem în noul program şi să facem eventualele adaptări (numărul de componente şi numele vectorului pot fi altele). Aceste operaţii sunt destul de greoaie şi necesită multă atenţie. Prin implementarea 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 multe 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 (lizibile).

Page 22: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

22 Capitolul 2. Subprograme

2.2. Subprograme în Pascal

2.2.1. Un exemplu de utilizare a funcţiilor

Problema 2.1. Se citeşte n, număr natural. Să se scrie programele care afişează valoarea calculată a expresiilor:

Rezolvare. Funcţia care calculează E1 este:

function subp(n:integer):real;

var s:real; i:integer;

begin s:=0; for i:=1 to n do s:=s+1/i; subp:=s; end;

Analiza programului anterior Antetul funcţiei este “function subp(n:integer):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 ei. Î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;“, funcţia a primit ca valoare de retur conţinutul variabilei s.

În continuare, prezentăm cele două programe care utilizează funcţia:

.131

211;1

31

211 21

n

nE

nE

++++=++++=

Page 23: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 23

var n:integer;

function subp(n:integer):real; var s:real; i:integer; begin s:=0; 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,prod:real;

function subp(n:integer):real; var s:real; i:integer; begin s:=0; for i:=1 to n do s:=s+1/i; subp:=s; end;

begin write ('n='); readln(n); rez:=subp(n); prod:=1; for i:=1 to n do prod:=prod*rez; write(prod:5:2); end.

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

Apelul funcţiei în primul program s-a făcut astfel: “write(subp(n):5:2);“, iar în al doilea, “rez:=subp(n)“. În primul caz expresia este trecută ca parametru pentru write, iar în 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 efectivi.

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 efectivi.

Page 24: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

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 exemplu de utilizare a procedurilor

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.

Rezolvare. Programul este următorul:

type vector=array [1..9] of integer; var n,i:integer; v:vector;

procedure citesc; begin write('n='); readln(n); for i:=1 to n do begin write('v[',i,']='); readln(v[i]); end; end;

procedure tiparesc; begin for i:=1 to n do writeln(v[i]); end;

procedure sortez; var gasit:boolean; man:integer; begin repeat gasit:=false; for i:=1 to n-1 do if v[i]>v[i+1] then begin gasit:=true; man:=v[i]; v[i]:=v[i+1]; v[i+1]:=man end until not gasit; end;

begin citesc; sortez; tiparesc; end.

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

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

procedure nume; begin ........ end;

Page 25: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 25

Apelul unei proceduri se face prin utilizarea numelui ei, sub forma nume;. Mai precis, apelul unei proceduri este instrucţiune, 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 în programul principal la 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 între declaraţiile de variabile şi instrucţiunea compusă.

Orice variabilă a programului principal este şi variabilă a procedurii (invers nu este adevărat).

Î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 importante necesare compilatorului,

numele subprogramului, lista parametrilor formali. În 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: Figura 2.1. Structura antetului unui subprogram de tip funcţie

Page 26: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

26 Capitolul 2. Subprograme

Exemplu:

function suma(x,y:integer):real; begin suma:=x+y; end

- antetul este “function suma(x,y:integer):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 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ă:

Exemplu:

procedure suma(var s:real; x,y:real); begin s:=x+y; end;

- antetul este: “procedure suma(var s:real; x,y:real); “

- identificatorul (numele procedurii) este suma;

- lista parametrilor formali este: “var s:real; x,y:real;“;

- blocul este:

begin s:=x+y; end;

Figura 2.2. Structura antetului unui subprogram de tip procedură

Page 27: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 27

2.2.4. Definirea şi declararea unui subprogram

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

Definiţia 2.4. A defini un subprogram, înseamnă a-l scrie efectiv, după structura anterior prezentată. O problemă importantă este locul unde se defineşte subprogramul.

În Pascal, subprogramele se definesc în trei locuri: 1. În blocul programului care-l utilizează - numit şi program principal - între

declaraţia variabilelor şi instrucţiunea compusă:

program exemplu1; var x:integer;

procedure t; begin writeln(x) end; begin x:=3; t end.

2. În cadrul unităţilor de program - vor fi studiate ulterior. 3. În blocul unui alt subprogram. Această formă este proprie limbajului

Pascal. În exemplu, procedura t, are ca subprogram procedura z. Iniţial programul va atribui variabilei 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 exemplu2; var x:integer;

procedure t; procedure z; begin writeln('eu sunt z'); end; begin writeln(x); z; end; begin x:=3; t end.

Page 28: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

28 Capitolul 2. Subprograme

Definiţia 2.5. A declara un subprogram, înseamnă a-l anunţa. Un subprogram nedeclarat nu poate fi folosit.

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

sau în 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 exemplu1, 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 s1, s2, ..., sk. În acest caz, automat s1 este declarat pentru s2, s3, ..., sk, apoi s2 este declarat pentru s3, ..., sk, iar sk-1 este declarat pentru sk.

Exemple Procedurile s1 şi s2 pot fi apelate din programul principal. Procedura s1

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

procedure s1; begin writeln('s1') end;

procedure s2; begin s1; writeln ('s2'); end;

begin s1; s2; end.

Procedura test conţine definiţia a două proceduri s1 şi s2. Nici una din

aceste proceduri nu se consideră declarată pentru programul principal. Amândouă sunt declarate pentru procedura test. Pentru procedura s2, s1 este declarată, dar pentru s1 procedura s2 nu este declarată.

Page 29: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 29

procedure test; procedure s1; begin writeln('s1') end; procedure s2; begin s1; writeln('s2'); end; begin s1; s2; end; begin test; end.

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

la nivelul unui bloc să fie declarat pentru toate subprogramele definite la nivelul aceluiaşi bloc, ca în exemplul următor.

Declaraţia se face prin antet, urmat de cuvântul cheie “forward“.

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

procedure b; forward; procedure a; begin writeln('Eu sunt a'); b; end; procedure b; begin writeln('Eu sunt b') end; begin a; end.

4. În cazul în 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 subprogramele definite în cadrul ei (unităţile de program vor fi studiate în cadrul acestui capitol).

Page 30: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

30 Capitolul 2. Subprograme

Figura 2.3. Structura operandului care reprezintă apelul funcţiei

2.2.5. Apelul subprogramelor

Definiţia 2.6. A apela un subprogram, înseamnă a-l 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. În exemplu, valoarea găsită este 7:

var p,x,y:integer; function prod(x,y:integer):integer; begin prod:=x*y; end; begin x:=2; y:=3; p:=1+prod(x,y); writeln(p) end.

Apelul unei funcţii se realizează din interiorul unei expresii. În exemplu,

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

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.

Page 31: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 31

2.2.5.2. Apelul procedurilor

Cele câteva exemple date până acum fac inutilă prezentarea unui alt exemplu. Trebuie să ştim că: 1. Apelul unei proceduri se face printr-o instrucţiune, numită instrucţiune procedurală. Sintaxa acestei instrucţiuni este prezentată mai jos:

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

2.2.5.3. Transmiterea parametrilor la apel

În ce priveşte mecanismul de transmitere a parametrilor nu există nici o diferenţă între proceduri şi funcţii. Din acest motiv, transmiterea parametrilor se va trata unitar. Parametrii formali sunt cei trecuţi în antetul subprogramului. Parametrii efectivi sunt cei trecuţi la apelul subprogramului.

Aşa cum am văzut, subprogramele pot lucra cu variabilele globale ale blocului care conţine definiţia lor. Atunci care este motivul pentru care sunt necesari parametrii?

Cunoştinţele dobândite până în acest moment nu permit o justificare

completă. Putem spune numai că utilizarea parametrilor permite ca subprogramul să fie scris independent de programul principal. Să luăm un exemplu: ni se cere să scriem un subprogram care calculează 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:integer;

procedure suma(a,b:integer; var s:integer); begin s:=a+b; end;

begin x:=3; y:=4; suma(x,y,sum); writeln(sum); suma(2,3,sum); writeln(sum); end.

Figura 2.4. Structura instrucţiunii procedurale

Page 32: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

32 Capitolul 2. Subprograme

În programul anterior, procedura suma calculează suma a două numere întregi. Ea este apelată de două ori din cadrul programului 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.

Iată câteva reguli care trebuie respectate la apel: 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 efectivi să fie de alt tip (de exemplu, de tipul real).

Nu este obligatoriu ca numele parametrilor formali să coincidă cu numele parametrilor efectivi.

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; ... procedure test(n:integrer, v:vector); corect procedure 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 în 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 următoare. Pentru moment, prezentăm sintaxa parametrilor formali şi a parametrilor efectivi:

Figura 2.5. Sintaxa parametrilor trimişi

Page 33: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 33

2.2.5.4. Cum memorează subprogramele parametrii transmişi ?

În acest paragraf vom analiza modul prin care sunt memoraţi parametrii transmişi în momentul lansă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 memorează 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: ”procedure suma(a,b:integer; var s:integer);”; la primul 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: La al doilea apel, parametrii sunt: 2, 3, sum.

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.

3 4 adresa variabilei sum

a b s

stiva

2 3 adresa variabilei sum

a b s

stiva

Page 34: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

34 Capitolul 2. Subprograme

Astfel, se pot transmite prin valoare: valorile reţinute de variabile - în acest caz, parametrii efectivi trebuie să fie

numele variabilelor. Exemplu:

var n:integer;

procedure test(n:integer); begin n:=n+1; writeln(n) end;

begin n:=1; 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ă.

Afişă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 mai are spaţiu alocat. Prin urmare, valoarea 2 este pierdută.

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

parametrii efectivi sunt valori sau expresii, care mai întâi se evaluează. Exemplu:

procedure test(n:integer); begin writeln(n) end; begin test(3); test(3+4*5); end.

Page 35: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 35

În procedură se creează o variabilă în segmentul de stivă, numită n, care la primul apel reţine valoarea 3 şi la al doilea, valoarea 23. La ieşirea din procedură conţinutul variabilei se pierde.

Aşa cum am văzut, în cazul transmiterii prin valoare, se pot folosi ca parametri efectivi expresii. De asemenea, expresiile pot avea ca operanzi funcţii. Urmă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):integer; begin suma:=a+b; end;

function dif (a,b:integer):integer; begin dif:=a-b; end;

function prod(a,b:integer):integer; begin prod:=a*b; end;

begin write('x='); readln(x); write('y='); 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 subprogramului. În cazul transmiterii prin referinţă, parametrii efectivi 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ă.

Page 36: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

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;

procedure interschimb(var x,y:integer); var man:integer; begin man:=x; x:=y; y:=man; end;

begin x:=2; y:=3; interschimb(x,y); write(x,' ',y); end.

2.2.6. Variabile locale şi globale

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.0 a limbajului Pascal, variabilele numerice sunt iniţializate cu 0.

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 variabile înseamnă a spune care sunt blocurile de unde se poate adresa (ca să-i atribuim o valoare, de exemplu). Variabilele globale sunt vizibile la nivelul programului principal şi la nivelul

subprogramelor.

var x:integer; procedure a; procedure b; begin writeln(x) end; begin x:=2; b; writeln(x) end; begin a; x:=2; writeln(x); end.

Programul alăturat tipăreşte de trei ori 2, conţinutul variabilei globale x.

Page 37: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 37

Variabilele locale sunt vizibile doar la nivelul subprogramului în care au fost declarate şi la nivelul subprogramelor definite în acesta.

procedure a; var x:integer; procedure b; begin writeln(x) end; 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, adică atunci când acesta conţine declaraţia unei alte variabile, cu acelaşi nume. În acest caz, prin nume, se adresează variabila locală. Regula este următoarea: în cazul în care două variabile au acelaşi nume, dar vizibilităţi diferite, se adresează î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:integer;

procedure a; begin x:=2; writeln(x) end;

begin x:=3; a; end.

Alt termen des utilizat în practica programării este durata de viaţă a unei

variabile. El se referă la perioada de timp în care variabila este alocată în memorie. Astfel, 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 executării 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.

Programul alăturat tipăreşte de două ori 2, conţinutul variabilei locale x. Dacă am fi încercat să tipărim x din cadrul programului principal, programul ar fi dat eroare de compilare.

Page 38: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

38 Capitolul 2. Subprograme

Din acest punct de vedere putem spune că variabilele globale au durată 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). Iată o procedură scrisă greşit:

procedure er (var v:array[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 v:vect; ... procedure er (var v:vect); ... begin ... end;

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

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

function suma( n,i:integer):integer; var s:integer; begin calculez s; ....... suma(n,i):=s; end;

Eroarea constă în faptul că în atribuirea ”suma(n,i):=s;” au fost trecuţi parametri. Corect este ”suma:=s;”.

Page 39: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-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 multe ori sunt de mari dimensiuni. O preocupare constantă a informaticienilor este de a pune la dispoziţia programatorilor instrumente eficiente de programare. În acest sens, limbajul Pascal oferă posibilitatea lucrului cu 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>; INTERFACE [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 IMPLEMENTATION [tipuri de date şi variabile locale (valabile numai pentru unitatea de program)] procedurile şi funcţiile utilizate de unitatea de program [BEGIN ....... o eventuală secvenţă de program care se execută ....... în momentul declarării 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 ”IMPLEMENTATION”.

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.

Page 40: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

40 Capitolul 2. Subprograme

În continuare, vom da un exemplu pur didactic de unitate de program, numită OPERATII. Aceasta conţine patru proceduri: de adunare, scădere, înmulţire şi împărţire de variabile reale. Variabilele a şi b sunt globale

pentru programul care utilizează această unitate, iar variabila c poate fi utilizată numai în cadrul unităţii. În secvenţa de iniţializare se citesc valorile variabilelor a şi b.

Unitatea de program este prezentată în continuare:

unit operatii; interface var a,b:real; procedure adun(a,b:real); procedure scad(a,b:real); procedure produs(a,b:real); procedure impart(a,b:real); implementation var c:real; procedure adun(a,b:real); begin c:=a+b; writeln(c:3:2); end; procedure scad(a,b:real); begin c:=a-b; writeln(c:3:2); end; procedure produs(a,b:real); begin c:=a*b; writeln(c:3:2); end procedure impart(a,b:REAL); begin if b=0 then writeln('Impartirea nu se poate face ') else begin c:=a/b; writeln(c:3:2); end; end; begin write('a='); readln(a); writeln('b='); readln(b); end.

Page 41: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 41

Programul următor utilizează această unitate. Faptul că programul utilizează o unitate se precizează prin clauza ”USES”. Rularea sa decurge în felul următor:

• se cer valorile variabilelor a şi b (declarate în unitate);

• se apelează procedurile de adunare, scădere, înmulţire şi împărţire iar rezultatele se tipăresc;

• se apelează aceleaşi proceduri utilizând pentru apel două variabile globale ale programului (c şi d).

uses operatii; var c,d:real; begin adun(a,b); scad(a,b); produs(a,b); impart(a,b); write('c='); readln(c); write('d='); readln(d); adun(c,d); scad(c,d); produs(c,d); impart(c,d); readln; end.

După ce am văzut modul de alcătuire şi construcţie a unei unităţi de

program, trebuie să precizăm faptul că limbajul Pascal este livrat cu câteva unităţi de program standard (produse de firma BORLAND). Amintim câteva:

SYSTEM, CRT, DOS sau GRAPH.

Procedurile şi funcţiile puse la dispoziţie de limbaj se găsesc în aceste unităţi. Toate subprogramele folosite de noi până în prezent se află în unitatea de program SYSTEM. Pentru utilizarea lor nu este necesară clauza ”USES”.

Page 42: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

42 Capitolul 2. Subprograme

2.3. Subprograme în C++ 2.3.1. Exemple de utilizare a funcţiilor

Problema 2.3. Se citeşte n, număr natural. Să se scrie programele care tipăresc valoarea calculată a expresiilor:

Rezolvare. În continuare prezentăm cele două programe care utilizează funcţia:

#include <iostream.h> double subp(int n) double s=0; int i; for (i=1;i<=n;i++) s+=1./i; return s; main() int n; cout<<"n="; cin>>n; cout<<subp(n); _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ #include <iostream.h> double subp(int n) double s=0; int i; for (i=1;i<=n;i++) s+=1./i; // sau s=s+1./i return s; main() int n,i; double rez, prod=1; cout<<"n="; cin>>n; rez=subp(n); for (i=1;i<=n;i++) prod*=rez; cout<<prod;

Funcţia care calculează E1 este:

double subp(int n) double s=0;int i; for (i=1;i<=n;i++) s+=1./i; // sau s=s+1./i return s;

Antetul funcţiei este ”double subp(int n);”. Funcţia se numeşte ”subp”. Funcţia are un parametru numit n. Rolul său este important deoarece

precizează pentru ce valoare trebuie calculată expresia. Aşa cum vom vedea, există posibilitatea să avem mai mulţi parametri, de diferite tipuri.

.1...31

211;1...

31

211

n

21 nE

nE

++++=++++=

Page 43: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 43

Funcţia are un anumit tip, care precizează natura rezultatului. În exemplu, tipul este double întrucât expresia calculată este de acest tip.

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

Am văzut că funcţia întoarce un anumit rezultat - în exemplu, de tip double. 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 utilizată în teoria subprogramelor - în particular, în cazul

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

Definiţia 2.10. 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.11. 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 efectivi. Pentru apelul ”rez=subp(n); ”, parametrul efectiv este n.

Problema 2.4. 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.

#include <iostream.h> void citesc(int vt[10],int n) int i; for(i=0;i<n;i++) cout<<"v["<<i+1<<"]="; cin>>vt[i];

void sortez(int vt[10],int n) int gasit,i,man; do gasit=0; for (i=0;i<n-1;i++) if (vt[i]>vt[i+1]) man=vt[i]; vt[i]=vt[i+1]; vt[i+1]=man; gasit=1; while (gasit);

Page 44: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

44 Capitolul 2. Subprograme

void scriu(int vt[10],int n) int i; for(i=0;i<n;i++) cout<<vt[i]<<endl;

main() int v[10],n; cout<<"n="; cin>>n; citesc(v,n); sortez(v,n); scriu(v,n);

Programul conţine trei funcţii: citesc, sortez şi tiparesc. La apel, controlul programului este transferat la prima instrucţiune a funcţiei.

După executarea funcţiei, se revine în programul principal la prima instrucţiune care urmează celei de apel - în cazul de faţă se întâlneşte o altă instrucţiune de apel.

Cele trei funcţii au tipul void, adică nu au valoare de retur. Ele returnează

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(lista parametrilor formali)

Lista parametrilor formali este de forma:

parametru1, parametru2, ..., parametrun

Există şi posibilitatea ca lista parametrilor formali să 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 dată cu excepţia masivelor.

Page 45: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 45

Exemple de antete: - int suma(int a, int b) - funcţia se numeşte suma, returnează un

rezultat de tip int şi are doi parametri formali de tip int, numiţi a şi b. - void t(int n, float v[20]) - funcţia se numeşte t, este de tip void

(nu returnează rezultat prin nume), are doi parametri formali, primul numit n, de tip int, al doilea numit 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 expresie;

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

La întâlnirea instrucţiunii return, după atribuirea valorii, execuţia funcţiei se încheie şi se revine la funcţia care a apelat-o. În absenţa instrucţiunii return, execuţia funcţiei se încheie după execuţia ultimei instrucţiuni. În acest caz nu se întoarce nici o valoare. O funcţie poate fi apelată de sine stătă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 valabilă în cazul funcţiilor de tip void.

#include <iostream.h> int prod (int x, int y) return x*y;

main() int x=2,y=3; cout<<1+prod(x,y);

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

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. 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.

În programul alăturat este apelată o funcţie care calculează produsul a două numere întregi. Programul tipăreşte suma între 1 şi produsul calculat. În exemplu, 7.

Page 46: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

46 Capitolul 2. Subprograme

2.3.3. Declararea variabilelor

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:

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.

Segment de date

Segment de stivă

Heap

Figura 2.5. Cele trei zone în memoria internă

Page 47: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 47

3. Durata de viaţă - reprezintă timpul în care variabila respectivă are alocat spaţiu în memoria internă. Astfel avem:

a. Durată statică - variabila are alocat spaţiu în tot timpul execuţiei programului.

b. Durată locală - variabila are alocat spaţiu în timpul în 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

dinamice. Noi studiem numai primele două categorii, variabilele globale şi locale. A) Variabile globale

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

#include <iostream.h> int a;

int t() a=3; cout<<a;

int b;

main() b=4; cout<<a<<endl; t();

Variabilele a şi b sunt globale. În astfel de cazuri, variabilele respective pot fi utilizate de toate funcţiile care urmează în textul sursă declaraţiei variabilei respective. Din acest motiv, astfel de variabile se numesc globale.

La declarare, variabilele globale sunt iniţializate cu 0. Atributele variabilelor globale sunt: 1. Clasa de memorare - segmentul de date. 2. Vizibilitatea - În cazul în care declaraţiile acestora sunt înaintea tuturor funcţiilor, acestea sunt vizibile la nivelul întregului program (fişier). Dacă anumite funcţii se află plasate înaintea declaraţiilor acestor variabile, atunci ele sunt vizibile doar pentru funcţiile care sunt plasate după aceste declaraţii. În exemplul anterior, variabila a poate fi accesată din corpul oricărei funcţii, dar variabila b poate fi accesată doar din funcţia main().

Page 48: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

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 b sunt 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=4;

Variabilele locale nu sunt iniţializate implicit cu 0. Î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 în 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=4; int c=3; cout<<b<<" "<<c;

Page 49: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 49

În programul următor am declarat trei variabile, toate numite a. Una este 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=5; t(); cout<<a;

În program, se afişează conţinutul tuturor acestor variabile (3,4,5).

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).

Există posibilitatea ca, un ciclu for să conţină declaraţia unei variabile locale.

În 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=0; for (int i=1;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.

Priviţi programul următor. Acesta conţine o funcţie care calculează suma a două numere naturale.

Page 50: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

50 Capitolul 2. Subprograme

#include <iostream.h>

int suma(int a , int b) return a+b;

main() int c=4,d=3; cout<<suma(2,3)<<endl; cout<<suma(2+7,3-1*2)<<endl; cout<<suma(c,d)<<endl; cout<<suma(1.9,3.3)<<endl;

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 urmă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. În 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

efectivi sau tipul parametrilor efectivi 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; - suma(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 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

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

Parametrii formali sunt a şi b. Funcţia este apelată de mai multe ori. Parametrii efectivi sunt, pe rând:

2, 3; 2+7, 3-1*2; c, d; 1.9, 3.3.

Page 51: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 51

În cadrul subprogramului, parametrii transmişi şi memoraţi în stivă sunt variabile. Numele lor este cel din lista parametrilor formali.

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

locale.

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

Observaţii La revenirea în blocul apelant, conţinutul variabilelor memorate în stivă se

pierde - după cum ştim, durata de viaţă 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 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.

Se pot transmite prin valoare:

1. Valorile reţinute de variabile; 2. Expresii (acestea pot conţine şi funcţii).

1. Valorile reţinute 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+=1; cout<<n<<endl;

main() int n=1; test(n); cout<<n<<endl;

Parametrul n este transmis prin valoare.

2 3

a b

stiva

Page 52: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

52 Capitolul 2. Subprograme

În 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 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 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ă. În main() se tipăreşte conţinutul variabilei n, adică 1.

2. Parametrii efectivi sunt expresii care mai î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 funcţiile să întoarcă noile

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

În 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[10]) for (int i=0;i<10;i++) x[i]=i;

main() int a[10]; vector(a); for (int i=0;i<10;i++) cout<<a[i]<<" ";

Page 53: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 53

B. Transmiterea 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 execuţiei subprogramului. În 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 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); 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 ne ajută să evităm anumite erori. Mai mult, aceste noţiuni sunt utilizate de orice limbaj de programare, nu numai în C++.

Definiţia 2.12. A defini un subprogram, înseamnă a-l scrie efectiv, după structura anterior prezentată. O problemă importantă este locul unde se defineşte subprogramul.

Definiţia 2.13. A declara un subprogram, înseamnă a-l 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: s1 şi s2. Definiţiile ambelor funcţii se găsesc înaintea funcţiei main(). Din acest motiv ele pot fi apelate din main(). Definiţia funcţiei s1 este înaintea definiţiei lui s2, deci funcţia s1 poate fi apelată din s2. În schimb, din s1 nu poate fi apelată funcţia s2, pentru că definiţia lui s2 este după cea a lui s1.

Page 54: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

54 Capitolul 2. Subprograme

#include <iostream.h>

void s1() cout<<"Eu sunt s1"<<endl;

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

main() s1(); s2();

În situaţia prezentată, totuşi se poate ca s1 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 s1() s2(); cout<<"Eu sunt s1"<<endl;

void s2() cout<<"Eu sunt s2"<<endl;

main() s1();

Programatorii în C++ obişnuiesc să scrie mai întâi prototipurile tuturor funcţiilor utilizate de program - fără main() - iar după funcţia main() să le definească. În acest fel, orice funcţie - mai puţin main() - poate fi apelată din oricare alta:

#include <iostream.h>

void s1(); // prototip s1 void s2(); // prototip s2

main() s1();

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

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

Page 55: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 55

2.4. Aplicaţii care folosesc subprograme

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

∞∈+

∞∈+

∈++

=

).( pentru ,

,-1);(- pentru

[-1,1]; pentru ,2

,11

6,1

11

)(

xx

xx

xx

x

xf .

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.

Rezolvare. Programul este prezentat în continuare:

Varianta Pascal Varianta C++

var a,b,va,vb:real;

function f(x:real):real; var y:real; begin y:=x+1; if x<-1 then f:=y else if x>1 then f:=6/y else f:=y/(1+x*x) end;

begin write('a='); readln(a); write('b='); readln(b); va:=f(a); vb:=f(b); if va>vb then writeln(va) else writeln(vb) end.

# include <iostream.h>

float f(float x) float y; y=x+1; if(x<-1) return y; else if(x>1) return 6/y; else return y/(1+x*x);

void main() float a,b,va,vb; cout<<"a="; cin>>a; cout<<"b="; cin>>b; va=f(a); vb=f(b); if (va>vb) cout<<va<<endl; else cout<<vb<<endl;

Aplicaţia 2.2. Se citesc două numere întregi m şi n. Se cere să se tipărească cel mai mare divizor comun şi cel mai mic multiplu comun al lor.

Rezolvare. Cunoaşteţi deja modul în care se calculează cel mai mare divizor comun pentru două numere naturale date. Cel mai mic multiplu comun a două numere se poate determina împărţind produsul lor la cel mai mare divizor comun al lor. Prin urmare, scriem o funcţie cmmdc cu doi parametri formali m şi n, care întoarce o valoare întreagă - cel mai mare divizor comun al lor.

Page 56: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

56 Capitolul 2. Subprograme

Varianta Pascal Varianta C++

var m,n:integer;

function cmmdc(m,n:integer) :integer; begin while m<>n do if m>n then m:=m-n else n:=n-m; cmmdc:=m; end;

begin write ('m='); readln(m); write ('n='); readln(n); writeln(cmmdc(m,n)); writeln(m*n div cmmdc(m,n)); end.

#include <iostream.h>

int cmmdc(int m, int n) while (m!=n) if (m>n) m-=n; else n-=m; return m;

void main() int cm,m,n; cout<<"m="; cin>>m; cout<<"n="; cin>>n; cm=cmmdc(m,n); cout<<cm<<" "<<m*n/cm;

Exerciţii

1. Găsiţ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 cmmmc care să returneze cel mai mic multiplu comun a două numere naturale. Stabiliţi locul unde trebuie integrat acesta în programul dat şi modificaţi programul principal astfel încâ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 c există? În orice expresie se utilizează numai apeluri ale funcţiei cmmdc şi variabilele a, b şi c. De exemplu, trei expresii sunt:

cmmdc(cmmdc(a,b),c), cmmdc(cmmdc(b,a),c) şi cmmdc(c,cmmdc(a,b)).

Aplicaţia 2.3. Se citeşte un vector cu n componente numere întregi. 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 c. Apoi, pentru fiecare dintre componentele următoare, se calculează cel mai mare divizor comun între valoarea curentă memorată în c şi cea reţinută de componenta curentă a vectorului. Cel mai mare divizor comun va fi reţinut din nou de c.

Page 57: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 57

Varianta Pascal Varianta C++

var v:array[1..20]of integer; n,i,c:integer;

function cmmdc(m,n:integer) :integer; var r:integer; begin repeat r:=m mod n; m:=n; n:=r until r=0; cmmdc:=m end;

begin write ('n='); readln(n); write('Componente vector:'); for i:=1 to n do read(v[i]); c:=cmmdc(v[1],v[2]); for i:= 3 to n do c:=cmmdc(c,v[i]); writeln(c) end.

#include <iostream.h> int v[21],n;

int cmmdc(int m, int n) int r; do r=m%n; m=n; n=r; while (r!=0) return m;

void main() int i,c; cout<<"n="; cin>>n; cout<<"Componente vector:"; for(i=1;i<=n;i++) cin>>v[i]; c=cmmdc(v[1],v[2]); for(i=3;i<=n;i++) c=cmmdc(c,v[i]); cout<<c;

Exerciţiu

În locul secvenţei

c cmmdc(v[1],v[2]) pentru i=3,n execută

c cmmdc(c,v[i]) sfârşit pentru

dorim să utilizăm, mai compact, secvenţa:

pentru i=1,n execută c cmmdc(c,v[i])

sfârşit pentru

Care este valoarea pe care trebuie să o aibă variabila c înaintea acestei secvenţe?

Aplicaţia 2.4. Triunghi special. Se citesc două numere naturale m şi n mai 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

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

2. m=7 n=9

9 1 2 3 4 5 6 7 8 9 1 2 3 4 5 6 7 8 9 1 2 3 4 5 6 7 8 9 Junior Division

Page 58: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

58 Capitolul 2. Subprograme

Rezolvare. Se observă că dacă n=5 trebuie să obţinem şirul 5, 6, 7, 8, 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.

Varianta Pascal Varianta C++

var m,n:integer;

function suc(k:integer):integer; begin if k=9 then succ:=1 else succ:=k+1 end;

procedure tipar(m,n:integer); var i,j:integer; begin for i:=1 to m do begin for j:=1 to i do begin write (n); n:=suc(n) end; writeln end end;

begin write('m='); readln(m); write('n='); readln(n); tipar(m,n) end.

#include <iostream.h>

int suc(int k) if (k==9) return 1; else return k+1;

void tipar(int m,int n) int i,j; for(i=1;i<=m;i++) for (j=1;j<=i;j++) cout<<n; n=suc(n); cout<<endl;

void main() int m,n; cout<<"m="; cin>>m; cout<<"n="; cin>>n; tipar(m,n);

Exerciţii

1. După apelul tipar(m,n) 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 nsuc(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=1,m execută pentru j=1,i execută scrie suc(n) sfârşit pentru scrie EOLN sfârşit pentru

Page 59: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 59

Aplicaţia 2.5. Se citesc două numere naturale m<n. Se cere să se tipărească toate numerele palindrom aflate între m şi n. Un număr este palindrom dacă, citit de la stânga la dreapta şi citit de la dreapta către stânga, rezultatul este acelaşi. De exemplu, numărul 121 este palindrom.

Rezolvare. Să analizăm problema. Este necesar să fie testate toate numerele între 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

palindromic. Pentru aceasta, vom scrie o funcţie numită palin care are rolul de a testa dacă un număr este sau nu palindrom. Analiza proprietăţii de număr palindromic o efectuăm construind răsturnatul (inversul) acelui număr şi verificăm dacă 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

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

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

palin, iar funcţia palin apelează la rândul ei funcţia invers. Programul apelant al funcţiei palin este programul principal, în timp ce programul apelant al funcţiei invers este subprogramul palin.

Care este avantajul scrierii fiecărei funcţii? Acesta este dat de faptul că,

atunci când scriem funcţia, ne concentrăm exclusiv asupra ei, deci posibilitatea de a greşi este mai mică. Dacă dispunem de funcţie, algoritmul în continuare devine mult mai simplu. În plus, este posibil ca oricare dintre funcţii să poată fi folosită şi în alte cazuri, la rezolvarea altor probleme.

Observaţii

Ordinea definirii celor două subprograme devine importantă, deoarece funcţia

invers, 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.

Varianta Pascal Varianta C++

function invers(n:longint):longint; var r:longint; begin r:=0; while n<>0 do begin r:=r*10+n mod 10; n:=n div 10 end; invers:=r end;

#include <iostream.h>

long invers(long n) long r=0; while(n!=0) r=r*10+n%10; n/=10; return r;

Page 60: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

60 Capitolul 2. Subprograme

function palin(n:longint):boolean; begin palin:=invers(n)=n; end;

var m,n,i:integer; begin write('m='); readln(m); write('n='); readln(n); for i:=m to n do if palin(i) then writeln(i); end.

int palin(long n) return invers(n)==n;

void main() int m,n,i; cout<<"m="; cin>>m; cout<<"n="; cin>>n; for(i=m;i<=n;i++) if (palin(i)) cout<<i<<endl;

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 transmis 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 superpalindrom 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).

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ă 23+53=133. Cu numărul obţinut procedăm la fel: 13+33+33=55. Repetăm procedeul: 53+53=250. Şi iar: 23+53+03=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 Division

Rezolvare. Suma cuburilor cifrelor unui număr natural este calculată de funcţia suma. Numărul n (citit) va fi memorat de prima componentă 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 suma calculează suma cuburilor cifrelor unui număr. Funcţia este verifică dacă valoarea n există deja în şirul s format din ls componente.

Page 61: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 61

Varianta Pascal Varianta C++

type vec=array[1..100] of longint; var n:longint; seria:vec; ind,i:integer;

function suma(n:longint):longint; var s,i,c:integer; begin s:=0; while n<>0 do begin c:=n mod 10; s:=s+c*c*c; n:=n div 10 end; suma:=s end;

function este(n:longint;var s:vec; ls:integer):boolean; var i:integer; begin este:=true; for i:=1 to ls do if s[i]=n then este:=false end;

begin write('n='); readln(n); seria[1]:=n; ind:=1; repeat n:= suma(seria[ind-1]); ind:=ind+1; seria[ind]:=n; until este(n,seria,ind-1); for i:=1 to ind do writeln(seria[i]); end.

#include <iostream.h>

long n; int seria[100],ind,i;

int suma(int n) int s=0,c; while (n) c=n%10; s+=c*c*c; n/=10; return s;

int este(long n,int s[100], int ls) int i; for(i=0;i<ls;i++) if(s[i]==n) return 0; return 0;

void main() cout<<"n="; cin>>n; ind=0; seria[ind]=n; do n=suma(seria[ind-1]); seria[++ind]=n; while(! este(n,seria, ind-1)); for (i=1;i<=ind;i++) cout<<seria[i]<<endl;

Aplicaţia 2.7. Să se scrie o procedură care citeşte o matrice cu elemente numere întregi din fişierul text MAT.TXT. 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 linii ale fişierului conţin, în ordine, elementele aflate pe fiecare linie a matricei. Verificaţi subprogramul definit, integrându-l într-un program de testare. Exemplu: fişierul f.in conţine: 3 4 3 1 7 9 1 2 3 4 9 1 3 8

Page 62: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

62 Capitolul 2. Subprograme

Rezolvare. Pentru a putea fi folosită pentru citirea oricărei matrice de numere întregi, procedura va trebui să utilizeze ca parametri de ieşire matricea, numărul de linii şi numărul de coloane ale matricei.

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

Varianta Pascal Varianta C++

type mat=array[1..100,1..100] of integer; var m:mat; linii,coloane,i,j:integer;

procedure citesc(var t:mat; var m,n:integer); var i,j:integer; f:text; begin assign(f,'mat.txt'); reset(f); readln(f,m,n); for i:=1 to m do for j:=1 to n do read(f,t[i,j]); close(f) end;

begin citesc(m,linii,coloane); for i:= 1 to linii do begin for j:=1 to coloane do write(m[i,j],' '); writeln end end.

#include <fstream.h>

void citesc(int mat[100][100], int& m, int& n) fstream f("fis.txt",ios::in); f>>m>>n; for (int i=1;i<=m;i++) for (int j=1;j<=n;j++) f>>mat[i][j]; f.close();

void main() int m,n, mat[100][100]; citmat(m,n,mat); for (int i=1;i<=m;i++) for (int j=1;j<=n;j++) cout<<mat[i][j]<<" "; cout<<endl;

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).

Page 63: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 63

2. Coifurile NFL . Există 28 de echipe de fotbal în NFL (Liga Naţională de Fotbal în SUA). Unele magazine au aparate care oferă coifuri în miniatură ale echipelor contra 0.25$. Când pui moneda în aparat, nu ştii ce coif vei obţine, oricare din cele 28 de coifuri diferite îţi poate veni din aparat (sunt oferite aleator). Scrieţi un program care simulează punerea monezii într-un astfel de aparat până când se obţin toate cele 28 de coifuri. Programul trebuie să tipărească şi totalul cheltuielilor efectuate pentru obţinerea celor 28 de coifuri.

Junior Division 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 returnează aria sa.

4. Scrieţi un subprogram care primeşte ca parametru lungimea laturii unui pătrat şi returnează 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 semnificaţia de lungimi pentru 3 segmente. Funcţia va returna 1 dacă cele trei segmente pot forma un triunghi şi 0, în 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ărului x x xn1 2+ + +.... ;

• ultima cifră a numărului x x x xn1

2 3 ..... . Exemplu: n=3; x1=11; x2=4; x3=3. Programul tipăreşte:

• 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ărire a unei matrice cu m linii şi n coloane; • se scrie un subprogram care adună două matrice; • programul principal rezultă din apelul acestor subprograme.

Page 64: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

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 înmulţ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. 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 parametru.

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

14. Scrieţi 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. În 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+1 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, înmulţ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-l 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

.)(0

∑=

−=+n

k

knkkn

n xaCax

Page 65: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 65

19. Ce afişează programul următor?

Varianta Pascal Varianta C++

var z:integer;

procedure test(a:integer); begin a:=a+1; write(a); end;

begin z:=5; test(z); write(z) end.

#include <iostream.h>

void test(int a) a++; cout<<a;

void main() int z=5; test(z); cout<<z;

a) 6 6; b) 5 6; c) 6 6; d) 6 5. 20. Ce afişează programul următor?

Varianta Pascal Varianta C++

function f(x:integer):integer; begin f:=x+1 end;

procedure t(a:integer); begin a:=a+1; writeln(a); end;

begin t(f(f(f(1)))) end.

#include <iostream.h>

int f(int x) return x+1;

void t(int a) a++; cout<<a;

void main() t(f(f(f(1))));

a) 3; b) 4; c) 5; d) eroare de sintaxă. 21. Ce afişează programul următor:

Varianta Pascal Varianta C++ var a:integer;

function f:integer; var a:integer; begin a:=3; write(a); f:=a; end;

begin a:=2; writeln(a,f); end.

#include <iostream.h> int a;

int f() int a=3; cout<<a; return a;

void main() a=2; cout<<a; cout<<f();

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

Page 66: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

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?

Varianta Pascal Varianta C++

var a,b,i,j,t:integer;

procedure increment(var x:integer); begin x:=x+1 end;

begin readln(a); readln(b); t:=a; ... writeln(t) end.

#include <iostream.h> int a,b,i,j,t;

void increment(int& x) x++;

void main() cin>>a; cin>>b; t=a; ... cout<<t;

a) for i:=1 to a do

for j:=1 to b do increment(t);

for(i=1;i<=a;i++) for(j=1;j<=b;j++) increment(t);

b) for i:=1 to a do for j:=1 to b-1 do increment(t);

for(i=1;i<=a;i++) for(j=1;j<b;j++) increment(t);

c) for i:=1 to b do for j:=1 to t do increment(t);

for(i=1;i<=b;i++) for(j=1;j<=t;j++) increment(t);

d) for i:=1 to a do increment(t);

for(i=1;i<=b;i++) increment(t);

23. Pentru varianta de limbaj preferată, stabiliţi care dintre următoarele antete de subprogram sunt corecte:

a) procedure p(n:integer; var v:array[1..10] of integer);

void p(int n, int& v[10])

b) function p(n:integer;var v:real);

p(int n, float& v)

c) procedure p(n:integer;v:real); void p(int n, float v) d) function p(n):integer; int p(int& n; float v)

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

Varianta Pascal Varianta C++

function f(x:real):real; begin if x<3 then f:=3*x+1 else f:=x-1 end;

function g(x:real):real; begin if x<10 then g:=4*x else g:=x-1 end;

float f(float x) if (x<3) return 3*x-1; else return x-1; float g(float x) if (x<10) return 4*x; else return x-1;

Page 67: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 67

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

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

Varianta Pascal Varianta C++

function Cat(n1,n2:integer): longint; var inv:integer; begin inv:=0; while n2<>0 do begin inv:=inv*10+ n2 mod 10; n2:=n2 div 10 end; while inv<>0 do begin n1:=n1*10+inv mod 10; inv:=inv div 10 end; Cat:=n1 end;

long Cat(int n1,int n2) int inv=0; while (n2!=0) inv=inv*10+n2%10; n2=n2/10; while (inv!=0) n1=n1*10+inv%10; inv=inv/10; return n1;

a) Cat(12,10) returnează 1210; b) Cat(10,12) returnează 1012; c) Cat(0,12) returnează 12; d) Cat(12,0) returnează 120. 26. Fiind dată funcţia următoare, care dintre afirmaţii este falsă?

Varianta Pascal Varianta C++

function Cat(n1,n2:integer) :longint; var n2s:integer; begin n2s:=n2; while n2 <>0 do begin n2:=n2 div 10; n1:=n1*10 end; Cat:=n1+n2s end;

long Cat(int n1,int n2) int n2s; n2s=n2; while (n2!=0) n2/=10; n1*=10; return n1+n2s;

a) Cat(-12,13) returnează -1213; b) Cat(12,0) returnează 12; c) Cat(0,12) returnează 12; d) Cat(13,12) returnează 1312.

Page 68: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

68 Capitolul 2. Subprograme

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

Varianta Pascal Varianta C++

type vect=array[1..40] of byte;

procedure Concat(A,B:vect; m,n:byte;var C:vect;var p:byte);

procedure Reun(A,B:vect; m,n:byte;var C:vect;var p:byte);

procedure Inters(A,B:vect; m,n:byte;var C:vect;var p:byte);

procedure Dif(A,B:vect;m,n:byte; var C:vect;var p:byte);

procedure Elimin(var A:vect; var m:byte);

void Concat(int A[20], int B[20], int m, int n, int C[40], int& p)

void Reun(int A[20], int B[20], int m, int n, int C[40], int& p)

void Inters(int A[20], int B[20],int m, int n, int C[40], int& p)

void Dif(int A[20], int B[20], int m, int n, int C[40], int& p)

void Elimin (int A[20],int& m)

în care: - Concat 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 A=(1,2,3,7), m=4, B=(2,5,3), n=3, se obţine C=(1,2,3,7,2,5,3), p=7.

- Reun are rolul de a returna vectorul C cu p componente, vector obţinut din reuniunea mulţimilor de numere naturale din vectorii A cu m elemente şi B cu n elemente (presupunând 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=(1,2,3,7,5), p=5.

- Inters are rolul de a returna vectorul C cu p componente, vector obţinut din intersecţia mulţimilor de numere naturale din vectorii A cu m elemente şi B cu n elemente (în aceleaşi condiţii ca şi în cazul reuniunii). De exemplu, din A=(1,2,3,7), m=4, B=(2,5,3), n=3, se obţine C=(2,3), p=2.

- Dif are rolul de a returna vectorul C cu p componente, vector obţinut din diferenţa mulţimilor de numere naturale din vectorii A cu m elemente şi B cu n elemente (în aceleaşi condiţii ca şi în cazul reuniunii). De exemplu, din A=(1,2,3,7), m=4, B=(2,5,3), n=3, se obţine C=(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=5, 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 doi vectori? Rezultatul se va găsi în vectorul Z.

Page 69: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 69

a) Concat(X,Y,a,b,Z,p); Elimin(Z,p); b) Elimin(X,a); Elimin(Y,b); Concat(X,Y,a,b,Z,p); c) Elimin(Concat(X,Y,a,b,Z,p),p); d) Elimin(X,a); Elimin(Y,b); Reun(X,Y,a,b,Z,p); B. Se citesc doi vectori X cu a numere naturale şi Y cu b numere naturale. Elementele din fiecare vector sunt distincte. Care dintre secvenţele de mai jos calculează intersecţia mulţimilor numerelor naturale reţinute de cei doi vectori? Rezultatul se va găsi în vectorul Z. a) Dif(X,Y,a,b,Z,p); Dif(X,Z,a,p,Z,p); b) Dif(X,a,Y,b,Z,p); Dif(Y,Z,b,p,Z,p); c) Intersect(X,a,Y,b,Z,p) d) Intersect(X,Y,a,b,Z,p);

C. Se citesc doi vectori X cu a numere naturale şi Y cu b numere naturale. Elementele din fiecare vector sunt distincte. Care dintre secvenţele de mai jos nu calculează nici X-Y, nici Y-X? Rezultatul se va găsi în vectorul Z. a) Inters(X,Y,a,b,Z,p); Dif(X,Z,a,p,Z,p); b) Inters(X,Y,a,b,Z,p); Dif(Y,Z,b,p,Z,p); c) Inters(Y,X,a,b,Z,p); Dif(X,Z,a,b,Z,p); d) Dif(X,Y,a,b,Z,p); 28. Se consideră subprogramul următor:

Varianta Pascal Varianta C++

procedure Afis (n:integer); var i,j,k:integer; begin k:=2*n-1;i:=1; while i<=n do begin for j:=1 to (k-2*i+1)div 2 do write (' '); for j:=1 to 2*i-1 do write ('*'); i:=i+1; writeln end end;

void Afis (int n) int i,j,k; k=2*n-1; i=1; while (i<=n) for(j=1;j<=(k-2*i+1)/2;j++) cout<<' '; for(j=1;j<=2*i-1;j++) cout<<'*'; i++; cout<<endl;

Page 70: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

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ă în total (pe toate rândurile)? a) 8; b) 16; c) 12; d) 20. C. Dacă n=20, câte 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:

Varianta Pascal Varianta C++

type vect=array[1..10] of byte;

procedure xx(var V:vect); var A:array[1..4,1..4] of byte; i,j,z:byte; begin for i:=1 to 4 do for j:=4 downto 1 do begin A[i,j]:=V[i] mod 2; V[i]:=V[i] div 2; end; for i:=1 to 4 do begin z:=A[1,i]; for j:=2 to 4 do z:=z*2+A[j,i]; v[i]:=z; end; end;

void xx(int V[10]) int A[4][4],i,j,z; for (i=0;i<4;i++) for (j=3;j>=0;j--) A[i][j]=V[i]%2; V[i]/=2; for (i=0;i<4;i++) z=A[0][i]; for (j=1;j<4;j++) z=z*2+A[j][i]; V[i]=z;

A. Care este conţinutul lui V după apel, dacă înainte este V=(1,2,3,4)? a) V=(1,2,3,4); b) V=(4,3,2,1); c) V=(0,1,6,10); d) V=(0,4,5,9). B. Care trebuie să fie iniţial conţinutul lui V, dacă după apel este V=(1,6,10,0)? 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 următoare de configuraţii ale lui V la intrarea în subprogram, conţinutul acestuia la ieşire coincide cu cel de la intrare? a) V=(13,10,5,11); b) V=(2,3,9,13); c) V=(0,1,2,12); d) V=(1,4,9,15.

Page 71: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 71

30. Se consideră subprogramul următor:

Varianta Pascal Varianta C++

type vect=array[1..10] of integer;

function Cmp(m,n:integer; A,B:vect):byte; var i:integer; begin i:=1; while (i<=m) and (i<=n) and (A[i]=B[i]) do i:=i+1; if (i>n) and (i>m) then Cmp:=0 else if i>n then Cmp:=1 else if i>m then Cmp:=-1 else if A[i]<B[i] then Cmp:=-1 else Cmp:=1; end;

int Cmp(int m, int n, int A[10], int B[10]) int i=0; while (i<=m && i<=n && A[i]==B[i]) i++; if(i>n && i>m) return 0; else if(i>n) return 1; else if(i>m) return -1; else if(A[i]<B[i]) return -1; else return 1;

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=(1,2), B=(1,2,3); c) m=2, n=3, A=(1,3), B=(1,2,3); d) m=1, n=1, A=(1), B=(1). B. Pentru m=5, n=3, A=(1,2,5,7,2), B=(1,2, ), stabiliţi ce valoare poate fi a treia componentă a vectorului B astfel încât funcţia să returneze valoarea 0. a) 3; b) 5; c) nu există o astfel de valoare ; d) 2. C. Pentru X=(1, ,5,7), Y=(1,2, ,7), stabiliţi ce valori pot fi scrise în casete astfel încât apelul cmp(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 5; c) nu există astfel de valori; d) 3 şi respectiv 6. D. 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=(1,2), C=(1,3); d) A=(2), B=(1), c=(0).

Page 72: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

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 înălţimea) pe baza valorii volumului conului şi a ariei sale laterale.

C. Obţinerea descompunerii în 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 îl parcurge şi care este înregistrat 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 început al sectorului de drum unde sunt amplasate camerele: d1,d2,…,dn şi momentele la care s-a înregistrat trecerea mobilului prin dreptul fiecărei camere t1,t2,…,tn.

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 în binar pe o linie a matricei. Se afişează numerele zecimale obţinute din numerele în 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) 0; b) -1; c) 1; d) 0; B. c); C. b); D. d).

Page 73: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

73

Capitolul 3

Şiruri de caractere

3.1. Generalităţi

Programele nu lucrează numai cu numere. Ele utilizează din plin şirurile de caractere. În absenţa lor ar fi foarte greu ca, de exemplu, un program să poată citi sau afişa numele unei persoane sau numele unei localităţi. Ne putem imagina un şir de caractere ca un vector în care fiecare componentă reţine un caracter. După cum se ştie, fiecare caracter este memorat prin utilizarea codului ASCII. Şirurile de caractere pot fi citite, afişate şi prelucrate cu ajutorul unor subprograme puse la dispoziţie de limbaj.

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

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 unui ş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 unui ş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?

Aţi învăţat, la ”Tehnologia Informaţiei şi a Comunicaţiilor”, cum, de exemplu, putem crea tabele (Excel, Word sau Access). Am văzut că liniile tabelului, pot conţine valori numerice, dar şi alfabetice, 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 de caractere există o relaţie de ordine, corezpunzătoare ordonării alfabetice. În 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,

Page 74: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

74 Capitolul 3 – Şiruri de caractere

Programul alăturat are declarată o variabilă t, de tip string. Ea este iniţializată cu şirul 'Iepuras', apoi este afişată.

să introduceţi numărul 12, veţi introduce de fapt şirul de caractere '12'. Programul va converti şirul de caractere '12' în numărul î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 '1@'. 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. În 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 numara 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ă-l declarăm cu type.

var t: string; begin t := 'Iepuras'; writeln(t); end.

Page 75: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 75

Pentru început, să analizăm modul în care o variabilă de tip string memorează un şir de caractere. Pentru aceasta, pornim de la exemplul dat în programul anterior:

Ce observăm? Pentru o variabilă de tip string se rezervă automat un vector cu 256 de octeţi, numerotaţi de la 0 la 255. Primul dintre ei are rolul de a reţine numărul de octeţi ocupaţi de şir.

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

Întrucât o variabilă de tip string memorează cuvintele sub formă de vector de caractere, există 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 := 'Iepuras'; for i := 1 to n 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 := 'Iepuras'; t[6] := 'i'; write(t); end.

În loc de 'a', t[6] reţine 'i'. Prin urmare, programul afişează 'Iepuris'.

În cazul variabilelor de tip string există posibilitatea ca atribuirea să se facă direct, nu pe litere, aşa cum rezultă din programul următor:

var t, z: string; begin t := 'Iepuras'; z := t; write(z); end.

În urma atribuirii, variabila z reţine cuvântul "iepuras" şi acesta este afişat.

t[0] t[1] t[2] t[3] t[4] t[5] t[6] t[7] t[8] t[255]

7 I e p u r a s

Page 76: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

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ă în aşa fel încât să ocupe un număr mai mic de octeţi.

Exemplu: var t: string[4];

Variabila t, ocupă 5 octeţi. Primul, cel de indice 0, are rolul de a reţine lungimea cuvântului memorat. În acest caz, variabila poate reţine cuvinte ce au cel mult 4 caractere. Programul afişează 'mama'.

var t: string[4]; begin t := '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 t: 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 nume: string; ... nume:= '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ţii care uşurează mult lucrul cu şirurile de caractere.

3.2.2. Concatenarea şirurilor

Pentru a concatena două şiruri de caractere se foloseşte operatorul '+'. Operatorul '+' este binar (adică are doi operanzi) şi poate acţiona asupra datelor de tip string. Ce înţelegem prin concatenare?

Page 77: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-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: primul şir (cel aflat în stânga operatorului), urmat de al doilea şir (cel aflat în dreapta operatorului).

Exemplu: program st6; var t, z: string; begin t := 'acest'; z := ' exemplu'; t := t+z; writeln(t); end.

În programul st6 se concatenează două şiruri de caractere (t:=t+z;):

• variabila t reţine şirul 'acest';

• variabila z reţine şirul ' exemplu';

• prin operaţia (t+z;) se obţine şirul 'acest exemplu';

• şirul obţinut este atribuit variabilei t.

Observaţii

1. Dacă am fi scris t:=z+t; programul ar fi afişat ' exempluacest'. De aici tragem concluzia că operaţia de concatenare nu este comutativă (contează ordinea în care sunt trecuţi cei doi operanzi).

2. Observăm cât este de important să ţinem cont de poziţia blank-ului (al doilea şir este precedat de blank). Observaţi că dacă am făcut concatenarea z+t, am obţinut şirul ' exempluacest', care este precedat de blank.

3. Trebuie să avem în vedere că şirul obţinut în urma concatenării să poată fi memorat î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 fi 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:

• a=b - 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.

Page 78: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

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 care au fost codificate caracterele, după o anumită logică: dacă un caracter urmează altuia, în ordine alfabetică, atunci el are drept cod un număr mai mare decât al caracterului căruia îi urmează.

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 (b are 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[1] este a şi are codul mai mic decât b[1] care este b.

• a='abc', b='aba'. Atunci a>b, codul lui a[1] este egal cu codul lui b[1], codul lui a[2] este egal cu codul lui b[2], iar codul lui a[3] este mai mare decât codul lui b[3].

• a='abc', b='abca'. Aici m=3, n=4. Atunci a<b, codul lui a[1] este egal cu codul lui b[1], codul lui a[2] este egal cu codul lui b[2], codul lui a[3] este egal cu codul lui b[3] şi n>m (şirul b are mai multe caractere).

Compararea şirurilor de caractere este extrem de utilă în sortarea alfabetică a cuvintelor (ca în dicţionar). Ordinea astfel impusă se mai numeşte şi ordine lexicografică.

Page 79: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-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] := v[i+1]; v[i+1] := 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:

• şirul 'mama' are lungimea 4;

• şirul 'mama ' are lungimea 5 (şi blank-ul este caracter).

Pentru aflarea lungimii unui şir avem două posibilităţi:

1. Prin utilizarea funcţiei length, care are forma generală:

function length(S: String): Integer;

şi întoarce lungimea şirului S - număr întreg.

Page 80: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

80 Capitolul 3 – Şiruri de caractere

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 0 reţine lungimea şirului. Pentru a-l obţine folosim funcţia ord astfel: ord(a[0]). De ce aşa? Atunci când scriem a[0] ne referim la un caracter. Folosind această expresie obţinem caracterul care are codul dat de număr (în binar). Deci, pentru a obţine codul său, utilizăm funcţia ord.

În programul următor se afişează lungimea unui şir citit. Tipărirea se face în cele două moduri prezentate:

var a: string; begin write('a= '); readln(a); writeln('lungimea sirului a este ', length(a)); writeln('lungimea sirului a este ', ord(a[0])); end.

Un caz aparte de şir este şirul vid. Prin şir vid înţelegem un şir fără nici un caracter. Evident, şirul vid are lungimea 0. 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 'rni'. 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 multe ori.

• Şirul 'harnic' nu are ca subşir şirul 'rnit', chiar dacă primele trei caractere (rni) se regăsesc în şirul iniţial.

Page 81: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 81

Funcţii ce acţionează asupra şirurilor de caractere

Există mai multe proceduri şi funcţii care acţionează asupra şirurilor de caractere. Acestea sunt prezentate î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;

unde:

• variabila s (de tip string) - conţine şirul din care se face extragerea; • inceput - reţine poziţia de început a subşirului care se extrage; • lungime - reţine numărul de caractere care se extrag.

În cazul în care prin parametrul lungime specificăm mai multe caractere decât are şirul, se extrag caracterele până la sfârşitul şirului.

Fie 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, variabila b va reţine subşirul 'tex'.

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ătorii parametri:

• subsir - subşirul căutat; • sir - şirul în care se face căutarea.

Funcţia returnează:

• 0, 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'. Se caută subşirul 'bcd'. Acesta este găsit, iar funcţia returnează 2 (poziţia de început a subşirului). Dacă se caută subşirul 'cz', funcţia returnează 0 ('cz' nu este subşir al şirului 'abcde').

Page 82: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

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: integer; begin write('a= '); readln(a); write('b= '); readln(b); n := pos(b,a); if n = 0 then writeln('b nu este subsir al lui a') else writeln('b este subsir al lui a si incepe in pozitia ',n); end.

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:

procedure insert (sir_de_ins: string; var şir_unde_ins: string; 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= '); readln(a); write('b= '); readln(b); insert(b, a, 3); writeln(a); end.

Î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 'abcd' şi b reţine şirul '123', programul afişează 'ab123cd'.

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.

Page 83: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 83

Exemplu:

Programul următor citeşte un şir de caractere într-o variabilă de tip string, numită a. Şirului citit i se şterge subşirul care începe în poziţia 3 şi are lungimea 2 (două caractere). De exemplu, dacă variabila a reţine şirul 'abcde', se afişează 'abe'.

var a:string;

begin write('a='); readln(a); delete(a,3,2); writeln(a); end.

Aplicaţia 3.1. În programul următor se listează indicii tuturor apariţiilor caracterului citit în şir:

var sir: string; ch: string[1]; pozn, pozv: byte;

begin writeln('introduceti sirul'); readln(sir); writeln('introduceti caracterul '); readln(ch); pozn := pos(ch, sir); pozv := 0; while pozn <> 0 do begin pozv := pozv+pozn; writeln(pozv); sir := copy(sir, pozn+1, 255); pozn := pos(ch, sir); end end.

Aplicaţia 3.2. Ştergerea tuturor apariţiilor unui subşir din cadrul unui şir:

var sir, subsir: string; poz, lung: byte;

begin writeln('introduceti sirul'); readln(sir); writeln('introduceti subsirul '); readln(subsir); lung := length(subsir); poz := pos(subsir, sir); while poz <> 0 do begin delete(sir, poz, lung); poz := pos(subsir, sir); end; writeln(sir); end.

Page 84: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

84 Capitolul 3 – Şiruri de caractere

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, lung: byte;

begin writeln('introduceti sirul '); readln(sir); writeln('introduceti sirul care se sterge); readln(sir_sters); writeln('introduceti sirul care se adauga); readln(sir_adaugat); lung := length(sir_sters); poz := pos(sir_sters, sir); while poz <> 0 do begin delete(sir,poz,lung); insert(sir_adaugat, sir, poz); poz := pos(sir_sters, sir); end; writeln(sir); end.

3.2.6. Conversia de la şiruri la valori numerice şi invers

Fie şirul '123'. Evident, acesta este diferit de numărul 123. 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. procedure Str(X [: Lg [: Zec ]]; var S:string);

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: string; n: integer;

begin write ('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ă

Page 85: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 85

am fi afişat conţinutul variabilei n, am avea acelaşi rezultat. Atunci la ce folosesc astfel de conversii? Pentru a putea da răspunsul trebuie să mai învăţăm ceva despre modul în care putem apela procedura str. Am fi putut să scriem apelul şi aşa: str(n:4,a);. Observaţi faptul că, după numele variabilei care se afişează (n) au fost puse caracterul ':' şi numărul 4. Aceasta înseamnă că am cerut ca rezultatul (de tip string) să ocupe 5 octeţi - unde primul reţine lungimea, deci 4 octeţi pentru memorare.

Să analizăm acum modul de efectuare a conversiei.

⇒ Dacă variabila n reţine numărul 1234, variabila a va reţine şirul '1234'.

⇒ Dacă variabila n reţine numărul 123, atunci a va reţine şirul ' 123'. Rezultă că şirul, fără octetul de lungime, ocupă 4 octeţi. În astfel de cazuri, şirul este completat în stânga cu numărul de blank-uri necesar.

⇒ Dacă variabila n reţine numărul 12345 atunci şirul va fi '12345'. Cu 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? Programatorul va avea grijă ca întotdeauna numărul de octeţi ai şirului să fie mai mare sau egal cu numărul de octeţi ai valorii convertite. În acest fel, la afişare, vom şti care este spaţiul ocupat de şir şi putem să afişăm rezultatele aliniate (tabele). În continuare, ne ocupă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 şirul efectiv să ocupe 10 caractere, dintre care ultimele două 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 numărul este rotunjit. Dacă numărul solicitat de zecimale ar fi fost 0, 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

Page 86: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

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 '-67.79'.

În cazul în care numărul care se converteşte este pozitiv, semnul '+' 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 '1a2' către o valoare de tip integer, conversia nu reuşeşte, pentru că şirul conţine caracterul 'a'.

Pentru realizarea conversiei utilizăm procedura val. Ea are trei parametri şi anume:

procedure val(s:string; var variabila_numerica; var cod_er:integer);

unde:

• s - conţine şirul ce urmează a fi convertit;

• variabila_numerica - variabila de tip întreg sau real care va reţine rezultatul conversiei (valoarea numerică).

• cod_er - variabilă de tip întreg. După conversie, aceasta va reţine 0 dacă conversia a reuşit sau o valoare diferită de 0, în caz contrar.

Să analizăm programul următor:

var a: string; x, er: integer;

begin write('Sirul este '); readln(a); val(a, x, er); if er = 0 then writeln(' conversia a reusit ', x) else 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 string a. Se încearcă conversia şirului către o variabilă de tip integer. În cazul î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.

Page 87: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 87

Observaţii

Dacă şirul de caractere cifre este precedat de un număr de blank-uri, conversia reuşeşte.

Exemplu: şirul ' 123' 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 '12i' nu poate fi convertit. Excepţie fac şirurile de caractere care respectă sintaxa unei constante reale în formă ştiinţifică (de exemplu, '1.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.

Aplicaţia 3.4. Programul următor testează dacă o valoare introdusă este numerică şi dacă este cuprinsă în intervalul [10,20]:

var sir: string; eroare: integer; valoare: real;

begin writeln('introduceti sirul '); readln(sir); val(sir, valoare, eroare); if eroare<>0 then writeln('val. introdusa este eronata') else if (valoare < 10) or (valoare > 20) then writeln('val. nu este in intervalul dorit ') else writeln('ok!') end.

Page 88: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

88 Capitolul 3 – Şiruri de caractere

3.2.7. Citirea şi scrierea datelor de tip String din şi în fişiere text

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,'f1.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,'F1.txt'); rewrite (f); sir:='Marian'; write(f,sir:10); close(f); end.

Page 89: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-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ă între două caractere ", de exemplu: "calculator". Dar cum se reţine în memoria internă? Aceasta este reţinută sub forma unui vector de caractere. Primul element, cel de indice 0, reţine codul ASCII al caracterului 'c', al doilea reţine codul ASCII al caracterului 'a', ..., ş.a.m.d. Convenţia este ca ultimul octet să reţină 0 (codul caracterului nul). Prin urmare, pentru a reţine şirul "calculator" trebuie să fie rezervate 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 ASCII.

a[0] a[1] a[2] ................................... a[10]

Vectorii de caractere pot fi iniţializaţi la declarare, caracterul nul fiind memorat automat.

Exemple: char vect[11]="calculator". char vect[]="calculator". În acest caz, compilatorul face calculul

numărului de octeţi necesari. char vect[100]="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; • îl afişăm.

Programul următor realizează toate acestea: #include <iostream.h> main() char a[20]; int i; for(i=0;i<10;i++) cin>>a[i]; a[10]=0; for(i=0;i<10;i++) cout<<a[i];

c a l c u 0

Page 90: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

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 ENTER, î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;

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[0]='c', a[1]='a', ş.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").

Page 91: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 91

Observaţii

Sunt citite şi caracterele albe.

Este inserat caracterul nul.

Caracterul transmis ca ultim parametru nu este inserat în şir.

Al treilea parametru este trecut în mod facultativ. Dacă nu este trecut, se presupune că este '\n'.

Priviţi următoarele exemple:

1. Se citeşte un şir de maximum 2 caractere:

char a[10]; cin.get(a,3); cout<<a;

De exemplu, dacă tastăm 'mama' şi Enter se citeşte şirul "ma", care va fi afişat.

2. La fel ca mai sus, dar citirea se întrerupe la întâlnirea caracterului 'g' sau când au fost citite 9 caractere ale şirului.

char a[10]; cin.get(a,10,'g'); cout<<a;

În C++ pot exista mai multe funcţii cu acelaşi nume, dar care diferă prin parametrii primiţi. Astfel, există şi funcţia:

cin.get()

fără parametri. Ea are rolul de a citi un caracter (fie că este alb, fie că nu).

Observaţie. În cazul utilizării repetate a funcţiei cin.get() cu trei parametri, apare o problemă. Analizaţi programul următor:

#include <iostream.h> #include <string.h> main() char sir1[1000], sir2[25]; cout<<"sir 1 "; cin.get(sir1,1000); cin.get(); cout<<"sir 2 "; cin.get(sir2 ,25);

Dacă după prima citire nu am fi folosit funcţia cin.get() fără parametri, a doua citire nu ar mai fi fost efectuată. De ce? Sfârşitul primului şir introdus a fost marcat prin tastarea Enter. Aceasta a făcut ca în memorie (buffer) să fie păstrat caracterul '\n'. La a doua citire, noul şir va începe cu acesta. Prin logica funcţiei

Page 92: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

92 Capitolul 3 – Şiruri de caractere

cu trei parametri, citirea se face până la întâlnirea lui. În 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[]="masa"; cout<<a+1<<" "<<a+2<<" "<<a+3;

În urma executării, se tipăreşte: "asa sa 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+0;

când scriem a+1, adresăm vectorul începând cu al doilea octet;

când scriem a+2, adresăm vectorul începând cu al treilea octet;

. . .

Din acest motiv, programul tipăreşte pentru a+1 ş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+1)[0] reţine caracterul 'a', (a+1)[1] reţine caracterul 's', etc.

După cum observaţi, a+1, 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 în memoria internă se 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*.

Page 93: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 93

Considerăm următorul exemplu:

#include <iostream.h> main() char a[]="Exemplu", *p; p=a; cout<<p<<endl; p++; cout<<p<<endl; p++; cout<<p<<endl; cout<<p[1]<<endl; cout<<p-a;

Am declarat un vector de caractere numit a, pe care l-am iniţializat cu un şir. De asemenea, am declarat şi o variabilă de tip char* numită p. În aceste condiţii, putem afirma că:

p=a; este o atribuire corectă. Variabila p va reţine adresa vectorului de caractere a, adică adresa primului său octet. În schimb, atribuirea a=p este incorectă, pentru că a este o constantă ce reprezintă o adresă.

după atribuire, putem utiliza variabila p în 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[1].

se pot face şi scăderi între adrese. În acest caz, rezultatul este întreg. De exemplu, prin p-a se obţine indicele în v, al primului octet al vectorului reţinut de p. 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).

Page 94: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

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[100]; cin.get(a,100); 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ţă. În continuare, vom prezenta cele mai uzuale funcţii şi modul în 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);

şi are 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[100]="un sir", b[100]="alt sir"; strcpy (a,b); cout<<a;

În 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.

Page 95: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 95

Programul următor tipăreşte "mama merge":

#include <iostream.h> #include <string.h> main() char a[20]="mama", b[100]=" merge"; strcat (a,b); cout<<a;

Funcţia strncat are forma generală:

char *strncat(char *dest, const char *sursa, size_t nr);

şi acelaşi rol ca strcat cu deosebirea că adaugă şirului destinaţie primii nr octeţi ai şirului sursă. Adăugarea caracterelor se face înaintea caracterului nul. Funcţia returnează 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' în şirul s. Căutarea se face de la stânga la dreapta. În cazul în 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 0 (adică o adresă vidă de şir). Exemplele care urmează ne vor lămuri.

În programul următor se caută în şirul a caracterul 't'. Acesta este găsit, iar programul tipăreşte şirul "ta este". Evident, acesta este subşirul care începe cu caracterul 't'.

#include <iostream.h> #include <string.h> main() char a[20]="Acesta este"; cout<<strchr (a,'t');

Aplicaţia 3.5. În 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.

Page 96: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

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[20]="Acesta este"; cout<<strchr (a,'t')-a;;

Aplicaţia 3.6. În programul următor se citeşte un şir şi un caracter. Dacă acesta este găsit în şir, 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 ";

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 0 (adică o adresă nulă de şir).

Aplicaţia 3.7. În programul următor se listează indicii tuturor apariţiilor caracterului citit în şir:

#include <iostream.h> #include <string.h> main() char a[100], *t,c; cout<< "introduceti sirul "; cin.get(a,100); 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.

Page 97: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 97

Funcţia strrchr are forma generală:

char *strrchr(const char *s, int c);

şi acelaşi rol cu strchr, deosebirea fiind că întoarce adresa ultimei apariţii a caracterului (căutarea se face de la dreapta către stânga). Ea este utilizată în ipoteza în care se caută ultima apariţie a caracterului în cadrul şirului.

3.3.7. Compararea şirurilor

Funcţia strcmp are forma generală:

int strcmp(const char *s1, const char*s2);

şi rolul de a compara două şiruri de caractere Valoarea returnată este:

• <0, dacă s1<s2;

• =0, dacă s1=s2;

• >0, dacă s1>s2.

Dar care este mecanismul prin care se compară două şiruri de caractere?

Fie m numărul de caractere al şirului s1 şi n numărul de caractere al şirului s2. Să presupunem că primele i caractere ale lui s1 coincid cu primele i ale lui s2.

În cazul în care codul caracterului i+1 al şirului s1 este mai mare decât codul caracterului corespunzător şirului s2, avem s1>s2.

În cazul în care codul caracterului i+1 al şirului s1 este mai mic decât codul caracterului corespunzător şirului s2, avem s1<s2. În cazul în 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+1, caz în care se compară ca înainte caracterele de pe poziţia i+2;

• s1 are i+1 caractere, iar s2 are un număr de caractere mai mare decât i+1, în acest caz s1<s2;

• s2 are i+1 caractere, iar s1 are un număr de caractere mai mare decât i+1, în acest caz s1>s2;

• atât s1 cât şi s2 au i+1 caractere, caz în care s1=s2.

Pe scurt, un şir s1 este mai mic ca altul s2, dacă în dicţionar s1 ar figura înaintea lui s2.

Exemple: "soare">s; "tata">mama;

Page 98: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

98 Capitolul 3 – Şiruri de caractere

Probaţi relaţia de 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<0) cout<<"a<b"; else if (semnal>0) cout<<"a>b"; else cout<<"a=b";

Funcţia strcmp face distincţie între literele mari şi mici ale alfabetului.

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 mici.

Vectori de cuvinte. Există aplicaţii în care este necesar să se lucreze cu n cuvinte - înţ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];

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[0] (primul cuvânt), a[1] cuvântul al doilea, ş.a.m.d.

Aplicaţia 3.8. În programul următor se citesc n cuvinte. Acestea sunt sortate alfabetic:

#include <iostream.h> #include <string.h> main() char cuvinte[10][25], man[25]; int i,n,gasit; cout<<"n="; cin>>n; for (i=0;i<n;i++) cout<<"cuvant "; cin>>cuvinte[i];

Page 99: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 99

do gasit=0; for (i=0;i<n-1;i++) if (strcmp(cuvinte[i],cuvinte[i+1])>0) strcpy(man,cuvinte[i]); strcpy(cuvinte[i],cuvinte[i+1]); strcpy(cuvinte[i+1],man); gasit=1; while (gasit); for (i=0;i<n;i++) cout<<cuvinte[i]<<endl;

Dacă pot compara două cuvinte, atunci pot să le sortez. Am folosit sortarea prin interschimbare. Iată că, algoritmii, cu precădere cei fundamentali, pot fi folosiţi şi într-un context diferit de cel în care au fost prezentaţi.

3.3.8. Subşiruri

Funcţia strstr are forma generală:

char *strstr(const char *s1, const char *s2);

şi are rolul de a identifica dacă şirul s2 este subşir (caractere succesive) al şirului s1. Dacă acesta este identificat, funcţia returnează adresa de început în cadrul şirului s1, altfel returnează adresa nulă (0). Căutarea se face de la stânga la dreapta. În cazul în care s2 apare de mai multe ori în cadrul lui s1, se returnează adresa de început a primei apariţii.

Exemplu: fie char s1[]="xyzt", s2[]="yz", atunci strstr(s1,s2); returnează s1+1 (adresa caracterului y în s1).

Aplicaţia 3.9. În programul următor se citesc două şiruri de caractere şi se testează dacă al doilea este subşir al primului. În caz afirmativ, programul afişează indicele caracterului de început al subşirului.

#include <iostream.h> #include <string.h> main() char sir[1000],subsir[25], *t; cout<<"introduceti textul "; cin.get(sir,1000); cin.get(); cout<<"introduceti subsirul cautat "; cin.get(subsir,25); t=strstr(sir,subsir); if (t) cout<<"este subsir si are indicele "<<t-sir; else cout<<"nu este subsir";

Page 100: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

100 Capitolul 3 – Şiruri de caractere

Aplicaţia 3.10. Ştergerea tuturor apariţiilor unui subşir din cadrul unui şir. Imediat ce am identificat adresa de început a subşirului, restul şirului (fără subşir) este copiat pe poziţia de început a subşirului.

#include <iostream.h> #include <string.h> main() char sir[1000],subsir[25],*p; int lung_subsir; cout<<"introduceti textul "; cin.get(sir,1000); cin.get(); cout<<"introduceti subsirul "; cin.get(subsir,25); lung_subsir=strlen(subsir); p=strstr(sir,subsir); while (p) strcpy(p,p+lung_subsir); p=strstr(p,subsir); cout<<sir;

Aplicaţia 3.11. Înlocuirea tuturor apariţiilor unui subşir cu alt subşir. Vă las pe dvs. să descoperiţi modul în care programul următor realizează aceasta:

#include <iostream.h> #include <string.h> main() char sir[100], man[100], sterg[25], adaug[25], *p; int lung_sterg, lung_adaug; cout<<"introduceti textul "; cin.get(sir,100); cin.get(); cout<<"inlocuim subsirul "; cin.get(sterg,25); cin.get(); cout<<"cu subsirul "; cin.get(adaug,25); lung_sterg=strlen(sterg); lung_adaug=strlen(adaug); p=strstr(sir,sterg); while (p) man[0]=0;//subsir vid; strncat(man,sir,p-sir); strcat(man,adaug); strcat(man,p+lung_sterg); strcpy(sir,man); p=strstr(p+lung_adaug,sterg); cout<<sir;

Page 101: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 101

3.3.9. Alte funcţii utile în prelucrarea şirurilor

Funcţia strtok are forma generală:

char *strtok(char *s1, const char *s2);

Principiul de executare este următorul:

• şirul s1 este considerat ca fiind alcătuit din 0, 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 s1 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(s1,s2);, funcţia întoarce adresa primului caracter al primei entităţi (în exemplu "mama"). În plus, după prima entitate separatorul este înlocuit automat prin caracterul nul (cod 0);

• următoarele apeluri ale funcţiei sunt de forma strtok(NULL,s2);, iar funcţia întoarce 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 şirul rămas nu mai conţine entităţi, funcţia întoarce adresa nulă.

Aplicaţia 3.12. În programul următor se citeşte un şir de caractere. Entităţile se consideră a fi cuvinte - şiruri de caractere care nu sunt albe - separate prin blank-uri şi/sau virgule. Programul listează entităţile depistate, fiecare pe un rând.

#include <iostream.h> #include <string.h> main() char sir[1000],separator[]=" ,", *p; cin.get(sir,1000); p=strtok(sir,separator); while (p) cout<<p<<endl; p=strtok(NULL, separator);

Variabila p reţine adresa de început a entităţii. Dacă entitatea este găsită, p va reţine o valoare diferită de 0, caz în care entitatea este afişată şi se va căuta următoarea entitate.

Page 102: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

102 Capitolul 3 – Şiruri de caractere

Aplicaţia 3.13. Programul următor citeşte un şir de caractere şi tipăreşte şirul obţinut prin 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[]=" ,", *p; cin.get(sir,1000); p=strtok(sir,separator); while (p) cout<<p; p=strtok(NULL, separator);

Funcţia strcspn are forma generală:

size_t strcspn(const char *s1, const char *s2);

şi are rolul de a returna numărul de caractere al şirului s1 - caractere consecutive care încep obligatoriu cu primul caracter - care nu se găsesc în şirul s2.

Funcţia standard strspn are forma generală:

size_t strspn(char *s1, char *s2);

şi are rolul de a returna numărul de caractere al şirului s1 - caractere consecutive care încep obligatoriu cu primul caracter - care se găsesc în şirul s2.

Exemple

1. Fie char* s1="AB2def"; *s2="123";. Atunci: strcspn(s1,s2) returnează 2 (pentru că primele două caractere 'A' şi 'B' din s1 nu se găsesc în s2), iar strspn(s1,s2) returnează 0 (primul caracter din s1 nu se găseşte în s2).

2. Fie char* s1="AB2def"; *s2="16A32BF";. Atunci: strcspn(s1,s2) returnează 0 (pentru că primul caracter 'A' din s1 se găseşte în s2), iar strspn(s1,s2) returnează 3 (caracterele 'A', 'B', '2' din s1 se găsesc în s2).

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 cuvant[100], cifre[]="0123456789"; cout<<"Introduceti cuvantul "; cin>>cuvant; if (strspn(cuvant,cifre)==strlen(cuvant)) cout<<"numeric"; else cout<<"nenumeric";

Page 103: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 103

Aplicaţia 3.15. Se citeşte un şir de caractere care nu conţine caractere albe. Să se verifice dacă şirul e alcătuit exclusiv din caractere nenumerice.

Vom reţine un vector numit cifre. Componentele lui reţin toate caracterele numerice: 0-9. Cuvântul citit are toate caracterele nenumerice dacă toate caracterele vectorului cifre - numărul lor este 10 - nu se regăsesc printre caracterele vectorului cuvant.

#include <iostream.h> #include <string.h> main() char cuvant[100], cifre[]="0123456789"; cout<<"Introduceti cuvantul "; cin>>cuvant; if (strcspn(cifre,cuvant)==10) cout<<"corect "; else cout<<"incorect";

Funcţia strlwr are forma generală:

char *strlwr(char *s);

Ea converteşte toate literele mari ('A' ... 'Z') în litere mici ('a' ... 'z'). Restul caracterelor rămân neschimbate. Funcţia întoarce adresa s.

Funcţia strupr are forma generală:

char *strupr(char *s);

şi rolul de a converti toate literele mici ('a' ... 'z') în litere mari ('A ... Z'). Restul caracterelor rămân neschimbate. Funcţia întoarce adresa s. În programul următor 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<<" Introduceti cuvantul "; cin>>a; cout<<strupr(a);

Funcţia strpbrk are forma generală:

char *strpbrk(char *s1, char *s2);

şi acţionează în felul următor:

• Caută primul caracter al şirului s1 în s2. Dacă este găsit, returnează adresa sa din cadrul şirului s1 şi execuţia se termină, altfel trece la pasul următor.

• Caută al doilea caracter al şirului s1 în s2. Dacă este găsit, returnează adresa sa din cadrul şirului s1 şi execuţia se termină, altfel trece la pasul următor.

• . . .

• dacă nici un caracter al şirului s1 nu aparţine şirului s2, funcţia returnează adresa nulă.

Page 104: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

104 Capitolul 3 – Şiruri de caractere

Exemplu: fie char *s1="abcdefghijklmnopqrstuvwxyz",*s2 = "c12";. Atunci strpbrk(s1, s2) returnează adresa s1+2 (adresa caracterului c, pentru că acesta este primul caracter din s1 găsit în s2).

Aplicaţia 3.16. Se citesc două cuvinte. Se cere să se afişeze toate caracterele primului cuvânt care se regăsesc în al doilea.

#include <iostream.h> #include <string.h> main() char cuvant1[10], cuvant2[10],*p; cout<<"Introduceti primul cuvant "; cin>>cuvant1; cout<<"Introduceti al doilea cuvant "; cin>>cuvant2; p=strpbrk(cuvant1,cuvant2); while(p) cout<<p[0]<<endl; p++; p=strpbrk(p,cuvant2);

Caracterele primului cuvânt, începând de la primul, sunt căutate în al doilea cuvânt. 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.10. Conversia şirurilor în valori numerice şi invers

Următoarele funcţii au prototipul în "stdlib.h" şi folosesc pentru conversia valorilor numerice în şir şi invers.

Funcţia atof converteşte un şir către tipul double. Dacă conversia eşuează (se întâlneşte un caracter nenumeric) valoarea întoarsă este 0. 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 tipul long 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 atol converteşte un şir către tipul long:

long atol(const char *s);

Page 105: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 105

Aplicaţia 3.17. Se citeşte un text alcătuit din mai multe cuvinte. Se cere să se calculeze suma valorilor numerice întâlnite în text. Se presupune că valorile numerice sunt introduse corect.

Şirul se separă în entităţi. Dacă o entitate este alcătuită exclusiv din '+', '-', '.' şi cifre, atunci ea este convertită în double şi adunată la o variabilă s, iniţializată cu 0.

#include <iostream.h> #include <string.h> #include <stdlib.h> main() char sir[1000],separator[]=" ",cifre[]="0123456789.+-", *p; double s=0; cin.get(sir,1000); p=strtok(sir,separator); while (p) if (strspn(p,cifre)==strlen(p)) s+=atof(p); p=strtok(NULL, separator); cout<<"suma numerelor intalnite in sir este "<<s;

Funcţia ecvt are rolul de a converti o valoare de tip double către un şir. Caracterul nul este adăugat automat şirului obţinut. Forma generală este:

char* ecvt(double valoare, int poz, int* zec, int* semn);

unde: • valoare - valoarea de convertit; • poz - numărul de poziţii pe care trebuie să le ocupe şirul obţinut în urma

conversiei; • 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 0, în caz contrar.

Până în prezent nu am studiat modul în care putem obţine adresa unei variabile. Reţineţi că dacă a este o variabilă, &a este adresa ei. Funcţia utilizează acest mecanism de transmitere a parametrilor pentru ca să poată returna anumite rezultate pe care le găsim în variabilele respective. În cazul ei, variabila zec va reţine după apel numărul întregilor, iar variabila semn semnul rezultatului.

Funcţia nu inserează în şir nici punctul zecimal, nici semnul numărului. Şirul obţinut începe cu cifra cea mai semnificativă a numărului. Pentru ca şirul obţinut să fie corect, este necesar să fie prelucrat, în continuare, de către programator. Dacă numărul poziţiilor ocupate de şir (poz) este mai mare decât ocupă data, şirul este completat în stânga cu un număr de 0 necesar. În cazul în care numărul poziţiilor este mai mic decât cel ocupat de număr, rezultatul este rotunjit.

Exemple: ecvt(val,3,&zec,&semn);

Page 106: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

106 Capitolul 3 – Şiruri de caractere

− dacă val reţine 1234, atunci se returnează şirul "123", zec reţine 4 şi semn reţine 0.

− dacă val reţine 1239, atunci se returnează şirul "124", zec reţine 4 şi semn reţine 0.

− dacă val reţine 1, atunci se returnează şirul "100", zec reţine 1 şi semn reţine 0.

− 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 // asa cum este furnizat de functie strcpy(numar_sir,ecvt(numar,19,&zec, &semn)); cout<<"Sirul este "<<numar_sir<<" "<<zec<<" "<<semn<<endl; // prelucrez sirul pentru a tipari rezultatul asa cum // 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ă în variabila sir. Valoarea baza reţine baza de numeraţie către care să se facă conversia (de obicei baza este 10). În 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 ltoa are acelaşi efect ca itoa, 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:

Page 107: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 107

Funcţia long strtol(const char *s, char **endptr, int baza);

are rolul de a converti un şir către long. În afara şirului care trebuie convertit, funcţia primeşte ca parametru de intrare adresa unei variabile de tip char*. După apel, această variabilă va reţine poziţia primului caracter din şir care nu poate fi convertit. De exemplu, dacă şirul este "12m21", după apel, variabila a cărei adresă a fost transmisă ca parametru reţine adresa caracterului 'm'. Dar care este rostul existenţei acestei informaţii? Cu ajutorul ei se pot depista eventualele erori apărute atunci când se introduc datele. Priviţi şirul dat anterior ca exemplu: e clar că utilizatorul a dorit să introducă un număr, dar în locul unei cifre a tastat 'm'.

Aplicaţia 3.18. Programul următor testează dacă o valoare introdusă este numerică şi dacă este cuprinsă în intervalul [10,20]. Testul propriu-zis se face atunci când comparăm numărul caracterelor convertite cu lungimea şirului. Egalitatea are semnificaţia că întreg şirul este numeric. Variabila radix trebuie să conţină 8, 10 sau 16, adică baza în care este considerat numărul sub formă de şir.

#include <iostream.h> #include <stdlib.h> #include <string.h> main(void) char numar[20], *adresa; long v; cin>>numar; v=strtol(numar,&adresa,10); if(adresa-numar!=strlen(numar)) cout<<"Data contine caractere nenumerice"; else if (v<10 || v>20) cout<<"data numerica in afara limitei "; else cout<<v<<endl;

Funcţia double strtod(const char *s, char **endptr);

converteşte un şir către double.

Funcţia long double _strtold(const char *(s), char **endptr);

converteşte un şir către long double.

Funcţia unsigned long strtoul(const char *s,char **endptr,int baza);

converteşte un şir către unsigned long.

În 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ă valoare care poate fi memorată de tipul în care se face conversia, după cum valoarea este pozitivă sau negativă.

Page 108: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

108 Capitolul 3 – Şiruri de caractere

3.3.11. Citirea şi scrierea şirurilor de caractere 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).

Observaţii

Caracterul care marchează sfârşitul liniei '\n' nu este inserat în şir, în schimb este inserat caracterul nul (marchează sfârşitul şirului).

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 în care citirea se face până la întâlnirea ei sau până când au fost citite Nr_car-1 caractere.

Programul următor citeşte linie cu linie un fişier text ale cărui linii nu au mai mult de 500 de caractere şi afişează liniile pe monitor. Nu se cunoaşte lungimea fiecărei linii.

#include <fstream.h> main() fstream f("f.dat",ios::in); char Linie_citita[501]; while (f.getline(Linie_citita,501)) cout<<Linie_citita<<endl;

Observaţie foarte importantă! Prin f>>Linie_citita, citirea se efectuează 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!

Page 109: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 109

3.3.11.2. Operaţia de scriere

Scrierea se face prin comanda f<< sir;. Prin această instrucţiune se scrie întreg şirul, începând cu poziţia curentă a pointerului, inclusiv caracterele albe. Analizaţi exemplul următor:

#include <fstream.h> main() fstream f("f.dat",ios::out); f<<" Afara este"<<endl; f<<"primavara!"; f.close();

3.3.12. O modalitate de conversie de la şir la alt tip

Există şi alte posibilităţi de conversie de şiruri la alte tipuri de date. Un tip special, numit istrstream, permite chiar declararea stream-urilor (fluxurilor) de la şiruri către variabile. "Citirea" se efectuează cu ajutorul operatorului “>>” la fel ca din fişier.

Un şir X reţine "1 2 3 4 5". O funcţie specială (numită constructor) ataşează şirului X, un stream (flux), numit ins. Ea are doi parametri: şirul X şi lungimea lui: istrstream ins(X, strlen(X));.

"Citirea" se efectuează cu conversie către tipul respectiv. Programul va afişa numerele 1,2,3,4,5 câte unul pe linie!

#include <iostream.h> #include <strstrea.h> #include <string.h> main() char X[]="1 2 3 4 5"; istrstream ins(X, strlen(X)); int nr; while (ins>>nr) cout<<nr<<endl;

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 lungime 0, 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ă.

Şir de caractere Variabilă

Page 110: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

110 Capitolul 3 – Şiruri de caractere

Programul este prezentat în continuare: #include <iostream.h> #include <strstrea.h> #include <string.h> main() char X[]="1 mama tata 4 bunica"; char cuvant[20]; istrstream ins(X, strlen(X)); while (ins>>cuvant && strlen(cuvant)) cout<<cuvant<<endl;

Probleme propuse

1. Se citeşte de la tastatură un text. Cuvintele se consideră separate prin virgulă, spaţiu sau punct. Câte cuvinte are textul citit?

2. Se citeşte de la tastatură un text şi o succesiune de caractere. De câte ori întâlnim această succesiune în cadrul textului?

3. Se citeşte un text. Două cuvinte pot fi separate printr-unul sau mai multe spaţii. Se cere să se elimine spaţiile inutile.

4. Se citeşte un fişier text care conţine o singură propoziţie, pe o linie. Programul rearanjează literele în fiecare propoziţie, în ordine alfabetică, păstrând 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 PRICE OF BREAD IS $1.25 PER POUND. ABC DDEEE EF HIIINO OP $1.25 PPR RRSTU. THE LICENSE PLATE READ G76-ZA3. AAA CDEEEEE GHILL NPRS T76-TZ3.

Junior Division

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 Division

Page 111: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 111

6. Eliminare. "MSSSSPP" este versiunea micşorată a cuvântului "MISSISSIPPI", obţinută prin eliminarea tuturor literelor "I". O frază poate fi micşorată şi ea: "TRAVERSAREA CAII FERATE" are versiunea micşorată "TRVRSR C FRT", prin eliminarea literelor "A", "I", "E". Se cere ca programul dvs. să elimine literele dintr-o frază citită din fişierul text "input.txt" şi scrisă pe o singură linie. Literele care se elimină se citesc de la tastatură, iar ieşirea este în fişierul "output.txt".

Junior Division

7. Problemă de lucru în colectiv - numai pentru elevii care studiază limbajul Pascal. Şiruri generalizate.

Presupunem că nu sunteţi mulţumiţi de faptul că, în Pascal, şirurile cu care se poate lucra pot avea cel mult 255 de caractere.

De exemplu, dorim să lucrăm cu şiruri care au cel mult 3000 de caractere. Un astfel de şir va fi reţinut ca un vector de 3000 de caractere, iar sfârşitul unui şir va fi marcat prin memorarea valorii 0 (în binar).

Scrieţi subprogramele următoare:

subprogram care determină lungimea unui şir;

subprogram care permite concatenarea a două şiruri;

subprogram care identifică dacă un şir este sau nu subşir pentru altul;

subprogram care converteşte un şir către un întreg;

subprogram care converteşte un întreg către un şir;

subprogram care converteşte un şir către un număr real;

subprogram care converteşte un real către un şir;

subprogram care inserează un şir pe o anumită poziţie a altui şir;

subprogram care şterge un subşir al unui şir dat (se cunoaşte poziţia de început a subşirului);

subprogram care permite scrierea unui şir într-un fişier text;

subprogram care permite citirea unui şir dintr-un fişier text.

Page 112: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

112

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 mai multe subvariabile numite, uneori, câmpuri. Pentru exemplul considerat, o variabilă, s-o numim Pers, va conţine o subvariabilă numită Nume, care este 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 este neomogenă din punct de vedere al structurii, spre deosebire, de exemplu, de un vector care are toate componentele de acelaşi tip. Cum se declară şi cum se utilizează o astfel de variabilă vom învăţa în acest capitol.

În ceea ce priveşte răspunsul la a doua întrebare, o posibilitate de a reţine un tabel este dată de crearea unui fişier cu tip.

Crearea şi exploatarea fişierelor cu tip nu face parte din programa dvs., dar 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 prezent nu ne sunt de mare folos. Să analizăm un exemplu.

Presupunem că dorim să prelucrăm anumite date referitoare la mai mulţi elevi. Pentru fiecare elev, cunoaştem:

Page 113: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 113

1. Numele şi prenumele - se reţine în string[20]; 2. Nota la matematică – variabilă de tip real; 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 'F'.

Observaţi faptul că informaţiile referitoare la un elev sunt eterogene (de la numele de tip string, până vârsta de tip byte). În Pascal, există posibilitatea ca toate aceste informaţii să se regăsească într-un singur tip de înregistrare, numit 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_fiz: real; varsta: byte; sex: char; end;

var e: elev;

begin write('nume elev '); readln(e.nume); write('nota matematica '); readln(e.n_mat); write('nota fizica '); 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 ', e.varsta); writeln('sexul ', e.sex); end.

Observaţii

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.nume reprezintă câmpul nume al variabilei e.

Î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 şi de tip real, deci declaraţia este:

n_mat, n_fiz: real;

Page 114: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

114 Capitolul 4. Structuri de date neomogene

Iată cum arată în memorie o înregistrare de acest tip - ocupă 35 de octeţi:

sexvirstafiznmatnnume

octet1octetoctetioctetiocteti 16621..

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.

Dacă a şi b sunt două variabile de acelaşi tip RECORD se pot face fără probleme atribuiri de genul a:=b;.

4.2.2. Accesul simplificat la câmpuri

Aşa cum a fost prezentat, modul de acces la câmpurile unei variabile de tip RECORD este deosebit de greoi - întotdeauna punem numele variabilei în faţă. În realitate, accesul la câmpurile unei astfel de variabile se poate face mult mai uşor, prin utilizarea instrucţiunii WITH, cu forma generală:

with var1, var2, ..., varn do instrucţiune

şi are rolul ca, în cadrul instrucţiunii subordonate, adresarea să se facă simplificat, adică prin utilizarea exclusivă a numelui câmpului.

Reluăm programul anterior, dar de această dată am utilizat instrucţiunea with:

type elev = record nume: string[20]; n_mat, n_fiz: real; varsta: byte; sex: char; end;

var e: elev;

begin with e do begin write('nume elev '); readln(nume); write('nota matematica '); readln(n_mat); write('nota fizica '); readln(n_fiz); write('varsta '); readln(varsta); write ('sexul '); readln(sex); writeln('nume ',nume); writeln('nota matematica ',n_mat); writeln('nota fizica ',n_fiz); writeln('varsta ',varsta); writeln('sexul ',sex); end end.

Page 115: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 115

4.2.3. Înregistrări imbricate

În general, câmpurile care alcătuiesc un tip RECORD pot avea orice tip. O întrebare: dar RECORD? Răspunsul este afirmativ. Analizaţi programul următor:

program r3; type elev = record nume: string[20]; data_n: record zi, luna: byte; an: integer; end; n_mat, n_fiz: real; varsta: byte; sex: char; end; var e: elev;

begin with e 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.

Pentru un elev, se citesc numele şi data naşterii. Aceasta din urmă este de tipul RECORD (include ziua, luna şi anul naşterii). Pentru adresarea simplificată a câmpurilor care o alcătuiesc s-a folosit, din nou, instrucţiunea WITH. Adresarea s-ar fi putut face şi aşa: e.data_n.zi, e.data_n.luna, e.data_n.an. Evident, o astfel de adresare este greoaie şi nerecomandată…

Putem avea oricâte niveluri de imbricare (un tip RECORD include un alt tip RECORD, care include un altul, ş.a.m.d.).

4.2.4. Vectori de înregistrări

Aşa cum s-a arătat, elementele unui vector pot fi de orice tip, deci inclusiv de tip RECORD.

Adresarea câmpurilor se face prin numele vectorului, urmat de perechea de paranteze drepte între care este trecut indicele componentei, apoi selecţia propriu-zisă se face aşa cum am învăţat.

Dacă v este vectorul, iar înregistrarea este de tip elev (ca în programul următor), atunci numele se selectează prin v[i].nume, iar ziua naşterii prin v[i].nume.zi.

Page 116: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

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 := 1 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 înregistrare 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). În concluzie, în funcţie de tipul de studii, înregistrarea arată altfel.

Limbajul permite ca înregistrările să aibă o structură variabilă. Cum se realizează aceasta?

În primul rând, trebuie reţinut că partea variabilă este plasată în cadrul înregistrării după partea fixă. O parte variabilă se dezvoltă în cadrul înregistrării după valorile pe care le ia un câmp situat în cadrul părţii fixe.

Page 117: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 117

În programul care urmează, se exemplifică descrierea unui tip de înregistrare variabilă, selecţia părţii variabile făcându-se în funcţie de valorile pe care le ia câmpul studii. Pentru selectare, se foloseşte o clauză specială numită CASE. Câmpul după care se face selecţia apare descris în această clauză.

Câmpul selector trebuie să fie de tip ordinal cu un număr finit de elemente. În funcţie de valorile pe care le poate lua câmpul selector, se va dezvolta partea variabilă. În esenţă, se scriu pe rând valorile posibile ale câmpului selector. În dreptul fiecărei valori se trece partea pe care trebuie să o conţină înregistrarea în acest caz. Aceasta se încadrează între paranteze rotunde şi poate fi chiar vidă. Câmpurile prezente între paranteze se scriu separate prin ';'.

Analizaţi programul următor (care citeşte o singură înregistrare):

type persoana = record nume: string[30]; varsta: byte; case studii: char of 'f': ( ); 'g': (nr_cl: integer); 'l': (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 write('numar clase '); readln(p.nr_cl); end; 'l': begin write('anul terminarii liceului '); 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.

Observaţii

Pentru fiecare înregistrare de acest tip compilatorul rezervă numărul de octeţi necesari celei mai lungi variante.

Este preferabil ca citirea unei variabile de tip înregistrare cu variante să se facă prin utilizarea instrucţiunii CASE.

Page 118: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

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 5 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 elev i, î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 struct, 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, ...]>] ; ...

[lista de variabile] ;

Pentru exemplul dat, structura este:

struct elev char nume[20], prenume[20]; float nota_mate,nota_info; int varsta; ;

şi se numeşte elev.

Page 119: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 119

Există două posibilităţi de declarare a variabilelor care alcătuiesc structura.

1. Aşa cum rezultă din forma generală, scriind la sfârşit numele variabilelor:

struct elev char nume[20], prenume[20]; float nota_mate,nota_info; int varsta; inr1,inr2;

Aici, inr1 şi inr2 sunt două variabile de tipul elev.

2. Clasic, declarând variabilele aşa cum suntem obişnuiţi: elev inr1, inr2;

Definiţia structurii poate figura atât în cadrul funcţiei main() cât şi în faţa ei, după includerile de fişiere antet. În ce ne priveşte, vom prefera a doua variantă.

Se pune următoarea întrebare: fiind dată o variabilă de un tip struct, care este modalitatea de acces la câmpurile ei? Pentru aceasta, se foloseşte operatorul de selecţie directă, notat cu '.', operator cu prioritate maximă.

Fie inr o variabilă de tipul elev. Atunci:

• inr.nume - reprezintă şirul nume al variabilei inr; • inr.nume[0] – reprezintă primul caracter al şirului nume; • inr.nota_mate – reprezintă câmpul nota_mate al variabilei inr.

În programul următor, se citeşte şi se tipăreşte o variabilă de tipul elev:

#include <iostream.h> struct elev char nume[20], prenume[20]; float nota_mate,nota_info; int varsta; ;

main() elev inr; cout<<"Nume "; cin>>inr.nume; cout<<"Prenume "; cin>>inr.prenume; cout<<"Nota matematica "; cin>>inr.nota_mate; cout<<"Nota informatica "; cin>>inr.nota_info; cout<<"Varsta "; cin>>inr.varsta; cout<<"Am citit:"<< endl <<inr.nume<<" "<<inr.prenume <<endl <<inr.nota_mate<<endl <<inr.nota_info <<endl <<inr.varsta;

Page 120: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

120 Capitolul 4. Structuri de date neomogene

Între două variabile de acelaşi tip struct se poate folosi atribuirea. Astfel, dacă inr1, inr2 sunt două variabile de tip elev, prin atribuirea inr1=inr2, variabila inr1 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 în 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ă, în general, elementele structurii se accesează prin numele variabilei de tipul structurii respective.

Fie inr o înregistrare de tipul elev. În aceste condiţii, accesarea elementelor situate în interiorul substructurii se face ca mai jos:

• inr.situatie.clasa - se accesează câmpul clasa al substructurii;

• inr.situatie.note[0] - se accesează prima notă a vectorului inclus în substructură.

Exemplul următor prezintă o altă posibilitate de declarare a structurilor: struct elev1 char nume[20], prenume[20]; struct int clasa; float note[20]; situatie_1, situatie_2; int varsta; ;

Tipul structurat elev1, subordonează, pe lângă alte tipuri, două structuri situatie_1 ş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 în alta, ca mai sus.

Page 121: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 121

4.3.3. Înregistrări cu structură variabilă

Pentru început, vom studia un tip aparte de dată structurată, numit union. Analizaţi programul următor:

#include <iostream.h> union test int a; char b[10]; double c; ;

main() test var;int i; cin>>var.c; cout<<var.c<<endl; cin>>var.b; cout<<var.b;

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 8 octeţi. În realitate, pentru toate variabilele s-au reţinut 10 octeţi - adică octeţii necesari pentru a memora câmpul cel mai 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.

Iată forma generală a unei uniuni:

union [<numele uniunii>] <tip> <nume variabila> ; ... [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 întreba, dar la ce folosesc "uniunile"? Sunt situaţii în care înregistrările nu au format fix, ci variabil. Ca să fiu clar, voi da un exemplu.

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). În concluzie, în funcţie de tipul de studii, înregistrarea arată altfel.

Limbajul permite ca înregistrările să aibă o structură variabilă. Cum se realizează aceasta?

Page 122: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

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 'g', dacă are liceul, reţine 'l', iar dacă are studii superioare, 's'.

Apoi urmează partea variabilă care este o uniune. Aceasta conţine o variabilă nr_clase - pentru cazul în care studii reţine 'g', o structură pentru cazul în care studii, reţine 'l', ş.a.m.d. În rest, citirea şi afişarea înregistrării se face, de fiecare dată, sub switch, în funcţie de datele citite sau conţinute. În concluzie, înregistrarea are structura variabilă, 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; union 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 'l': cout<<"anul terminarii liceului "; cin>>pers.std.liceu.an_t; cout<<"orasul "; cin>>pers.std.liceu.oras; break; case 's': cout<<"numele facultatii ";cin.get(); cin.get(pers.std.facultate.nume_f,30); cout<<"nr ani de studiu "; cin>>pers.std.facultate.nr_ani;

Page 123: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-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 'l': 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ă:

• nume_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 5. Î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.

Page 124: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

124

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. În 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. Mai 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ândul lor, tipurile de date pot fi:

simple - descriu date care aparţin unor mulţimi care nu sunt

rezultate ca produs cartezian al altor mulţ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:

Varianta Pascal Varianta C++

type rational=record p,q:integer; end

struct rational int p,q; ;

Prin tipul de mai sus se descrie structura unei variabile capabilă să reţină

numere raţionale. Fie A1 mulţimea valorilor care pot fi memorate prin utilizarea tipului întreg. Fie A2=A1-0. Atunci, o variabilă de tip rational poate memora valori care aparţin mulţimii A1×A2. Evident, este sarcina programatorului ca valoarea reţinută de variabila corespunzătoare lui q să fie diferită de 0. 1 În unele lucrări veţi întâlni definiţii ale tipului de date care exclud regula de codificare. Aici se porneşte de la ideea că nu întotdeauna este necesar, pentru a lucra cu tipul respectiv, să cunoaştem regula de codificare.

Page 125: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 125

2. Mai jos, este prezentat un alt exemplu:

Varianta Pascal Varianta C++

type vector=array [1..100] of real; var a:vector;

double a[100];

Fie B mulţ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:

orinde

BBB ××× ... .

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 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.

În cele mai multe cazuri, "ansamblul de date" care formează structura este alcătuit dintr-o mulţime cu un număr variabil de noduri.

Relaţiile existente între noduri şi operaţiile care pot fi efectuate cu ele vor fi prezentate prin exemple.

De reţinut! 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).

În 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.

În 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.

Page 126: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

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≥0 noduri, X1, X2, ..., Xn aflate într-o relaţie de ordine. Astfel, X1 este primul nod al listei, X2 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ă în listă;

• ştergerea unui nod, indiferent de poziţia pe care o ocupă în 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 îi urmează în listă) şi, cu excepţia primului nod, are un singur predecesor (care se află imediat înaintea lui în listă).

Dacă vorbim la modul general de o structură de date, nu ne interesează,

pentru moment, modul în care aceasta va fi implementată (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 infi: 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 în ordinea alfabetică a numelor elevilor. În clasa respectivă pot fi transferaţi elevi din alte clase, caz în care se adaugă noduri. Din clasă, unii elevi pot pleca în alte clase, caz în care se şterg noduri. b) Se doreşte să se reţină un şir de numere naturale, în ordinea în 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 genealogic al unei persoane.

inf 1

nod1

inf 2

nod2

inf n

nodn

Page 127: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 127

5.2.2. Liste alocate secvenţial

Din acest moment ne punem problema să vedem modul în care se poate implementa o listă liniară.

O primă formă de alocare este cea secvenţială. În cazul acestei alocări, nodurile listei ocupă poziţii succesive în memorie. Acest tip de alocare se întâlneşte des, de câte 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 în 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[k] (presupunând că vectorul care reţine nodul se numeşte V). b) Ştergerea unui nod, indiferent de poziţia pe care o ocupă în listă

Fie lista alocată secvenţial: 7 3 1 2 8 9 5 8 3 2 6

Eliminăm al doilea nod - conţinut 3 (ştergem conţinutul nodului 2):

7 1 2 8 9 5 8 3 2 6

Pentru a păstra structura şi pentru a nu pierde informaţii, este obligatoriu ca informaţiile reţinute de nodurile care urmează nodului şters să fie deplasate către stânga: 7 1 2 8 9 5 8 3 2 6

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. În multe aplicaţii, această modificare nu prezintă importanţă.

c) Adăugarea unui nod, indiferent de poziţia pe care o ocupă în listă

Fie lista alocată secvenţial: 7 3 1 2 8 9 5 8 3 2 6

Page 128: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

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 informaţiile asociate nodurilor către dreapta: 7 3 1 2 8 9 5 8 3 2 6

Acum se completează valoarea reţinută de nodul 2: 7 5 3 1 2 8 9 5 8 3 2 6

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. În 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ănţuită şi alocare dublu înlănţuită. În acest paragraf prezentăm principiile alocării înlănţuite, urmând ca în paragraful următor să arătăm modul în care implementăm listele alocate înlănţuit. 1. O listă liniară simplu înlănţuită este o structură de forma:

Semnificaţia notaţiilor folosite este următoarea:

adr1, adr2, adr3, ..., adrn reprezintă adresele celor n înregistrări; in1, in2, ..., 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 în listă.

După cum observăm, fiecare nod, cu excepţia ultimului, reţine adresa

nodului următor. 2. Alocarea dublu înlănţuită. Alocarea simplu înlănţuită permite parcurgerea listei într-un singur sens (de la stânga la dreapta). În cazul în care se doreşte ca lista să poată fi parcursă în ambele sensuri, se utilizează alocarea dublu înlănţuită. Aici,

adrn in1 adr2 adr3 nil in2 inn

adr1 adr2

Page 129: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 129

fiecare nod reţine adresele predecesorului şi succesorului său, aşa cum se vede în figura următoare: 5.2.4. Implementarea alocării înlănţuite prin utilizarea vectorilor

Aşa cum am învăţat, lista liniară este alcătuită din mai multe noduri între care există o relaţie de ordine. În cazul alocării înlănţuite, informaţia memorată de fiecare nod va cuprinde şi un câmp de adresă -în cazul alocării simplu înlănţuită- sau două câmpuri de adresă -în cazul alocării dublu înlănţuită. În acest paragraf vom studia implementarea alocării simplu înlănţuită. Iată cum se descrie informaţia dintr-un nod, în cazul listelor alocate simplu

înlănţuit, atunci când acesta reţine un număr întreg.

Varianta Pascal Varianta C++

type Adresa=Integer; nod=record info:integer; adr_urm:Adresa; end;

typedef int adresa; struct nod int info; adresa adr_urm; ;

Pentru memorarea listei folosim un vector care are componentele de tip Nod,

descris mai jos:

Varianta Pascal Varianta C++

Lista=array[1..1000] of Nod; var L:lista;

nod L[1000];

Din descriere rezultă că lista poate avea cel mult 1000 de noduri. Acesta

este spaţiul disponibil.

Priviţi exemplul de mai jos:

in1 adr2 adr3 nil in2 inn

adr1 adr2 adrn

nil adr1 adrn-1

info adresa nodului următor

7 3

1

3 4

4 0 5 4 1 5

2

5 6

L

Page 130: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

130 Capitolul 5. Structuri de date

Dacă facem abstracţie de implementare, lista este:

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ărul unui nod (acesta este în cadrul listei) şi indicele vectorului unde este memorat. În 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ă.

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 0, 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.

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.).

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.

1 5

4

5 4

3

7 3

1

4 0

5

1 0 1 1 1 0

1 2 3 4 5 6

ocupat

3

1

3 4

4 5 4 1 5

2

5 6

L

Page 131: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 131

Prin testarea vectorului ocupat, se observă că primul element liber al vectorului L este cel de indice 2. a) Marcăm nodul ca ocupat (ocupat[2] va reţine 1). b) Memorăm informaţia: 9. 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): d) Al treilea nod va reţine ca adresă următoare, adresa nodului nou introdus, pentru că acesta s-a introdus după el:

După această modificare, lista va fi:

1 0 1 1 1 0

1 2 3 4 5 6

ocupat

7 3

1

3 4

4 0 5 4 1 5

2

5 6

L

1 1 1 1 1 0

1 2 3 4 5 6

ocupat

7 3

1

9

3 4

4 0 5 4 1 5

2

5 6

L

1 1 1 1 1 0

1 2 3 4 5 6

ocupat

7 3

1

9

3 4

4 0 5 5 4 1 5

2

5 6

L

1 1 1 1 1 0

1 2 3 4 5 6

ocupat

7 3

1

9

3 4

4 0 5 5 4 1 2

2

5 6

L

1 5

4

5 4

3

7 3

1

4 0

5

9 5

5

Page 132: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

132 Capitolul 5. Structuri de date

Problema 4. Ştergerea unui nod. Să presupunem că în lista de mai jos dorim să ştergem al doilea nod.

Al doilea nod se găseşte în elementul de indice 3. Prin urmare, ocupat[3]

va reţine 0. Î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.

Observaţi faptul că, deşi nu am şters informaţia asociată nodului, acesta 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.

1 0 1 1 1 0

1 2 3 4 5 6

ocupat

7 3

1

3 4

4 0 5 4 1 5

2

5 6

L

1 0 0 1 1 0

1 2 3 4 5 6

ocupat

7 4

1

3 4

4 0 5 4 1 5

2

5 6

L

1 5

4

7 4

1

4 0

5

Page 133: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-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

în stivă.

Stiva funcţionează pe principiul LIFO (Last In First Out) - "ultimul intrat 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[i] un vector. ST[1],

ST[2], ..., ST[n] pot reţine numai litere sau numai cifre. O variabilă k indică în permanenţă vârful stivei, adică ultimul element introdus.

Exemplificăm, în continuare, modul de lucru cu stiva:

Observaţii

În 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.

Scoatem din stivă pe B (A nu poate fi scos deocamdată); k=1.

Introducem în stivă litera B, deci k va lua valoarea 2.

Scoatem din stivă pe A; stiva rămâne vidă k=0.

În stiva iniţial vidă se introduce litera A, vârful stivei va fi la nivelul 1 (k=1). A

A

B

A

Page 134: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

134 Capitolul 5. Structuri de date

Pe un anumit nivel se reţine, de regulă, o singură informaţie (literă sau cifră), însă 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, în comparaţie cu alocarea dinamică înlănţuită 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.

În 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 principiul stivei.

Exemple

1. Funcţia Manna-Pnueli. Se citeşte x∈Z. Se cere programul pentru calculul funcţiei:

<≥

+−

=12x12x

2)),F(F(x1,x

F(x)

Vom începe prin a studia modul de calcul al funcţiei pentru x=15 şi x=8: f(15)=14;

f(8)=f(f(10))=f(f(f(12)))=f(f(11))=f(f(f(13)))=f(f(12))=f(11)=f(f(13))=f(12)=11.

Algoritmul va folosi o stivă ST şi o variabilă numită k, ce indică în permanenţă vârful stivei.

Algoritmul se bazează pe următoarele considerente:

la o nouă autoapelare a funcţiei f, se urcă în stivă (k se incrementează cu 1) şi se pune noua valoare;

în situaţia în care, pentru valoarea aflată pe nivelul k, se poate calcula funcţia, se coboară în stivă, punându-se pe acest nivel noua valoare;

algoritmul se încheie când se ajunge în stivă la nivelul 0.

Page 135: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 135

Pentru exemplul dat, prezentăm schematic funcţionarea sa:

Programul este prezentat în continuare:

Varianta Pascal Varianta C++

var st:array [1..100] of integer; n,k:integer; begin write('n='); readln(n); k:=1; st[1]:=n; while k>0 do if st[k]<12 then begin k:=k+1; st[k]:=st[k-1]+2 end else begin k:=k-1; if k>0 then st[k]:=st[k+1]-1 end; writeln('f=',st[1]-1) end.

#include <iostream.h> int st[100],n,k; main() cout<<"n="; cin>>n; k=1; st[1]=n; while (k>0) if (st[k]<12) k++; st[k]=st[k-1]+2; else k--; if (k>0) st[k]=st[k+1]-1; cout<<"n="<<st[1]-1;

Se poate demonstra uşor că pentru valori mai mici decât 12, funcţia ia valoarea 11. Observaţia simplifică mult programul, dar exemplul a fost dat în alt scop.

2. Funcţia lui Ackermann. Se dă funcţia de mai jos, definită pe produsul cartezian N×N. Se citesc m şi n. Să se calculeze Ack(m,n).

altfel0n0m

1)),nAck(m,1,Ack(m1,1),Ack(m

1,nn)Ack(m, =

=

−−−

+=

8

12

11

11

13

12

8

8

10

8

10

12

8

11

8

11

13

f=11

Page 136: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

136 Capitolul 5. Structuri de date

Pentru a elabora algoritmul, studiem un exemplu numeric: ack(2,1)=ack(1,ack(2,0))=ack(1,ack(1,1))=ack(1,ack(0,ack(1,0)))= = ack(1,ack(0,ack(0,1)))=ack(1,ack(0,2))=ack(1,3)=ack(0,ack(1,2)) = =ack(0,ack(0,ack(1,1)))=ack(0,ack(0,ack(0,ack(1,0))))= =ack(0,ack(0,ack(0,ack(0,1))))=ack(0,ack(0,ack(0,2)))= ack(0,,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 nivelul 1. Pe nivelul k al stivei se reţin valorile curente m şi n. În funcţie de valorile acestora se procedează astfel: pentru m şi n diferite de 0, este necesar un nou calcul de funcţie, caz în care

se urcă în stivă şi pe noul nivel se pun argumentele m şi n-1; pentru cazul n=0, se rămâne pe acelaşi nivel în stivă, punând în locul lui m

valoarea m-1, iar în locul lui n, valoarea 1; în situaţia în care m=0, funcţia se poate calcula; se coboară în stivă şi se

înlocuieşte valoarea lui m cu m-1, valoarea lui n cu valoarea calculată anterior. În continuare, prezentăm grafic modul de funcţionare a algoritmului pentru

exemplul ack(2,1):

2 1

2 1

2 0

2 1

1 1

1 0

2 1

1 1

0 1

2 1

1 1

2 1

0 2

1 3

1 2

1 3

1 1

1 3

1 2

1 0

1 1

1 3

1 2

0 1

1 1

1 3

1 2

0 2

1 3

1 2

1 3

0 3

0 4

ack(2,1)=5.

Page 137: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 137

Varianta Pascal Varianta C++

type stiva=array [1..10000,1..2] of integer;

var st:stiva; m,n,k:integer;

begin write('m='); readln(m); write('n='); readln(n); k:=1; st[k,1]:=m; st[k,2]:=n; while k>0 do if (st[k,1]<>0) and (st[k,2]<>0) then begin k:=k+1; st[k,1]:=st[k-1,1]; st[k,2]:=st[k-1,2]-1 end else if st[k,2]=0 then begin st[k,1]:=st[k,1]-1; st[k,2]:=1 end else begin k:=k-1; if k>0 then begin st[k,1]:=st[k,1]-1; st[k,2]:=st[k+1,2]+1 end end; writeln('ac(',m,',',n,')=', st[1,2]+1) end.

int st[10000][2];

main() int m,n,k; cout<<"m="; cin>>m; cout<<"n="; cin>>n; k=1; st[k][0]=m; st[k][1]=n; while (k>0) if (st[k][0] && st[k][1]) k++; st[k][0]=st[k-1][0]; st[k][1]=st[k-1][1]-1; else if (!st[k][1]) st[k][0]=st[k][0]-1; st[k][1]=1; else k--; if (k>0) st[k][0]=st[k][0]-1; t[k][1]=st[k+1][1]+1; cout<<"ac("<<m<<','<< n<<")="<<st[1][1]+1;

Funcţia lui Ackermann ia valori extrem de mari pentru valori mici ale lui m şi n. De exemplu, nu veţi reuşi să calculaţi Ack(4,4). Încercaţi...

Page 138: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

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 FIFO (First In First Out) -

"primul intrat primul ieşit".

Este cu totul nerecomandabilă alocarea secvenţială a cozii, deoarece în 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 întregi. Introducem în coadă, pe rând, numerele 1, 2, 3, 4.

1 2 3 4

Dacă scoatem din coadă pe 1 şi introducem în coadă pe 5, coada va arăta

în felul următor:

2 3 4 5 Scoatem din coadă pe 2 şi introducem pe 6:

3 4 5 6

Se observă acest fenomen de "migraţie".

Probleme propuse

1. Care dintre structurile de mai jos nu este liniară?

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 subprogram care inserează în listă, pe poziţia p, un număr natural citit de la tastatură.

a) b) c) d)

Page 139: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 139

Exemplu: k=3; V=(1,2,3); p=2. Numărul citit este 5. După rulare trebuie să avem:

k=4; V=(1,5,2,3). 3. 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 subprogram care şterge din listă nodul aflat pe poziţia p, 1≤p≤k. Exemplu: k=3; V=(1,2,3); p=2. După rulare trebuie să avem: k=2; V=(1,3). 4. Pentru problema anterioară, care dintre afirmaţiile de mai jos este falsă? a) Pentru a şterge un nod aflat pe poziţia p sunt necesare k-p deplasări spre stânga ale conţinuturilor celorlalte noduri. b) Subprogramul va avea parametrul k transmis prin valoare. c) Subprogramul va avea parametrul k transmis prin referinţă. d) Dacă k=p, nu se efectuează deplasări către stânga. 5. Lucrare în colectiv. Implementarea listelor alocate simplu înlănţuit. Scrieţi un set de subprograme care creează şi gestionează o listă liniară simplu înlănţuită, alocată secvenţial (prin utilizarea unui vector). 6. Lucrare în colectiv. Implementarea listelor alocate simplu înlănţuit. Scrieţi un set de subprograme care creează şi gestionează o listă liniară simplu înlănţuită, alocată înlănţuit. 7. Sortaţi n numere naturale utilizând algoritmul de sortare prin inserţie. Programul va utiliza o listă liniară alocată înlănţuit. Care sunt avantajele utilizării listei alocate înlănţuit în cazul algoritmului de sortare prin inserţie? 8. Creaţi o listă liniară cu n noduri alocată secvenţial. Nodul 1 va conţine numărul 1, nodul 2 numărul 2, ş.a.m.d. Se generează aleator un număr natural k, mai mare ca 1 şi mai mic decât n. Nodurile de la k la n vor fi, în această ordine, primele în listă, urmate de nodurile de la 1 la k-1, în această ordine. Afişaţi lista după p astfel de inversări. Observaţie: aceasta este o modalitate de a genera aleator o permutare a primelor n numere naturale. 9. În cazul în care, pentru o listă liniară alocată înlănţuit, câmpul de adresă al ultimului nod reţine adresa primului nod, se obţine o listă circulară:

Creaţi o listă circulară în care fiecare nod reţine un număr natural. De

asemenea, scrieţi subprograme de inserare şi ştergere a unui nod al listei create.

Page 140: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

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. Persoana la care s-a oprit numărătoarea este eliminată 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-l determină pe L; citeşte M, N, L şi-l determină pe K.

11. Urmăriţi secvenţa următoare, care se referă la o stivă, unde Push a este operaţia 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, în 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 în ordine inversă.

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, în care primul vagon este cel aflat la ieşirea de pe linia B. Care 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 şirul vagoanelor de pe lina B. Dacă acest şir nu se poate obţine, în momentul în 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)

Figura 5.1.

1 2 3 4

A B

C

Page 141: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

141

Capitolul 6

Introducere în recursivitate

6.1. Prezentare generală

Recursivitatea este una din noţiunile fundamentale ale informaticii. Utilizarea frecventă a recursivităţii s-a făcut după anii 80. Multe din limbajele de programare evoluate şi mult utilizate - Fortran, Cobol - nu permiteau scrierea programelor recursive.

Definiţia 6.1. Recursivitatea este un mecanism general de elaborare a programelor. Ea constă în posibilitatea ca un subprogram să se autoapeleze.

Recursivitatea a apărut din necesităţi practice date de transcrierea directă a

formulelor matematice recursive. În timp, acest mecanism a fost extins, fiind utilizat în elaborarea multor algoritmi.

6.2. Modul în care se realizează autoapelul

În acest paragraf vom învăţa modul în care subprogramele se autoapelează. Mecanismul 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 continuare este recursivă şi afişează, pe rânduri separate, numerele 7, 6, ..., 1:

Page 142: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

142 Capitolul 6. Introducere în recursivitate

procedure exemplu(n:integer); begin if n<>0 then begin writeln(n); exemplu(n-1); end end;

begin exemplu(7); 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(n:integer):integer; begin suma:=0; if n<>0 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.

Funcţia de mai jos este recursivă. Ea afişează, pe rânduri separate, numerele 7, 6, ..., 1:

#include <iostream.h> void exemplu(int n) if (n!=0) cout<<n<<endl; exemplu (n-1); main() exemplu(7);

Page 143: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 143

În cazul funcţiilor care nu sunt de tipul void, autoapelul se realizează prin instrucţiunea return. Ea este de forma return expresie, dar în expresia respectivă trebuie să intre şi funcţia care se autoapelează.

Funcţia următoare calculează suma 1+2+...+7:

#include <iostream.h> int suma (int n) if (n!=0) return n+suma(n-1);

main() cout<<suma(7);

6.3. Mecanismul recursivităţii

Problemă. Să se calculeze recursiv n!. A) Pentru a scrie o funcţie recursivă care efectuează acelaşi calcul, vom porni de la o definiţie recursivă a lui n!. Aceasta este:

Nnaltfel

0n1),fact(nn

1,fact(n)n! ∈

=

−⋅== cu

De exemplu, pentru a calcula 3!, procedăm astfel:

3!=fact(3)=3×fact(2)=3×2×fact(1)=3×2×1×fact(0)=3×2×1×1=6.

Funcţia recursivă fact nu face altceva decât să transcrie definiţia recursivă prezentată anterior:

Varianta Pascal Varianta C++

var n:integer;

function fact(n:integer): integer; begin if n=0 then fact:=1 else fact:=n*fact(n-1) end;

begin write('n='); readln(n); writeln(fact(n)) end.

#include <iostream.h> int fact(int n) if (!n) return 1; else return n*fact(n-1);

main() int n; cout<<"n="; cin>>n; cout<<fact(n);

Page 144: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

144 Capitolul 6. Introducere în recursivitate

Care este mecanismul prin care subprogramele se pot autoapela? Să ne 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.

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.

3 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. În stivă se creează un nou nivel, care reţine n cu valoarea 2. 3 n

2 n

3

n

Funcţia se autoapelează. Parametrul n ia valoarea 1. În stivă se creează un nou nivel, care reţine n cu valoarea 1.

n

1 n

2

Page 145: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 145

Aici se afişează 6, adică s-a calculat 3!.

B) Mai puţin eficient, n! se poate calcula şi printr-o funcţie ca în exemplul următor:

Varianta Pascal Varianta C++

var val,n,p:integer;

procedure fact(val,n:integer; var prod:integer); begin if val<=n then begin prod:=prod*val; fact(val+1,n,prod) end; end;

begin write ('n='); readln(n); p:=1; fact(1,n,p); writeln(p); end.

#include <iostream.h>

void fact(int val,int n,int& prod) if (val<=n) prod*=val; fact(val+1,n,prod);

main() int val,n,p=1; cout<<"n="; cin>>n; fact(1,n,p); cout<<p;

Funcţia se autoapelează. Parametrul n ia valoarea 0. În stivă se creează un nou nivel, care reţine n cu valoarea 0. Funcţia ia valoarea 1, autoapelul nu mai are loc. Se revine pe nivelul 3.

3

n

n

2

1 n

0 n fact=1

Pe nivelul 3 se efectuează calculul 1*1=1. Se revine apoi pe nivelul 2. n

3 n

2

1 n fact=1

Pe nivelul 2 se efectuează calculul 2*1=2. Se revine apoi pe nivelul 1.

n

3 n

2 fact=2

Pe nivelul 1 se efectuează calculul 3*2=6. Se revine apoi în main(). 3 n

fact=6

Page 146: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

146 Capitolul 6. Introducere în recursivitate

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ţă. Iată conţinutul variabilelor după primul apel:

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ă:

Pentru că val este 2 - mai mic ca 3 - se efectuează prod=prod*val, după care funcţia se autoapelează.

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 nivelul 2, apoi 1, apoi în programul principal.

Observaţii După cum rezultă din aceste exemple, subprogramul lucrează 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 transmise prin referinţă. După cum ştim variabilele globale pot fi accesate din orice subprogram. În 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.

1 3 Referinţă către p

2

p val n prod

2 3 Referinţă către p

3 3 Referinţă către p

1 3 Referinţă către p

1

p val n prod

2 3 Referinţă către p

1 3 Referinţă către p 1

p val n prod

Page 147: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-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 în comparaţie cu iterativitatea.

6.4. Cum gândim un algoritm recursiv ?

Pentru a ne familiariza cu raţionamentul recursiv, vom porni de la câteva exemple intuitive. 1. Wirth. 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 fumează!”.

3. Constatare. Ştiţi povestea cu săracul care a prins un peştişor de aur. Acesta îi spune omului că dacă îi dă drumul îi î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 îndeplinească 3 dorinţe...

Lăsând gluma la o parte, constatăm că, în general, o gândire recursivă

exprimă concentrat o anumită stare, care se repetă la infinit. Această gândire se aplică în elaborarea algoritmilor recursivi, dar cu o modificare esenţială: adăugarea condiţiei de terminare. În absenţa acestei condiţii, nu poate fi vorba de algoritm, întrucât algoritmul trebuie să fie finit. ⇒ În elaborarea algoritmilor recursivi se aplică raţionamentul: ce se întâmplă la

un nivel, se întâmplă la orice nivel. ⇒ Subprogramul care se autoapelează trebuie să conţină instrucţiunile

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 subprogramului).

Observaţii

Un algoritm recursiv se elaborează utilizând acest tip de gândire, nu o

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 problemă.

Nu întotdeauna alegerea unui algoritm recursiv reprezintă un avantaj.

Numeroasele exemple care urmează vă vor lămuri asupra celor afirmate.

Page 148: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

148 Capitolul 6. Introducere în recursivitate

6.5. Aplicaţii recursive

6.5.1. Aplicaţii la care se transcrie o formulă recursivă

Aplicaţia 6.1. Se citeşte x∈Z. Se cere programul pentru calculul funcţiei:

<≥

+−

=12x12x

2)),F(F(x1,x

F(x)

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.

Varianta Pascal Varianta C++

var x:integer;

function manna (x:integer):integer; begin if x>=12 then manna:=x-1 else manna:=manna(manna(x+2)) end;

begin write(‘x=‘); readln(x); writeln(manna(x)) end.

#include <iostream.h> int x;

int manna (int x) if (x>=12) return x-1;

else return manna(manna(x+2));

main() cout<<"x="; cin>>x; cout<<manna(x);

În comparaţie cu abordarea iterativă, abordarea recursivă prezintă avantajul scrierii concentrate.

Aplicaţia 6.2. Se dă funcţia de mai jos, definită pe N×N. Se citesc numerele m şi n. Să se calculeze Ack(m,n).

altfel0n0m

1)),nAck(m,1,Ack(m1,1),Ack(m

1,nn)Ack(m, =

=

−−−

+=

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.

Page 149: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 149

Varianta Pascal Varianta C++

var m,n:integer;

function ack(m,n:integer):integer; begin if m=0 then ack:=n+1 else if n=0 then ack:=ack(m-1,1) else ack:=ack(m-1,ack(m,n-1)) end;

begin write(‘m=‘); readln(m); write(‘n=‘); readln(n); writeln(ack(m,n)) end.

#include <iostream.h> int m,n;

int Ack(int m,int n) if (!m) return n+1; else if (!n) return Ack(m-1,1); else return Ack(m-1,Ack(m,n-1));

main() cout<<"m="; cin>>m; cout<<"n="; cin>>n; cout<<Ack(m,n);

În comparaţie cu abordarea iterativă, abordarea recursivă prezintă avantajul scrierii concentrate şi acela al scutirii programatorului de un efort suplimentar în elaborarea algoritmului.

Aplicaţia 6.3. Şirul lui Fibonacci. Se consideră şirul definit astfel:

UU U

nnaltfel

n

n n

=+

==

− −

01

01

1 2

,,

Se citeşte n, număr natural. Să se calculeze Un.

Rezolvare. Funcţia fib transcrie definiţia recursivă:

Varianta Pascal Varianta C++

var n:integer;

function fib (n:integer):integer; begin if n=0 then fib:=0 else if n=1 then fib:=1 else fib:=fib(n-1)+fib(n-2) end;

begin write(‘N=‘); readln(n); writeln(fib(n)) end.

#include <iostream.h> int n;

int fib (int n) if (!n) return 0; else if (n==1) return 1; else return fib(n-1)+fib(n-2);

main() cout<<"n="; cin>>n; cout<<fib(n);

Page 150: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

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=100). Se calculează u100 ca sumă între u99 şi u98. Pentru calculul lui u99 se calculează u98 şi u97. După ce calculăm u99, reluăm calculele pentru u98.

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.

Varianta Pascal Varianta C++

var n,f0,f1,f2,i:integer; begin write('n='); readln(n); f0:=0; f1:=1; if n=0 then writeln(f0) else if n=1 then writeln(f1) else begin for i:=2 to n do begin f2:=f0+f1; f0:=f1; f1:=f2 end; writeln(f2) end end.

#include <iostream.h>

main() int n,f0=0,f1=1,f2; cout<<"n="; cin>>n; if (!n) cout<<f0; else if (n==1) cout<<f1; else for (int i=2;i<=n;i++) f2=f0+f1; f0=f1; f1=f2; cout<<f2;

Aplicaţia 6.4. Se dau două numere naturale a şi b. Se cere să se calculeze cel mai mare divizor comun al lor. Rezolvare. Utilizăm o definiţie recursivă a celui mai mare divizor comun pentru două numere naturale a şi b:

bababa

a),bcmmdc(a,b),b,cmmdc(a

a,b)cmmdc(a,

<>=

−−=

Page 151: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 151

Această definiţie este transcrisă în funcţia recursivă cmmdc:

Varianta Pascal Varianta C++

var a,b:integer;

function cmmdc (a,b:integer):integer; begin if a=b then cmmdc:=a else if a>b then cmmdc:=cmmdc(a-b,b) else cmmdc:=cmmdc(a,b-a) end; begin write(‘a=‘); readln(a); write(‘b=‘); readln(b); writeln(cmmdc(a,b)) end.

#include <iostream.h> int a,b;

int cmmdc (int a,int b) if (a==b) return a; else if (a>b) return cmmdc(a-b,b); else return cmmdc(a,b-a);

main() cout<<"a="; cin>>a; cout<<"b="; cin>>b; cout<<cmmdc(a,b);

Rezolvăm aceeaşi problemă iterativ (utilizând definiţia de mai sus):

Varianta Pascal Varianta C++

var a,b:integer;

begin write('a='); readln(a); write('b='); readln(b); while a<>b do if a>b then a:=a-b else b:=b-a; writeln('cmmdc=',a) end.

#include <iostream.h> main() int a,b; cout<<"a="; cin>>a; cout<<"b="; cin>>b; while (a!=b) if (a>b) a=a-b; else b=b-a; cout<<"cmmdc="<<a;

Pentru această problemă, este indiferent ce variantă de rezolvare se alege.

Aplicaţia 6.5. Să se scrie o funcţie recursivă pentru a calcula suma cifrelor unui număr natural.

Rezolvare. Iniţial, prezentăm varianta iterativă:

Varianta Pascal Varianta C++

var n,s:integer; begin write('n='); readln(n); s:=0; while n<>0 do begin s:=s+n mod 10; n:=n div 10; end; writeln('s=',s) end.

#include <iostream.h> main() int n,s=0; cout<<"n="; cin>>n; while (n) s+=n%10; n/=10; cout<<"s="<<s;

Page 152: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

152 Capitolul 6. Introducere în recursivitate

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):

Programul de mai jos calculează suma utilizând relaţia prezentată:

Varianta Pascal Varianta C++

var n:integer;

function s(n:integer):integer; begin if n=0 then s:=0 else s:=n mod 10 + s(n div 10) end;

begin write('n='); readln(n); writeln(s(n)) end.

#include <iostream.h> int n;

int s(int n) if (!n) return 0; else return n%10 + s(n/10);

main() cout<<"n="; cin>>n; cout<<s(n);

Există posibilitatea ca un subprogram să se autoapeleze prin intermediul altui

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 indirectă. Aplicaţia 6.6. Se consideră şirurile definite recurent astfel: a0=a; b0=b; a,b>0:

1n1nn1n1n

n bab,2

baa −−

−− =+

= . Să se scrie un program care să citească a, b şi n şi să se calculeze an şi bn.

Varianta Pascal Varianta C++

var a,b:real; n:integer;

function bn(n:integer):real; forward; function an(n:integer):real; begin if n=0 then an:=a else an:=(an(n-1+bn(n-1))/2 end;

#include <iostream.h> #include <math.h> double a,b; int n; double bn(int n); double an(int n) if (!n) return a; else return (an(n-1)+bn(n-1))/2;

=

+=

altfel0n

10),divS(n10modn0,

S(n) =

+=

altfel0n

10),S(n / 10%n0,

S(n)

Page 153: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 153

function bn(n:integer):real; begin if n=0 then bn:=b else bn:=sqrt(an(n-1)*bn(n-1) end; begin write(‘a=‘); readln(a); write(‘b=‘); readln(b); write(‘n=‘); readln(n); writeln(an(n):5:10,’ ‘, bn(n):5:10) end.

double bn (int n) if (!n) return b; else return sqrt(an(n-1)*bn(n-1));

main() cout<<"a="; cin>>a; cout<<"b="; cin>>b; cout<<"n="; cin>>n; cout<<an(n)<<' '<<bn(n);

6.5.2. Aplicaţii la care nu dispunem de o formulă de recurenţă Aplicaţia 6.7. Să se scrie o funcţie recursivă care citeşte caractere şi le afişează în ordinea inversă citirii. Sfârşitul şirului este marcat de caracterul “0”.

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 0, se reapelează funcţia;

se tipăreşte caracterul.

Raţionamentul este suficient pentru a scrie programul.

Varianta Pascal Varianta C++

procedure inv; var a:char; begin read(a); if a<>'0' then inv; write(a) end;

begin inv; writeln; end.

#include <iostream.h> void inv() char a; cin>>a; if (a!='0') inv(); cout<<a;

main() inv(); cout<<endl;

Exerciţiu. Modificaţi programul astfel încât caracterul 0 - care marchează sfârşitul şirului - să nu fie tipărit.

Page 154: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

154 Capitolul 6. Introducere în recursivitate

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).

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ă).

Varianta Pascal Varianta C++

var n,b:integer;

procedure transform(n,b:integer); var rest:integer; begin rest:=n mod b; if n>b then transform(n div b,b); write(rest) end;

begin write('n='); readln(n); write('baza='); readln(b); transform(n,b); writeln; end.

#include <iostream.h> int n,b;

void transform(int n,int b) int rest=n%b; if (n>=b) transform(n/b,b); cout<<rest;

main() cout<<"n="; cin>>n; cout<<"baza="; cin>>b; transform(n,b);

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.

Rezolvare. În vederea rezolvării problemei, observăm următoarele: mulţimea cu un singur element 1 are o singură permutare: 1; din fiecare permutare a mulţimii 1,2,...,n-1 (a1,a2,...,an-1), se

obţin următoarele permutări ale mulţimii 1,2,...,n:

n,a2,a3,...,an-1,a1;

a1,n,a3,...,an-1,a2;

a1,a2,n,...,an-1,a3;

...

a1,a2,a3,...,an-1,n.

Page 155: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 155

Figura 6.1. Cazul în care n=3

Pentru n=3, priviţi reprezentarea din figura alăturată:

Avem în vedere posibilitatea de revenire la situaţia iniţială: după ce am operat o interschimbare a elementelor ai şi aj, urmată de o reapelare a funcţiei pentru valoarea k+1, interschimbăm din nou ai cu aj.

Varianta Pascal Varianta C++

var p: array [1..8] of integer; n,i:integer;

procedure tipar; var i:integer; begin for i:=1 to n do write(p[i]); writeln end;

procedure permut(k,n:integer; var p:vector); var i,j,c:integer; begin if k=n+1 then tipar else begin p[k]:=k; for i:=1 to k do begin c:=p[i]; p[i]:=p[k]; p[k]:=c; permut(k+1,n,p); c:=p[i]; p[i]:=p[k]; p[k]:=c end end end;

begin write('n='); readln(n); permut(1,n,p) end.

#include <iostream.h> int p[10],n; void tipar() for (int i=1;i<=n;i++) cout<<p[i]; cout<<endl;

void permut(int k,int n, int p[10]) int i,c; if (k==n+1) tipar(); else p[k]=k; for (i=1;i<=k;i++) c=p[i]; p[i]=p[k]; p[k]=c; permut(k+1,n,p); c=p[i]; p[i]=p[k]; p[k]=c;

main() cout<<"n="; cin>>n; permut(1,n,p);

1

21

12

123

132

312

321

231

213

Page 156: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

156 Capitolul 6. Introducere în recursivitate

Aplicaţia 6.10. Algoritmul Fill. 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 0). De asemenea, se dau coordonatele x şi y ale unui element al matricei, semnificând un punct din interiorul acestei suprafeţe.

Fie matricea de mai jos:

=

0001111010000110

A

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:

=

0001111111110111

A

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.

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 0;

• în caz afirmativ, acesta ia valoarea 1, iar funcţia se autoapelează

pentru fiecare dintre elementele învecinate (sus, jos, dreapta, stânga);

• în caz contrar, se iese din funcţie.

Programul este prezentat în continuare:

Page 157: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 157

Varianta Pascal Varianta C++

program umplere; var a:matrice: array [0..9,0..9] of integer; i,j,m,n,x,y:integer;

procedure scriu (x,y:integer; var a:matrice); begin if a[x,y]=0 then begin a[x,y]:=1; scriu(x+1,y,a); scriu(x,y+1,a); scriu(x-1,y,a); scriu(x,y-1,a) end end;

begin write('M='); readln(m); write('N='); readln(n); for i:=1 to m do for j:=1 to n do begin write('a[',i,',',j,']='); readln(a[i,j]) end; for i:=1 to n do begin a[0,i]:=1; a[m+1,i]:=1 end; for i:=1 to m do begin a[i,0]:=1; a[i,n+1]:=1 end; write('X='); readln(x); write('Y='); readln(y); for i:=1 to m do begin for j:=1 to n do write(a[i,j]); writeln end; scriu(x,y,a); writeln; writeln; for i:=1 to m do begin for j:=1 to n do write(a[i,j]); writeln end end.

#include <iostream.h> int a[10][10],i,j,m,n,x,y;

void scriu (int x,int y, int a[10][10]) if (!a[x][y]) a[x][y]=1; scriu(x+1,y,a); scriu(x,y+1,a); scriu(x-1,y,a); scriu(x,y-1,a);

main() cout<<"M="; cin>>m; cout<<"N="; cin>>n; for (i=1;i<=m;i++) for (j=1;j<=n;j++) cout<<"a["<<i<<',' <<j<<"]="; cin>>a[i][j]; for (i=1;i<=n;i++) a[0][i]=1; a[m+1][i]=1; for (i=1;i<=m;i++) a[i][0]=1; a[i][n+1]=1; cout<<"X="; cin>>x; cout<<"Y="; cin>>y; for (i=1;i<=m;i++) for (j=1;j<=n;j++) cout<<a[i][j]; cout<<endl; scriu(x,y,a); cout<<endl<<endl; for (i=1;i<=m;i++) for (j=1;j<=n;j++) cout<<a[i][j]; cout<<endl;

Page 158: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

158 Capitolul 6. Introducere în recursivitate

Aplicaţia 6.11. Problema fotografiei (aplicaţie Fill). 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:

=

1111111110000011

A ,

iar în matricea de mai jos este reprezentat un singur obiect:

=

0001111010000110

A .

Ca şi în problemele anterioare, pentru a evita testul ieşirii din matrice, aceasta este bordată cu linii şi coloane având valoarea ”0”. Algoritmul este tot cel din problema anterioară (Fill), 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 0 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 mai multe obiecte (altfel, fotografia conţinea un singur obiect).

Varianta Pascal Varianta C++

var a: array [0..9,0..9] of integer; i,j,m,n,x,y:integer; gasit:boolean; procedure compact (x,y:integer; var a:matrice); begin if a[x,y]=1 then begin a[x,y]:=0; compact(x-1,y,a); compact(x-1,y+1,a); compact(x,y+1,a); compact(x+1,y+1,a); compact(x+1,y,a); compact(x+1,y-1,a); compact(x,y-1,a); compact(x-1,y-1,a); end end;

#include <iostream.h> int a[10][10], i,j,m,n, x,y,gasit;

void compact(int x,int y, int a[10][10]) if (a[x][y]) a[x][y]=0; compact(x-1,y,a); compact(x-1,y+1,a); compact(x,y+1,a); compact(x+1,y+1,a); compact(x+1,y,a); compact(x+1,y-1,a); compact(x,y-1,a); compact(x-1,y-1,a);

Page 159: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 159

begin write('M='); readln(m); write('N='); readln(n); for i:=1 to m do for j:=1 to n do begin write('a[',i,',',j,']='); readln(a[i,j]) end; for i:=1 to n do begin a[0,i]:=0; a[m+1,i]:=0; end; for i:=1 to m do begin a[i,0]:=0; a[i,n+1]:=0 end; x:=0; repeat x:=x+1; y:=0; repeat y:=y+1 until (y=n) or (a[x,y]=1) until (x=m) or (a[x,y]=1); compact(x,y,a); gasit:=false; for i:=1 to m do for j:=1 to n do if a[i,j]=1 then gasit:=true; if gasit then writeln('mai multe obiecte') else writeln('un obiect') end.

main() cout<<"M=";cin>>m; cout<<"N=";cin>>n; for (i=1;i<=m;i++) for (j=1;j<=n;j++) cout<<"a["<<i<<',' <<j<<"]="; cin>>a[i][j]; for (i=1;i<=n;i++) a[0][i]=0; a[m+1][i]=0; for (i=1;i<=m;i++) a[i][0]=0; a[i][n+1]=0; x=0; do x++; y=0; do y++; while (y!=n && a[x][y]!=1); while ((x!=m) && a[x][y]!=1); compact(x,y,a); gasit=0; for (i=1;i<=m;i++) for (j=1;j<=n;j++) if (a[i][j]==1) gasit=1; if (gasit) cout<<"mai multe obiecte"; else cout<<"un obiect";

Probleme propuse

1. Calculaţi recursiv suma a n numere naturale citite. 2. Calculaţi recursiv expresiile:

a) 1×2+2×3+...+n×(n+1); b) 1+1/2+...+1/n; c) 1/(2×3)+2/(3*4)+...+n/((n+1)(n+2)).

Page 160: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

160 Capitolul 6. Introducere în recursivitate

3. Se citesc n şi k (numere naturale n>k). Calculaţi recursiv Cnk , prin utilizarea

formulei de recurenţă: C C Cnk

nk

nk= +−

−−1

11. Este eficient?

4. Scrieţi un program iterativ care rezolvă problema anterioară utilizând aceeaşi

formulă.

5. Calculaţi recursiv Cnk prin utilizarea formulei:

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 natural

ascuns de dumneavoastră (numărul este cuprins între 1 şi 30000). Atunci când calculatorul propune un număr i, se va răspunde prin:

1, dacă numărul este prea mare; 2, dacă numărul este prea mic;

0, dacă numărul a fost ghicit. 7. Scrieţi un subprogram recursiv care calculează câte cuvinte distincte cu 2n

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 de

numere naturale cu n componente:

−=

=altfel.V[n])1]),nV[2],...V[1],max(max(V[

1;nV[1],n])V[2],...V[max(V[1],

9. Se citeşte un număr natural n. Se cere să se scrie o funcţie recursivă care

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>1 este prim. 11. Scrieţi o funcţie recursivă care returnează suma elementelor pare ale unui vector citit.

Exemplu: Pentru n=4 şi V=(2,2,5,6), se returnează 10. 12. Scrieţi o funcţie recursivă prin care se testează dacă un număr natural x se regăseşte între componentele unui vector V cu n numere naturale. 13. Scrieţi o funcţie recursivă care primeşte ca parametri două numere naturale i<j şi un număr real x∈[i,j] şi returnează [x] (parte întreagă din x). Nu se vor folosi funcţiile specializate ale limbajului.

+−=

= − .1;0,1

1 altfelCkkn

kC k

n

kn

Page 161: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 161

14. Scrieţi o funcţie recursivă care verifică dacă un vector cu componente numere naturale este palindrom (afişarea componentelor de la 1 la n coincide cu afişarea lor de la n la 1). 15. Scrieţi o funcţie recursivă care returnează numărul cifrelor pe care le are un număr natural primit ca parametru. 16. Scrieţi un subprogram recursiv care afişează, cifră cu cifră, oglinditul unui număr natural.

Exemplu: pentru n=123, se afişează 321. 17. Scrieţi un subprogram recursiv care returnează, oglinditul unui număr natural.

Exemplu: pentru n=123, se returnează 321. 18. Scrieţi o funcţie recursivă care testează dacă un vector cu n numere naturale reţine numai valori distincte, caz în care funcţia returnează true, iar în caz contrar, returnează false. 19. Scrieţi un subprogram recursiv care descompune în toate modurile posibile un număr natural n în două numere n1 şi n2, n1≤n2 a căror sumă este n.

20. Pentru un vector cu n componente 0 sau 1 care are semnificaţia de număr binar, se cere să se scrie o funcţie recursivă care afişează numărul în baza 10.

Exemplu: pentru n=4 şi V=(1011), se va returna 11. 21. Scrieţi o funcţie recursivă care afişează valoarea unui polinom în punctul a. Coeficienţii polinomului sunt daţi într-un vector. Astfel, pentru V=(1,2,3) avem polinomul P=x2+2x+3. 22. Fie funcţia definită pe N*×N*. Se citesc n şi k. Se cere să se scrie o funcţie recursivă care evaluează funcţia:

23. Calculaţi S(n,k) nerecursiv. 24. Calculaţi recursiv şi nerecursiv funcţia definită pe N*×N:

Comparaţi eficienţa celor două moduri de calcul.

<<−+−−∈

>=

nk1k)1,kS(n1)k1,S(nn1,k1,

nk0,k)S(n,

<<−−−−=

>==

n.k1k)1,nF(n1)k1,F(nn;k1

n;ksau0k0,k)F(n,

Page 162: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

162 Capitolul 6. Introducere în recursivitate

25. Să se calculeze recursiv şi nerecursiv P(n,k), definit pe N*×N*:

P(n+k,k)=P(n,1)+P(n,2)+...+P(n,k), dacă 1<k<n;

P(n,k)=1, dacă k=1 sau k=n;

P(n,k)=0, 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:

27. Ce se afişează la: writeln(t(12)); / cout<<t(12); ?

Varianta Pascal Varianta C++

function t(n:integer):integer; begin if n<>0 then t:=10*t(n div 10) +(n mod 10) else t:=0; end;

int t(int n) if (n) return 10*t(n/10)+ n%10; else return 0;

a) 12; b) 21; c) eroare de executare; d) 0.

28. Ce calculează funcţia următoare?

Varianta Pascal Varianta C++

function t(n:integer):integer; begin if n<>0 then t:=ord(n mod 2=0)*n+t(n-1) else t:=0; end;

int t(int n) if (n) return (n%2==0)*n +t(n-1); else return 0;

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.

=≠

=0nm,0nn),modmcmmdc(n,

n)cmmdc(m,

=≠

=0nm,0nn),%mcmmdc(n,

n)cmmdc(m,

Pascal C++

Page 163: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 163

29. Pentru care dintre numerele de mai jos, care sunt parametri de intrare pentru funcţia următoare, ultimele 2 numere afişate vor fi 0?

Varianta Pascal Varianta C++

procedure t(n:integer); begin if n>15 then begin t(n div 16); writeln(n mod 16) end else writeln (n); end;

void t(int n) if (n>15) t(n/16); cout<<n%16<<endl; else cout<<n<<endl;

a) 295; b) 1024; c) 1000; d) 10000. Testele de la 30 la 33 se referă la funcţia de mai jos, unde V1 şi V2 sunt vectori cu n componente numere naturale:

Varianta Pascal Varianta C++

procedure t(n,i,j:integer); begin if (i<=n) and (j<=n) then begin if V1[i]<V2[j] then begin write (V1[i],’ ‘); i:=i+1 end else begin write (V2[j],’ ‘); j:=j+1 end; t(n,i,j); end; end;

void t(int n, int i, int j) if (i<=n && j<=n) if (V1[i]<V2[j]) cout<<V1[i++]<<" "; else cout<<V2[j++]<<" "; t(n,i,j); // V1 si V2 contin date // incepand cu indicele 1

30. Dacă n este egal cu 3, V1=(1,2,3) şi V2=(4,5,1) ce se afişează la 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.

Page 164: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

164 Capitolul 6. Introducere î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 maxim de valori distincte, dacă apelul este t(n,1,1)? a) V1=(1,2,8) şi V2=(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) şi 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 următor:

Varianta Pascal Varianta C++

type vector=array[1..10] of integer; var m,n,i:integer; V:vector;

procedure b(k,n:integer); begin if n<>0 then begin b(k+1,n div 2); if n mod 2=1 then writeln(V[k]);; end end;

begin readln (n); for i:=1 to n do readln(V[i]); readln(m);b(1,m); end.

#include <iostream.h> int m,n,i,V[20];

void b(int k, int n) if (n) b(k+1,n/2); if (n%2) cout<<V[k]<<endl;

main() int i,n; cin>>n; for (i=1;i<=n;i++) cin>>V[i]; cin>>m; b(1,m);

34. Dacă n=4, V=(1,2,3,4) şi m=2, ce afişează programul? a) 4; b) 3; c) 2; d) 1.

Page 165: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 165

35. Dacă n=4, pentru care dintre valorile de mai jos ale lui m se afişează două valori? a) 4; b) 3; c) 2; d) 1. 36. Dacă n=4, pentru care valoare a lui m se afişează toate cele 4 valori ale vectorului? a) 15; b) 16; c) 0; d) Nu există o astfel de valoare. 37. În funcţia de mai jos înlocuiţi linia ”...” cu una dintre instrucţiunile următoare, astfel încât funcţia să-şi încheie execuţia fără eroare pentru orice valoare admisibilă a argumentului:

Varianta Pascal Varianta C++

function x(n:integer):integer; begin if n<>0 then begin writeln(n); ... end end; a) x:=x(n-2); b) x:=x(n mod 2); c) x:=x(n-1); d) x:=x(n div 2).

int x(int n) if (n) cout<<n; ... a) return x(n-2); b) return x(n%2); c) return x(n-1); d) return x(n/2).

Testele 38 şi 39 se referă la funcţia de mai jos:

Varianta Pascal Varianta C++

function an(n:integer):integer; begin if n=0 then an:=1 else an:=3*an(n-1)+7 end;

int an(int n) if (n==0) return 1; else return 3*an(n-1)+7;

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 5 ori; d) depinde de stivă. 39. Pentru care dintre valorile de mai jos, care sunt parametri de intrare pentru funcţia an, executarea funcţiei se termină cu eroare? a) 0; b) -1; c) 1; d) nici una dintre valorile de mai sus.

Page 166: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

166 Capitolul 6. Introducere în recursivitate

( ) nnC

nnn

2!!!2

=⋅

40. Pentru programul de mai jos, de câte ori se autoapelează funcţia an?

Varianta Pascal Varianta C++

function an(n:integer):integer; begin if n=0 then an:=2 else if n=1 then an:=1 else an:=an(n-1)-an(n-2)+1 end;

begin writeln(an(4)); end.

#include <iostream.h> int an(int n) if (n==0) return 2; else if (n==1) return 1; else return an(n-1)-an(n-2)+1;

main() cout<<an(4);

a) de 8 ori; b) de 4 ori; c) de 9 ori; d) de 2 ori. Indicaţii / Rezolvări 1. Sn=1+2+....n-1+n; Sn⇒Sn-1+n;

7. Există (2n)! permutări ale literelor. Întrucât pentru fiecare permutare nu contează dacă s-a inversat A cu A, (2n)! se împarte la n!. Pentru că nu contează dacă s-a inversat B cu B (2n)!, se împarte din nou la n!. Se obţine: 8.

Varianta Pascal Varianta C++

function Maxim(n:integer):integer; var max:integer; begin if n=1 then Maxim:=V[1] else begin max:=Maxim(n-1); if max<V[n] then Maxim:=V[n] else Maxim:=max; end; end;

int Maxim(int n) int max; if (n==1) return V[1]; else max=Maxim(n-1); if (max<V[n]) return V[n]; else return max;

+=

=− altfeln,S

0ndaca0,S

1nn

Page 167: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 167

9. Se calculează cifra maximă a numărului şi se adună 1. 10. De exemplu, funcţia de mai jos se apelează Prim(n,2):

Varianta Pascal Varianta C++ function Prim(n,i:integer):boolean; begin if i>trunc(sqrt(n))+1 then Prim:=true else if n mod i=0 then Prim:=false else Prim:=Prim(n,i+1); end;

int Prim(int n,int i) if (i>(int)(sqrt(n)+1)) return 1; else if (n%i==0) return 0; else return Prim(n,i+1);

11.

Varianta Pascal Varianta C++

function Suma(n:integer):integer; begin if n=0 then Suma:=0 else Suma:=Suma(n-1)+ ord(V[n] mod 2=0)*V[n]; end;

int Suma(int n) if (n==0) return 0; else return Suma(n-1)+ (V[n]%2==0)*V[n];

12.

Varianta Pascal Varianta C++

function Apartine(n,x:integer):boolean; begin if n=0 then Apartine:=false else if V[n]=x then Apartine:=true else Apartine:=Apartine(n-1,x); end;

int Apartine(int n, int x) if (n==0) return 0; else if (V[n]==x) return 1; else return Apartine(n-1,x);

Page 168: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

168 Capitolul 6. Introducere în recursivitate

13. Este clasicul algoritm de căutare binară.

Varianta Pascal Varianta C++

function PInt(i,j:integer;x:real):integer; var Mijloc:integer; begin if j-i>1 then begin Mijloc:=(i+j) div 2; if x=Mijloc then Pint:=Mijloc else if x<Mijloc then Pint:=Pint(i,Mijloc,x) else Pint:=Pint(Mijloc,j,x) end else Pint:=i end;

int PInt(int i,int j,float x) int Mijloc; if (j-i>1) Mijloc=(i+j)/2; if (x==Mijloc) return Mijloc; else if (x<Mijloc) return PInt(i,Mijloc,x); else PInt(Mijloc,j,x); else return i;

14. Iniţial, se apelează cu Palindrom(1,n):

Varianta Pascal Varianta C++

function Palindrom(i,j:integer):boolean; begin if i>=j then Palindrom:=true else if V[i]<>V[j] then Palindrom:=false else Palindrom:= Palindrom(i+1,j-1); end;

int Palindrom(int i, int j) if (i>=j) return 1; else if (V[i]!=V[j]) return 0; else return Palindrom(i+1,j-1);

15.

Varianta Pascal Varianta C++

function Nrcif (n:longint): integer; begin if n<10 then Nrcif:=1 else Nrcif:=NrCif(n div 10)+1; end;

int Nrcif(long n) if (n<10) return 1; else return Nrcif(n/10)+1;

Page 169: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 169

16.

Varianta Pascal Varianta C++

procedure Oglinda(n:integer); begin if n<>0 then begin write(n mod 10); Oglinda(n div 10); end; end;

void Oglinda(int n) if (n) cout<<n%10; Oglinda (n/10);

17. Variabila ninv, transmisă prin referinţă, trebuie să reţină, iniţial, 0.

Varianta Pascal Varianta C++

procedure Oglinda(n:integer; var ninv:integer); begin if n<>0 then begin ninv:=ninv*10+n mod 10; Oglinda(n div 10, ninv); end; end;

void Oglinda(int n, int& ninv) if (n) ninv=ninv*10+n%10; Oglinda (n/10,ninv);

18. Funcţia se apelează cu Distincte(1,n):

Varianta Pascal Varianta C++

function Distincte(i,n:integer):boolean; var gasit:boolean; j:integer; begin if i=n then Distincte:=true else begin gasit:=false; for j:=i+1 to n do if V[i]=V[j] then gasit:=true; if gasit then Distincte:=false else Distincte:= Distincte(i+1,n); end end;

int Distincte(int i,int n) int gasit,j; if (i==n) return 1; else gasit=0; for (j=i+1;j<=n;j++) if (V[i]==V[j]) gasit=1; if (gasit) return 0; else return Distincte(i+1,n);

Page 170: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

170 Capitolul 6. Introducere în recursivitate

19. Apelaţi cu Descompun(1,n):

Varianta Pascal Varianta C++

procedure Descompun(i,n:integer); begin if i<= n div 2 then begin writeln (i, ' ', n-i); Descompun(i+1,n); end end;

void Descompun(int i,int n) if (i<=n/2) cout<<i<<" "<<n-i<<endl; Descompun(i+1,n);

20. Apelaţi cu Refac(n,n):

Varianta Pascal Varianta C++

function Refac(i,n:integer):integer; begin if i=1 then Refac:=V[1] else Refac:=2*Refac(i-1,n)+V[i]; end;

int Refac(int i,int n) if (i==1) return V[1]; else return 2*Refac(i-1,n)+V[i];

21. Apelaţi cu Valoare(n,n,a):

Varianta Pascal Varianta C++

function Valoare(i,n,a:integer):integer; begin if i=1 then Valoare:=V[1] else Valoare:=a*Valoare(i-1, n,a)+V[i]; end;

int Valoare(int i,int n,int a) if (i==1) return V[i]; else return a*Valoare(i-1, n,a)+V[i];

22. Este ineficient să calculăm recursiv. 23. Să presupunem că avem de calculat S(8,5). Mai întâi calculăm: S(1,1)=1, S(1,2)=0, S(1,3)=0, ..., S(1,8)=0. Urmează: S(2,1)=1, S(2,2)=1,S(2,3)=0, ..., S(2,8)=0 S(3,1)=1, S(3,2)=S(2,1)+2S(2,2)=3, S(3,3)=1, S(3,4)=0, ... S(4,1)=1, S(4,2)=S(3,1)+2S(3,2)=7, ... Cu excepţia primei linii S(1,x), pentru calculul elementelor de pe linia i, S(i,k), se folosesc rezultatele aflate pe linia i-1, adică S(i-1,x).

Page 171: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 171

Figura 6.2. Exemplu

25. În cazul în care k<n, avem:

P(n,k)=P(n-k+k,k)=P(n-k,1)+P(n-k,2)+...+P(n-k,k).

Varianta Pascal Varianta C++

function P(n,k:integer):integer; var i,s:integer; begin if k>n then P:=0 else if (k=1) or (k=n) then P:=1 else begin s:=0; for i:=1 to n-k do s:=s+P(n-k,i); P:=s; end end;

int P(int n,int k) int i,s=0; if (k>n) return 0; else if (k==1 || k==n) return 1; else for (i=1;i<=n-k;i++) s+=P(n-k,i); return s;

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:

Acum, vedeţi “pe viu” motivul pentru care, în cazul unor astfel de formule de

recurenţă, în care, în expresie, intervin mai mulţi operanzi ce se calculează recursiv, este de preferat metoda iterativă.

4

3 2

2 1

1 0

1 0

Page 172: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

172

Capitolul 7

Metoda DIVIDE ET IMPERA

7.1. Prezentare generală

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 soluţ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 descompunerilor 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 recursivi: ce se întâmplă la un nivel, se întâmplă la 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

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?

Page 173: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 173

Rezolvare. Trebuie tipărită valoarea maximă dintre numerele reţinute în vector de la i la j (iniţial i=1 şi j=n).

Pentru aceasta, procedăm astfel:

• dacă i=j, valoarea maximă va fi v[i];

• 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.

Programul este următorul:

Varianta Pascal Varianta C++

var v:array[1..10] of integer; n,i:integer; function max(i,j:integer) :integer; var a,b:integer; begin if i=j then max:=v[i] else begin a:=max(i,(i+j) div 2); b:=max((i+j) div 2+1,j); if a>b then max:=a else max:=b; end; end;

begin write('n='); readln(n); for i:=1 to n do begin write('v[',i,']='); readln(v[i]) end; writeln('max=',max(1,n)) end.

#include <iostream.h> int v[10],n; int max(int i,int j) int a,b; if (i==j) return v[i]; else a=max(i,(i+j)/2); b=max((i+j)/2+1,j); if (a>b) return a; else return b; main() cout<<"n="; cin>>n; for (int i=1;i<=n;i++) cout<<"v["<<i<<"]="; cin>>v[i]; cout<<"max="<<max(1,n);

Algoritmul prezentat este exclusiv didactic, în practică este preferat algoritmul clasic.

Page 174: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

174 Capitolul 7. Metoda DIVIDE ET IMPERA

7.2.2. Sortarea prin interclasare

Problema 7.2. Se consideră vectorul a cu n componente numere întregi (sau 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ţine 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ării unui vector prin interclasare.

Rezolvare. Algoritmul de sortare prin interclasare se bazează pe următoarea idee: pentru a sorta un vector cu n elemente îl împărţim în doi vectori care, odată sortaţi, se interclasează.

Conform strategiei generale DIVIDE ET IMPERA, problema este descompusă în

alte două subprobleme de acelaşi tip şi, după rezolvarea lor, rezultatele se combină (în particular se interclasează). Descompunerea unui vector în alţi doi vectori care urmează a fi sortaţi are loc până când avem de sortat vectori de una sau două componente.

În aplicaţie, funcţia sort sortează un vector cu maximum două elemente;

interc interclasează rezultatele; divimp implementează strategia generală a metodei studiate.

Varianta Pascal Varianta C++

type vector=array [1..10] of integer; var a:vector; n,i:integer; procedure sort(p,q:integer; var a:vector); var m:integer; begin if a[p]>a[q] then begin m:=a[p]; a[p]:=a[q]; a[q]:=m end end;

#include <iostream.h>

int a[10],n; void sort(int p,int q, int a[10]) int m; if (a[p]>a[q]) m=a[p]; a[p]=a[q]; a[q]=m;

void interc(int p,int q, int m,int a[10]) int b[10],i,j,k; i=p; j=m+1; k=1;

Page 175: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 175

procedure interc (p,q,m:integer; var a:vector);

var b:vector; i,j,k:integer;

begin i:=p; j:=m+1; k:=1; while (i<=m) and (j<=q) do if a[i]<=a[j] then begin b[k]:=a[i]; i:=i+1; k:=k+1 end else begin b[k]:=a[j]; j:=j+1; k:=k+1 end; if i<=m then for j:=i to m do begin b[k]:=a[j]; k:=k+1 end else for i:=j to q do begin b[k]:=a[i]; k:=k+1 end; k:=1; for i:=p to q do begin a[i]:=b[k]; k:=k+1 end end;

procedure divimp (p,q:integer;var a:vector); var m:integer; begin if (q-p)<=1 then sort(p,q,a) else begin m:=(p+q) div 2; divimp(p,m,a); divimp(m+1,q,a); interc(p,q,m,a) end end;

begin write('n='); readln(n); for i:=1 to n do begin write('a[',i,']='); readln(a[i]) end; divimp(1,n,a); for i:=1 to n do writeln(a[i]) end.

while (i<=m && j<=q) if (a[i]<=a[j]) b[k]=a[i]; i=i+1; k=k+1; else b[k]=a[j]; j=j+1; k=k+1; if (i<=m) for (j=i;j<=m;j++) b[k]=a[j]; k=k+1; else for (i=j;j<=q;j++) b[k]=a[i]; k=k+1; k=1; for (i=p;i<=q;i++) a[i]=b[k]; k=k+1; void divimp (int p,int q, int a[10]) int m; if ((q-p)<=1) sort(p,q,a); else m=(p+q)/2; divimp(p,m,a); divimp(m+1,q,a); interc(p,q,m,a); main() int i; cout<<"n="; cin>>n; for (i=1;i<=n;i++) cout<<"a["<<i<<"]="; cin>>a[i]; divimp(1,n,a); for (i=1;i<=n;i++) cout<<a[i]<<" ";

Page 176: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

176 Capitolul 7. Metoda DIVIDE ET IMPERA

Î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:

+

== altfel.n,

2n2T

1;n0,T(n)

Avem:

nlognknn...nn2...22...22)2T(2

2)22T(22)2T(2)2)2(T(2)T(2T(n)

2orikdeorikde

kkkkk2k

k1k2kk1k1k1kk

⋅=⋅=+++=+++=++

=++=+=+==−

−−−−−

7.2.3. Sortarea rapidă

Problema 7.3. Fie vectorul a cu n componente numere întregi (sau reale). Se cere ca vectorul să fie sortat crescător.

Rezolvare. Este necesară o funcţie POZ care tratează o porţiune din vector, cuprinsă între indicii daţi de li (limita inferioară) şi ls (limita superioară). Rolul acestei funcţii este de a poziţiona prima componentă a[li] pe o poziţie k cuprinsă între li şi ls, astfel încât toate componentele vectorului cuprinse între li şi k-1 să fie mai mici sau egale decât a[k] şi toate componentele vectorului cuprinse între k+1 şi ls să fie mai mari sau egale decâ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, i va lua valoarea li, iar j va lua valoarea ls (elementul care iniţial se află pe poziţia li 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[i] este strict mai mare decât a[j], atunci se inversează cele două numere ş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.

Page 177: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 177

Pentru a=(6,9,3,1,2), li=1, ls=5; modul de lucru a):

• i=1, j=5; • a[1]>a[5], 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[2]>a[5], deci a=(2,6,3,1,9) şi se revine la modul de lucru a); • i=2, j=4; • a[2]>a[4], deci a=(2,1,3,6,9); se trece la modul de lucru b); • i=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).

Alternanţa modurilor de lucru se explică prin faptul că elementul care trebuie

poziţionat se compară cu un element aflat în dreapta sau în stânga lui, ceea ce impune o modificare corespunzătoare a indicilor i şi j.

După aplicarea funcţiei POZ, este evident că elementul care se află iniţial în poziţia li va ajunge pe o poziţie k şi va rămâne pe acea poziţie în cadrul vectorului deja sortat, fapt care reprezintă esenţa algoritmului.

Funcţia QUICK are parametrii li ş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 li şi k-1; • se apelează QUICK pentru k+1 şi ls.

Varianta Pascal Varianta C++

type vector=array [1..100] of integer; var i,n,k:integer; a:vector;

procedure poz (li,ls:integer; var k:integer; var a:vector);

var i,j,c,i1,j1:integer;

begin

i1:=0; j1:=-1; i:=li; j:=ls;

#include <iostream.h> int a[100],n,k;

void poz (int li,int ls,int& k,int a[100]) int i=li,j=ls,c,i1=0,j1=-1; while (i<j) if (a[i]>a[j]) c=a[j]; a[j]=a[i]; a[i]=c; c=i1; i1=-j1; j1=-c; i=i+i1; j=j+j1; k=i;

Page 178: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

178 Capitolul 7. Metoda DIVIDE ET IMPERA

while i<j do begin if a[i]>a[j] then begin c:=a[j]; a[j]:=a[i]; a[i]:=c; c:=i1; i1:=-j1; j1:=-c end; i:=i+i1; j:=j+j1 end; k:=i end;

procedure quick(li,ls:integer); begin if li<ls then begin poz(li,ls,k,a); quick(li,k-1); quick(k+1,ls) end end;

begin write('n='); readln(n); for i:=1 to n do begin write('a[',i,']='); readln(a[i]) end; quick(1,n); for i:=1 to n do writeln(a[i]) end.

void quick (int li,int ls) if (li<ls) poz(li,ls,k,a); quick(li,k-1); quick(k+1,ls); main() int i; cout<<"n="; cin>>n; for (i=1;i<=n;i++) cout<<"a["<<i<<"]="; cin>>a[i]; quick(1,n); for (i=1;i<=n;i++) cout<<a[i]<<endl;

Reţineţi! Sortarea rapidă efectuează în medie nn 2log⋅ operaţii.

Demonstraţia necesită cunoştinţe de matematică pe care nu le aveţi la nivelul acestui an de studiu…

Page 179: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 179

7.2.4. Turnurile din Hanoi

Problema 7.4. Se dau 3 tije simbolizate prin a, b, c. Pe tija a se găsesc discuri de diametre diferite, aşezate în 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ă tija c, respectând următoarele reguli:

• la fiecare pas se mută un singur disc;

• nu este permis să se aşeze un disc cu diametrul mai mare peste un disc cu diametrul 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,c) şirul

mutărilor celor n discuri de pe tija a pe tija b, utilizând ca tijă intermediară, tija c.

Conform strategiei DIVIDE ET IMPERA, încercăm să descompunem problema în alte două subprobleme de acelaşi tip, urmând apoi combinarea soluţiilor. În acest sens, observăm că mutarea celor n discuri de pe tija a pe tija b, utilizând ca tijă intermediară tija c, este echivalentă cu: − mutarea a n-1 discuri de pe tija 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 şirului H(n,a,b,c) astfel:

>−−=

=1na),b,c,1,H(nab,b),c,a,1,H(n1nab,

c)b,a,H(n,

Priviţi următoarele exemple:

1) pentru n=2, avem: H(2,a,b,c)=H(1,a,c,b),ab,H(1,c,b,a)=ac,ab,cb; 2) pentru n=3, avem: H(3,a,b,c)=H(2,a,c,b),ab,H(2,c,b,a)=H(1,a,b,c),ac,H(1,b,c,a), ab,H(1,c,a,b),cb,H(1,a,b,c)=ab,ac,bc,ab,ca,cb,ab.

Page 180: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

180 Capitolul 7. Metoda DIVIDE ET IMPERA

Varianta Pascal Varianta C++ var a,b,c:char; n:integer; procedure han (n:integer; a,b,c:char); begin if n=1 then writeln(a,b) else begin han(n-1,a,c,b); writeln(a,b); han(n-1,c,b,a) end end;

begin write('N='); readln(n); a:='a'; b:='b'; c:='c'; han(n,a,b,c) end.

#include <iostream.h> char a,b,c; int n;

void han (int n,char a, char b,char c) if (n==1) cout<<a<<b<<endl; else han(n-1,a,c,b); cout<<a<<b<<endl; han(n-1,c,b,a);

main() cout<<"N="; cin>>n; a='a'; b='b'; c='c'; han(n,a,b,c);

7.2.5. Problema tăieturilor

Problema 7.5. Se dă o bucată dreptunghiulară de tablă cu lungimea l şi înălţimea h, 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.

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). În 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, HF. Î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.

Page 181: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 181

Fie dreptunghiul cu o gaură:

Pentru a se afla în interiorul dreptunghiului, gaura trebuie să îndeplinească simultan condiţiile:

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), l, h+y-yv(i).

Programul este următorul:

Varianta Pascal Varianta C++

type vect=array [1..9] of integer;

var l,h,i,n,xf,yf, lf,hf:integer; xv,yv:vect;

procedure dimp (x,y,l,h:integer; var xf,yf,lf,hf:integer; var xv,yv:vect);

var gasit:boolean; i:integer; begin i:=1; gasit:=false; while (i<=n) and (not gasit) do if (xv[i]>x) and (xv[i]<l) and (yv[i]>y) and (yv[i]<y+h) then gasit:=true else i:=i+1;

#include <iostream.h> int l,h,i,n,xf,yf,lf, hf,xv[10],yv[10];

void dimp(int x,int y,int l, int h, int& xf, int& yf, int& lf,int& hf, int xv[10],int yv[10]) int gasit=0,i=1; while (i<=n && !gasit) if (xv[i]>x && xv[i]<l && yv[i]>y && yv[i]<y+h) gasit=1; else i++; if (gasit) dimp(x,y,xv[i]-x,h,xf, yf,lf,hf,xv,yv); dimp(xv[i],y,l+x-xv[i], h,xf,yf,lf,hf,xv,yv); dimp(x,y,l,yv[i]-y,xf, yf,lf,hf,xv,yv); dimp(x,yv[i],l,h+y-yv[i], xf,yf,lf,hf,xv,yv);

• xv(i),yv(i) h

l x,y

Page 182: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

182 Capitolul 7. Metoda DIVIDE ET IMPERA

if gasit then begin dimp(x,y,xv[i]-x, h,xf,yf,lf,hf,xv,yv); dimp(xv[i],y,l+x-xv[i], h,xf,yf,lf,hf,xv,yv); dimp(x,y,l,yv[i]-y, xf,yf,lf,hf,xv,yv); dimp(x,yv[i],l,h+y-yv[i], xf,yf,lf,hf,xv,yv) end else if (l*h)>(lf*hf) then begin xf:=x; yf:=y; lf:=l; hf:=h end end;

begin write('n='); readln(n); for i:=1 to n do begin write('x[',i,']='); readln(xv[i]); write('y[',i,']='); readln(yv[i]) end; write('l='); readln(l); write('h='); readln(h); lf:=0; hf:=0; dimp(0,0,l,h,xf,yf, lf,hf,xv,yv); writeln('x=',xf,' y=',yf,' l=',lf,' h=',hf) end.

else if (l*h>lf*hf) xf=x; yf=y; lf=l; hf=h;

main() cout<<"n="; cin>>n; for (int i=1;i<=n;i++) cout<<"x["<<i<<"]="; cin>>xv[i]; cout<<"y["<<i<<"]="; cin>>yv[i]; cout<<"l="; cin>>l; cout<<"h="; cin>>h; dimp(0,0,l,h,xf,yf,lf, hf,xv,yv); cout<<"x="<<xf<<" y="<<yf <<" l="<<lf<<" h="<<hf;

Page 183: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 183

7.3. Fractali

Fractalii au fost introduş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

informaţia genetică conţinută în nucleul unei celule se repetă la diferite scări. Calculatorul permite ca o anumită 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ţii

geometrice”, iar multe dintre ele erau atât de complexe încât necesitau calculatoare performante pentru a le vizualiza. Pe parcurs, domenii ştiinţifice 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ţele 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. Generalităţ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, calculatorul utilizează placa

video, care diferă în 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 Pascal deţine 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 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 în continuare doar la driver-ul VGA (Video Graphics Array), dezvoltat de firma IBM. Driver-ul VGA poate lucra în mai multe moduri, însă vom prefera modul standard de înaltă rezoluţie ”VGAHI” (constantă de tip întreg), ce poate afişa 640 x 480 puncte în 16 culori.

Page 184: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

184 Capitolul 7. Metoda DIVIDE ET IMPERA

Selectarea driver-ului şi a modului de lucru se face prin utilizarea procedurii 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 proceduri este

initgraph(gdriver,gmode,‘cale’);.

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 î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<>0 then begin writeln(“Tentativa esuata!”); halt end end;

Constanta detect are valoarea 0 ş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,’c:\tp\bgi’); if graphresult<>0 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 întreagă graphresult care returnează 0 în caz afirmativ şi o valoare diferită de 0, în caz contrar.

Odată intraţi în modul grafic, nu se mai poate scrie pe monitor, ca până acum (cu write sau writeln). Ieşirea din modul grafic se face prin utilizarea procedurii closegraph.

Page 185: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-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 aplicaţii grafice. Acestea sunt reunite în fişierul GRAPHICS.H, ce se găseşte în folderul INCLUDE.

Pentru ca o imagine să poată apărea pe ecran, calculatorul utilizează placa

video, care diferă în 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ă în 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 în continuare doar la driver-ul VGA (Video Graphics Array), dezvoltat de firma IBM. Driver-ul VGA poate lucra în mai multe moduri, însă vom prefera modul standard de înaltă rezoluţie ”VGAHI” (constantă de tip întreg), ce poate afişa 640 x 480 puncte în 16 culori. Fişierul ce conţine driver-ul utilizat este “EGAVGA.CGI”.

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,&gmode,"cale");.

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 încarce un anumit driver şi să se selecteze modul de lucru – cel mai bun din punct de vedere al performanţelor:

void init() gdriver = DETECT; initgraph(&gdriver,&gmode,"E:\\BORLANDC\\BGI"); if (graphresult()) cout<<"Tentativa nereusita."; cout<<"Apasa o tasta pentru a inchide..."; getch(); exit(1); Constanta DETECT are valoarea 0 ş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 în exemplele ulterioare.

Page 186: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

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:\\BORLANDC\\BGI");

if (graphresult()) cout<<"Tentativa nereusita."; cout<<"Apasa o tasta pentru a inchide..."; getch(); exit(1);

Tentativa de iniţializare grafică poate eşua din diverse motive, cum ar fi: lipsa

unităţii GRAPHICS, calea indicată greşit, etc. Testarea se realizează cu funcţia întreagă graphresult() care returnează 0 în caz afirmativ şi o valoare diferită de 0, î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 closegraph().

Atenţie! Pentru a putea scrie şi rula programe C++ ce utilizează modul grafic al limbajului, trebuie bifată următoarea opţiune, din meniu:

Options / Linker / Libraries / Graphics library.

7.3.1.3. Setarea culorilor şi procesul de desenare (Pascal şi C++)

Cu siguranţă, placa video utilizată de dvs. are performanţe superioare modului standard VGA, ce se regăseşte în 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 0 la 15, precum

urmează: 0 – black (negru); 1 – blue (albastru); 2 – green (verde); 3 – cyan (turcoaz); 4 – red (roşu); 5 – magenta (violet); 6 – brown (maro); 7 – lightgrey (gri deschis); 8 – darkgrey (gri închis); 9 – lightblue (albastru deschis); 10 – lightgreen (verde deschis); 11 – lightcyan (turcoaz deschis); 12 – lightred (roşu deschis); 13 – lightmagenta (violet deschis); 14 – yellow (galben) şi 15 – white (alb).

Aceste culori sunt cele implicite. Pentru a utiliza mai multe culori (dar nu în 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.

Page 187: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 187

Pentru a seta culoarea de fundal, se utilizează procedura (în Pascal) sau funcţia (în C++)

setbkcolor(culoare);.

Exemple: setbkcolor(6); sau setbkcolor(RED);.

Selectarea culorii cu care se desenează se face cu ajutorul procedurii (în Pascal) sau funcţiei (în C++)

setcolor(culoare);.

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 întregi. Punctul din stânga-sus are coordonatele (0,0). Pentru a ne muta la poziţia (x,y), vom folosi procedura (în Pascal) sau funcţia (în C++) moveto(x,y). Pentru a trasa o linie de la punctul curent, determinat anterior, până la o nouă poziţie, vom utiliza procedura (în Pascal) sau funcţia (în C++) lineto(x1,y1). Astfel, vom obţine o linie între punctele de coordonate (x,y) şi (x1,y1). Exemplu. Mai jos, este prezentat un program ce desenează o linie pe diagonala principală a ecranului (de la colţul din stânga-sus la colţul din dreapta jos):

Varianta Pascal Varianta C++

... begin initg; setcolor(red); moveto(0,0); lineto(getmaxx,getmaxy); readln; end.

... main() init(); setcolor(RED); moveto(0,0); lineto(getmaxx(),getmaxy()); getch();

De asemenea, două funcţii foarte utile sunt getmaxx şi getmaxy (în

Pascal) sau getmaxx() şi getmaxy() (în C++). Acestea întorc 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.

Page 188: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

188 Capitolul 7. Metoda DIVIDE ET IMPERA

7.3.2. Curba lui Koch pentru un triunghi echilateral

Se consideră un triunghi echilateral. Fiecare latură a sa se transformă aşa cum se vede în figura următoare (se împarte în trei segmente congruente, se elimină segmentul din mijloc şi se construieşte deasupra un triunghi echilateral):

Fiecare latură a acestui poligon se transformă din nou, după aceeaşi regulă. Să se vizualizeze figura obţinută după ls paşi (număr citit de la tastatură).

Această curbă este cunoscută în literatura de specialitate ca fiind curba lui Koch (Herge von Koch a fost matematician suedez şi a imaginat 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 (x1,y1),

iar B are coordonatele (x2,y2). Două puncte P1 şi P2 împart segmentul într-un anumit raport, notat cu k:

kykyy

kxkxx pp −

⋅−=

−⋅−

=1

,1

2121 .

Demonstraţi singuri aceste formule!

Fie segmentul AB cu A(x1,y1) şi B(x2,y2). Considerăm punctele C şi D care împart segmentul în trei segmente congruente.

Aflăm coordonata punctului D:

32

,32

;2 2121 yyy

xxx

DBDAk pp

⋅+=

⋅+=−== .

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 3/π . Rotaţia o efectuează procedura rotplan.

Figura 7.1. Exemplu de transformare

Page 189: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-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ă-l 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.

În 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ătorul:

Varianta Pascal Varianta C++

uses graph,crt; var L,gdriver,gmode, ls:integer; xmax,ymax:integer;

procedure initg; ...

procedure rotplan(xc,yc,x1, y1:integer; var x,y:integer; unghi:real); begin x := round(xc+(x1-xc)* cos(unghi)-(y1-yc)* sin(unghi)); y := round(yc+(x1-xc)* sin(unghi)+(y1-yc)* cos(unghi)) end;

procedure desenez(x1,y1,x2, y2,x3,y3:integer); begin moveto(x1,y1); lineto((2*x1+x2) div 3, (2*y1+y2) div 3); lineto(x3,y3); lineto((x1+2*x2) div 3, (y1+2*y2) div 3); lineto(x2,y2); end;

procedure generator(x1,y1,x2,y2, n,ls:integer); var x,y:integer;

#include "graphics.h" #include <iostream.h> #include <stdlib.h> #include <conio.h> #include <math.h> int gdriver,gmode,ls,L;

void init() ...

void rotplan(int xc,int yc, int x1, int y1,int &x, int &y,float unghi) x = ceil(xc+(x1-xc)*cos(unghi)- (y1-yc)*sin(unghi)); y = ceil(yc+(x1-xc)*sin(unghi)+ (y1-yc)*cos(unghi));

void desenez(int x1,int y1, int x2,int y2,int x3,int y3) moveto(x1,y1); lineto(div((2*x1+x2),3).quot, div((2*y1+y2),3).quot); lineto(x3,y3); lineto(div((x1+2*x2),3).quot, div((y1+2*y2),3).quot); lineto(x2,y2);

void generator(int x1,int y1, int x2,int y2,int n, int ls) int x,y; rotplan(div((2*x1+x2),3).quot, div((2*y1+y2),3).quot, div((x1+2*x2),3).quot, div((y1+2*y2),3).quot, x,y,M_PI/3);

Page 190: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

190 Capitolul 7. Metoda DIVIDE ET IMPERA

ls = 2 ls = 3 ls = 4

begin rotplan((2*x1+x2) div 3, (2*y1+y2) div 3,(x1+2*x2) div 3,(y1+2*y2) div 3,x,y,pi/3); if n<ls then begin generator(x1,y1,(2*x1+x2) div 3,(2*y1+y2) div 3, n+1,ls); generator((2*x1+x2) div 3, (2*y1+y2) div 3, x,y,n+1,ls); generator(x,y,(x1+2*x2) div 3,(y1+2*y2) div 3,n+1,ls); generator((x1+2*x2) div 3, (y1+2*y2) div 3, x2,y2,n+1,ls); end else desenez(x1,y1,x2,y2,x,y); end;

begin write('ls= '); readln(ls); initg; setcolor(red); L:=getmaxx-320; generator(160,getmaxy-150, 160+L,getmaxy-150,1,ls); generator(160+L,getmaxy-150, 160+L div 2,getmaxy-150 – L*round(sqrt(3)/2),1,ls); generator(160+L div 2,getmaxy- 150-L*round(sqrt(3)/2),160, getmaxy-150,1,ls); setfillstyle(1,blue); floodfill(getmaxx div 2, getmaxy div 2, red); readln end.

if (n<ls) generator(x1,y1,div((2*x1+x2), 3).quot,div((2*y1+y2), 3).quot,n+1,ls); generator(div((2*x1+x2), 3).quot,div((2*y1+y2), 3).quot,x,y,n+1,ls); generator(x,y,div((x1+2*x2), 3).quot,div((y1+2*y2), 3).quot,n+1,ls); generator(div((x1+2*x2), 3).quot,div((y1+2*y2), 3).quot,x2,y2,n+1,ls); else desenez(x1,y1,x2,y2,x,y);

main() cout<<"ls= "; cin>>ls; init(); setcolor(6); L = getmaxx()-320; generator(160,getmaxy()-150, 160+L,getmaxy()-150,1,ls); generator(160+L,getmaxy()- 150,160+div(L,2).quot, getmaxy()-150- ceil(L*(sqrt(3)/2)),1,ls); generator(160+div(L,2).quot, getmaxy()-150- ceil(L*(sqrt(3)/2)),160, getmaxy()-150,1,ls); setfillstyle(1,4); floodfill(div(getmaxx(),2) .quot,div(getmaxx(), 2).quot,6); getch(); closegraph();

Priviţi mai jos rezultatele obţinute pentru diferite valori ale lui ls:

Figura 7.2. Exemple de fractali formaţi cu ajutorul curbei lui Koch (triunghi echilateral)

Page 191: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 191

7.3.3. Curba lui Koch pentru un pătrat

Se consideră un pătrat. Fiecare latură a sa se transformă după cum se vede în figura de mai jos:

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ă.

În final, figura se colorează. Programul este prezentat în continuare:

Varianta Pascal Varianta C++

uses graph,crt;

var gdriver,gmode,ls:integer;

procedure initg; ...

procedure rotplan(xc,yc,x1, y1:integer; var x,y:integer; unghi:real); ...

#include "graphics.h"

#include <iostream.h> #include <stdlib.h> #include <conio.h> #include <math.h>

int gdriver,gmode,ls,L;

void init() ...

Figura 7.3. Exemplu de transformare

Page 192: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

192 Capitolul 7. Metoda DIVIDE ET IMPERA

procedure desen(x1,y1,x2, y2,n,ls:integer); var x3,x4,x5,x6,x7,x8,xc, y3,y4,y5,y6,y7,y8,yc:integer; begin if n<=ls then begin x3:=(3*x1+x2) div 4; y3:=(3*y1+y2) div 4; rotplan(x3,y3,x1,y1,x4,y4, -pi/2); xc:=(x1+x2) div 2; yc:=(y1+y2) div 2; rotplan(xc,yc,x3,y3,x5,y5, -pi/2); rotplan(xc,yc,x3,y3, x6,y6,pi/2); x8:=(x1+3*x2) div 4; y8:=(y1+3*y2) div 4; rotplan(x8,y8,xc,yc,x7,y7, pi/2); desen(x1,y1,x3,y3,n+1,ls); desen(x3,y3,x4,y4,n+1,ls); desen(x4,y4,x5,y5,n+1,ls); desen(x5,y5,xc,yc,n+1,ls); desen(xc,yc,x6,y6,n+1,ls); desen(x6,y6,x7,y7,n+1,ls); desen(x7,y7,x8,y8,n+1,ls); desen(x8,y8,x2,y2,n+1,ls); if n = ls then begin moveto(x1,y1); lineto(x3,y3); lineto(x4,y4); lineto(x5,y5); lineto(x6,y6); lineto(x7,y7); lineto(x8,y8); lineto(x2,y2); end end end;

begin write('ls= '); readln(ls); initg; setcolor(red); desen(100,100,300,100,1,ls); desen(300,100,300,300,1,ls); desen(300,300,100,300,1,ls); desen(100,300,100,100,1,ls); setfillstyle(1,blue); floodfill(getmaxx div 2, getmaxy div 2, red); readln end.

void rotplan(int xc,int yc, int x1,int y1,int &x,int &y, float unghi) ...

void desen(int x1,int y1,int x2,int y2,int n,int ls) int x3,x4,x5,x6,x7,x8, xc,y3, y4,y5,y6,y7,y8,yc; if (n<=ls) x3=div(3*x1+x2,4).quot; y3=div(3*y1+y2, 4).quot; rotplan(x3,y3,x1,y1,x4,y4, -M_PI/2); xc=div(x1+x2,2).quot; yc=div(y1+y2,2).quot; rotplan(xc,yc,x3,y3,x5,y5, -M_PI/2); rotplan(xc,yc,x3,y3,x6,y6, M_PI/2); x8=div(x1+3*x2, 4).quot; y8=div(y1+3*y2,4).quot; rotplan(x8,y8,xc,yc,x7,y7, M_PI/2); desen(x1,y1,x3,y3,n+1,ls); desen(x3,y3,x4,y4,n+1,ls); desen(x4,y4,x5,y5,n+1,ls); desen(x5,y5,xc,yc,n+1,ls); desen(xc,yc,x6,y6,n+1,ls); desen(x6,y6,x7,y7,n+1,ls); desen(x7,y7,x8,y8,n+1,ls); desen(x8,y8,x2,y2,n+1,ls); if (n == ls) moveto(x1,y1); lineto(x3,y3); lineto(x4,y4); lineto(x5,y5); lineto(x6,y6); lineto(x7,y7); lineto(x8,y8); lineto(x2,y2);

main() cout<<"ls= "; cin>>ls; init(); setcolor(6); desen(100,100,300,100,1,ls); desen(300,100,300,300,1,ls); desen(300,300,100,300,1,ls); desen(100,300,100,100,1,ls); setfillstyle(1,3); floodfill(div(getmaxx(),2) .quot,div(getmaxy(),2).quot,6); getch(); closegraph();

Page 193: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 193

Sunt prezentate mai jos imaginile obţinute în urma rulării programului, pentru diferite valori ale lui ls:

7.3.4. Arborele

Se dă un segment AB. Cu ajutorul lui se construieşte un arbore, aşa cum se vede în figura de mai jos:

Lungimea fiecărei ramuri este o treime din lungimea iniţială a segmentului. Fiecare latură se transformă în mod asemănător. Se cere să se vizualizeze figura astfel rezultată, după ls transformări.

Pentru obţinerea ramurilor se procedează astfel:

• se consideră punctul situat pe dreapta determinată de segment şi

pentru care avem:

.2

331

3,

23

313

;3 12211221 yyyyy

xxxxx

CBCAk pc

−⋅=

−⋅−

=−⋅

=−⋅−

===

• se roteşte acest punct în jurul punctului B(x2,y2) cu un unghi de 4/π ;

• se roteşte punctul în jurul lui B cu unghiul 4/π− .

Figura 7.4. Exemple de fractali formaţi cu ajutorul curbei lui Koch (pătrat)

ls=1 ls=2 ls=3

Figura 7.5. Exemplu de transformare în cazul unui arbore

Page 194: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

194 Capitolul 7. Metoda DIVIDE ET IMPERA

În urma acestor rotaţii se obţin coordonatele punctelor care, împreună cu 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 trebuie 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:

Varianta Pascal Varianta C++

uses graph,crt;

var gdriver,gmode,ls:integer; xmax,ymax:integer;

procedure initg; ...

procedure rotplan(xc,yc,x1, y1:integer; var x,y:integer; unghi:real); ...

procedure desenez(x1,y1,x2,y2, n,ls:integer); var x,y:integer; begin if n<=ls then begin setcolor(1+random(15)); moveto(x1,y1); lineto(x2,y2); rotplan(x2,y2,(3*x2-x1) div 2,(3*y2-y1) div 2,x,y,pi/4); desenez(x2,y2,x,y,n+1,ls); rotplan(x2,y2,(3*x2-x1) div 2,(3*y2-y1) div 2,x,y,-pi/4); desenez(x2,y2,x,y,n+1,ls); end end;

begin randomize; write('ls= '); readln(ls); initg; setbkcolor(white); desenez(getmaxx div 2, getmaxy, getmaxx div 2, getmaxy-250,1,ls); readln end.

#include "graphics.h" #include <iostream.h> #include <stdlib.h> #include <conio.h> #include <math.h> int gdriver,gmode,ls,L;

void init() ...

void rotplan(...) ...

void desenez(int x1,int y1, int x2,int y2,int n,int ls) int x,y; if (n<=ls) setcolor(1+random(15)); moveto(x1,y1); lineto(x2,y2); rotplan(x2,y2,div(3*x2- x1,2).quot,div(3*y2-y1,2) .quot,x,y,M_PI/4); desenez(x2,y2,x,y,n+1,ls); rotplan(x2,y2,div(3*x2- x1,2).quot,div(3*y2-y1, 2).quot,x,y,-M_PI/4); desenez(x2,y2,x,y,n+1,ls);

main() randomize(); cout<<"ls= "; cin>>ls; init(); setcolor(6); desenez(div(getmaxx(),2) .quot,getmaxy(), div(getmaxx(),2).quot, getmaxy()-250,1,ls); getch(); closegraph();

Page 195: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 195

Pentru diverse valori ale parametrului de intrare ls, vom obţine arborii:

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 aplicaţ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şte un număr real x∈(-10000, 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 0.7. Nu se vor folosi funcţii specializate ale limbajului.

5. Se ştie că ecuaţia x3+x-1=0 are o singură rădăcină reală în intervalul (0,1). Scrieţi un program, care o afişează cu 4 zecimale exacte.

ls = 3 ls = 5 ls = 7

Figura 7.6. Exemple de fractali de tip arbore

Page 196: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

196 Capitolul 7. Metoda DIVIDE ET IMPERA

6. Problema selecţiei. Se consideră un vector cu n componente numere naturale şi 1≤t≤n. 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. Se consideră un vector care reţine n numere naturale. Se cere să se determine dacă există un element majoritar (adică un număr care se găseşte în mai mult de [ ] 12/ +n elemente).

Victor Mitrana

8. Fiind dat x real, să se calculeze [ ]3 x cu patru zecimale exacte! Nu se vor folosi funcţii 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 în 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ări (Covorul lui Sierpinski).

Răspunsuri

1. ln(a)=x ⇔ a=ex ⇔ ex-a=0. Dacă notăm cu f(x)=ex-a, atunci trebuie rezolvată ecuaţia f(x)=0. Avem f(0)=e0-a=1-a<0 şi f(a)=ea-a>0. De aici, rezultă că f(x) are o rădăcină în intervalul (0,a). Cum f(x) este strict crescătoare (ca diferenţă între funcţia strict crescătoare ex şi o constantă), rădăcina este unică. Algoritmul pe care îl folosim se numeşte în matematică “metoda înjumătăţirii intervalului”, dar, din punct de vedere informatic, corespunde metodei DIVIDE ET IMPERA.

Fie li=0 şi ls=a, m=(a+b)/2. Dacă f(li)×f(m)<0, rădăcina se găseşte în (li,m), altfel rădăcina este în [m,ls). Condiţia de terminare este ca

0.0001lsli <− , pentru că trebuie să avem 3 zecimale exacte.

Varianta Pascal Varianta C++

var a:real;

function LogN(a,li,ls:double): double; begin if a=1 then LogN:=0 else if abs(li-ls)<0.0001 then LogN:=(li+ls)/2 else if (exp(li)-a)* (exp((li+ls)/2)-a)<0 then LogN:=LogN(a,li,(li+ls)/2) else LogN:=LogN(a,(li+ls)/2,ls) end;

#include <iostream.h> #include <math.h> double a; double LogN(double a,double li, double ls) if (a==1) return 0; else if (fabs(li-ls)<0.0001) return (li+ls)/2; else if ((exp(li)-a)* (exp((li+ls)/2)-a)<0) return LogN(a,li, (li+ls)/2); else return LogN(a, (li+ls)/2,ls);

Page 197: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 197

begin write ('a='); readln(a); writeln(' rezultat calculat:',LogN(a,0,a):3:3); writeln(' rezultat preluat ', ln(a):3:3); end.

main() cout<<"a="; cin>>a; cout<<"rezultat calculat " <<LogN(a,0,a)<<endl; cout<<"rezultat preluat " <<log(a)<<endl;

Practic, la fiecare pas se înjumă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 Varianta C++

type vector=array[1..9] of integer;

var v:vector; n,i:integer;

function Suma(li,ls:integer):integer;

begin if li=ls then Suma:=v[li] else Suma:=Suma(li,(li+ls) div 2) + Suma((li+ls) div 2+1,ls); end;

begin write('n='); readln(n); for i:=1 to n do readln(v[i]); writeln(suma(1,n)); end.

#include <iostream.h>

int n,i,v[10]; int Suma(int li, int ls) if (li==ls) return v[li]; else return Suma(li, (li+ls)/2)+ Suma((li+ls)/2+1,ls);

main() cout<<"n="; cin>>n; for (i=1;i<=n;i++) cin>>v[i]; cout<<Suma(1,n);

3. Fiecare problemă se descompune în alte două şi rezultatul se adună. Pentru simplitate, consideraţi n=2k. În 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=2k:

+

== altfel.1

2n2T

1;n0T(n)

4. A calcula [x] se reduce la DIVIDE ET IMPERA. Partea fracţionară se obţine uşor, dacă calculăm [ ]xx − .

Page 198: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

198 Capitolul 7. Metoda DIVIDE ET IMPERA

5. Vedeţi problema 1. 6. Funcţia Poz returnează poziţia 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[k] şi toate elementele de indice mai mare decât k sunt mai mari sau egale decât A[k]. Altfel spus: elementul A[1], 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 rezolvată. Dacă t<k, elementul căutat are indicele cuprins între li şi k-1 şi reluăm rularea funcţiei Poz între aceste limite, iar dacă t>k, elementul căutat are indicele între k+1 şi ls şi reluăm rularea funcţ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:

Varianta Pascal Varianta C++

li:=1; ls:=n; repeat poz(li,ls,k,a); if t<k then ls:=k-1; if t>k then li:=k+1; until t=k; writeln('Elementul cautat ', a[t]);

li=1; ls=n; do poz(li,ls,k,a); if (t<k) ls=k-1; if (t>k) li=k+1; while (t!=k); cout<<"elementul cautat " <<a[t];

7. După aplicarea algoritmului de la problema anterioară, elementul din mijloc trebuie să fie majoritar.

Page 199: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

199

Capitolul 8

Metoda BACKTRACKING

8.1. Prezentarea metodei

8.1.1. Când se utilizează metoda backtracking ?

Metoda backtracking se foloseşte în rezolvarea problemelor care îndeplinesc simultan următoarele condiţii:

soluţia lor poate fi pusă sub forma unui vector S=x1,x2,...,xn, cu x1∈A1, x2∈A2, ..., xn∈An;

mulţimile A1, 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 rezolvat prin utilizarea tehnicii backtracking.

Generarea permutărilor. Se citeşte un număr natural n. Să se genereze toate permutările mulţimii 1,2,...,n. De exemplu, pentru n=3, permutările mulţimii 1,2,3 sunt prezentate alăturat. Pentru această problemă, A1=A2=A3=1,2,3. Fie permutarea 213. Ea este scrisă sub formă de vector, unde 2∈A1, 1∈A2 şi 3∈A3.

8.1.2. Principiul care stă la baza metodei backtracking

Principiul care stă la baza metodei backtracking va fi prezentat printr-un exemplu, acela al generării permutărilor. Cum se poate rezolva această problemă?

O primă soluţie ar fi să generăm toate elementele produsului cartezian: 1,2,3×1,2,3×1,2,3=11, 12, 13, 21, 22, 23, 31, 32, 33 ×1,2,3= 111, 112, 113, 121, 122, 123, 131, 132, 133, 211, 212, 213, 221, 222, 223, 231, 232, 233, 311, 312, 313, 321, 322, 323, 331, 332, 333.

123 132 213 231 312 321

Page 200: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

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. În general, produsul cartezian are nn elemente, din care permutări sunt doar n!. Vă daţi seama că un astfel de algoritm de generare a permutărilor este ineficient…

Întrebarea 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 obţine o permutare, oricare ar fi numărul care urmează pe poziţia 3. Principiul metodei Metoda backtracking are la bază un principiu simplu: dacă în procesul de

generare a unui vector soluţie S=x1x2,...,xn, pentru componenta k, atunci când am generat deja x1x2...xk, constatăm că valoarea xk nu este bine aleasă (păstrând-o nu se va ajunge la o soluţie), nu trecem componenta k+1 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ărilor, î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ărul 1. Nu există permutări care încep cu 1,1, motiv pentru care,

pentru aceeaşi componentă, vom reţine valoarea următoare, adică 2. Întrucât există permutări care încep cu 1,2, vom trece la elementul 3 (înainte).

Componenta 3 va memora numărul 1. 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. Nu există permutări care sunt de forma 1,2,2, motiv pentru

care aceeaşi componentă va memora numărul următor, adică 3. Am obţinut deja o primă soluţie şi o afişăm.

1

1 1

1 2

1 2 1

1 2 2

1 2 3

Page 201: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 201

Pentru componenta 3, nu există o altă valoare pe care o 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 încep 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 0. Î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 0.

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 întotdeauna acelaşi nume şi parametri şi care, din punct de vedere al metodei, realizează acelaşi lucru. Sarcina celui care face programul este să scrie explicit, pentru fiecare problemă în 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.

Iată subprogramul care implementează metoda. El va fi apelat prin

back(1).

Varianta Pascal Varianta C++

procedure back(k:integer); begin if solutie(k) then tipar else begin init(k); while succesor(k) do if valid(k) then back(k+1) end end;

void back(int k) if (solutie(k)) tipar(); else init(k); while(succesor(k)) if (valid(k)) back(k+1);

1 3 0

1 3 2

Page 202: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

202 Capitolul 8. Metoda backtracking

Să-l 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, subprogramul 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 întâmplă atunci când s-a ajuns pe nivelul n+1. Dacă s-a obţinut o soluţie, aceasta se afişează. Pentru această operaţie se

va utiliza subprogramul tipar. În situaţia în 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 0. După iniţializare, se generează, pe rând, toate valorile mulţimii Ak. 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şte anumite condiţii de continuare. Acest test este realizat de subprogramul valid:

în cazul în care condiţiile sunt îndeplinite, se trece la componenta k+1, 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 Ak se trece, implicit, la componenta k-1, iar algoritmul se încheie când k=0.

Pentru permutări, pe fiecare nivel, valorile posibile sunt Ak=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.

Page 203: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 203

Varianta Pascal Varianta C++

var n:integer; sol:array [1..10] of integer;

procedure init(k:integer); begin sol[k]:=0 end;

function succesor (k:integer):boolean; begin if sol[k]<n then begin sol[k]:=sol[k]+1; succesor:=true end else succesor:=false end;

function valid (k:integer):boolean; var i:integer; begin valid:=true; for i:=1 to k-1 do if sol[i]=sol[k] then valid:=false end;

function solutie (k:integer):boolean; begin solutie:=(k=n+1) end;

procedure tipar; var i:integer; begin for i:=1 to n do write(sol[i]); writeln end;

procedure back(k:integer); begin if solutie(k) then tipar else begin init(k); while succesor(k) do if valid(k) then back(k+1) end end;

begin write('n='); readln(n); back(1) end.

#include <iostream.h> #include <iostream.h>

int n, sol[10];

void init(int k) sol[k]=0;

int succesor(int k) if (sol[k]<n) sol[k]++; return 1; else return 0;

int valid(int k) int i, ev=1; for (i=1;i<=k-1;i++) if (sol[k]==sol[i]) ev=0; return ev;

int solutie(int k) return k==n+1;

void tipar() for (int i=1;i<=n;i++) cout<<sol[i]; cout<<endl;

void back(int k) if (solutie(k)) tipar(); else init(k); while(succesor(k)) if (valid(k)) back(k+1);

main() cout<<"n="; cin>>n; back(1);

Page 204: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

204 Capitolul 8. Metoda backtracking

8.1.4. Problema celor n dame

Enunţ. Fiind dată o tablă de şah cu dimensiunea n×n, 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ţiei este prezentat în figurile următoare, de la b) la i):

Figura 8.1. Exemplu pentru n=4

a) b) c)

d) e) f)

g) h) i)

Page 205: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 205

sol(1) = 1 i = 1 sol(3) = 3 j = 3 |sol(1) - sol(3)| = |1 - 3| = 2 |i - j| = |1 - 3| = 2

Comentarii referitoare la figurile anterioare

b) Observăm că o damă trebuie să fie plasată singură pe linie. Poziţionăm prima damă pe linia 1, coloana 1.

c) A doua damă nu poate fi aşezată decât î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) În această situaţie dama a patra nu mai poate fi aşezată. Încercâ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ă în coloana a-2-a.

g) A doua damă nu poate fi aşezată decât în 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ţinut o soluţie a problemei.

Algoritmul continuă în 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 în vedere că pe fiecare linie se găseşte o singură damă). Prin sol[i] înţelegem coloana în care se găseşte dama de pe linia i.

Alăturat, puteţi observa modul în care este reprezentată soluţia cu ajutorul vectorului sol.

Două dame se găsesc pe aceeaşi diagonală dacă şi numai dacă este

îndeplinită condiţia: |sol(i)-sol(j)|=|i-j|

(diferenţa, în modul, dintre linii şi coloane este aceeaşi). Exemple: a)

2 4 1 3

Figura 8.2.

Page 206: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

206 Capitolul 8. Metoda backtracking

sol(1) = 3 i = 1 sol(3) = 1 j = 3 |sol(i) - sol(j)| = |3 - 1| = 2 |i - j| = |1 - 3| = 2

b)

Întrucât 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 în 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 soluţiilor 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 în locul subprogramului cu acelaşi nume din programul de generare a permutărilor, veţi obţine programul care rezolvă problema celor n dame.

Varianta Pascal Varianta C++

function valid(k:integer): boolean;

var i:integer;

begin valid:=true; for i:=1 to k-1 do if (sol[k]=sol[i]) or (abs(sol[k]-sol[i])=abs(k-i)) then valid:=false end;

int valid(int k) for (int i=1;i<k;i++) if (sol[k]==sol[i] || abs(sol[k]-sol[i])==abs(k-i)) return 0; return 1;

Problema este un exemplu folosit în mai toate lucrările în care este

prezentată metoda backtracking.

În ceea ce ne priveşte, dincolo de un exemplu de backtracking, am avut ocazia să vedem cât 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,5) şi verificaţi dacă aceasta reprezintă o soluţie a problemei damelor.

Figura 8.3.

Page 207: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 207

2. Explicaţi de ce configuraţia corespunzătoare vecorului sol=(3,1,3,4,5) nu este 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 1,2, …, n este o soluţie a problemei 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ă

Până î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 întotdeauna necesar să-l 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ă încorporare 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 0.

Putem elimina subprogramul solutie. În locul apelului vom testa dacă k este egal cu n+1.

Putem elimina subprogramul tipar. În locul apelului vom scrie secvenţa prin care se afişează st.

Putem elimina subprogramul succesor. În locul apelului vom testa dacă st[k]<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ămâne aceeaşi. Subprogramele prezentate au fost numai încorporate, nu s-a renunţat la ele.

Page 208: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

208 Capitolul 8. Metoda backtracking

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:

Varianta Pascal Varianta C++

var sol:array[1..9] of integer; n:integer; function valid(k:integer):boolean;

var i:integer;

begin valid:=true; for i:=1 to k-1 do if (sol[k]=sol[i]) or (abs(sol[k]-sol[i])=abs(k-i)) then valid:=false end; procedure back(k:integer);

var i:integer;

begin if k=n+1 solutie then begin tipar for i:=1 to n do write(sol[i]); writeln; end else begin sol[k]:=0; init while sol[k]<n do succesor begin sol[k]:=sol[k]+1; if valid(k) then back(k+1) end end end;

begin write('n='); readln(n); back(1) end.

#include <iostream.h> #include <math.h> int n, sol[10];

int valid(int k) for (int i=1;i<k;i++) if (sol[k]==sol[i] || abs(sol[k]-sol[i]) ==abs(k-i)) return 0; return 1; void back(int k) if (k==n+1) // solutie //tipar for (int i=1;i<=n;i++) cout<<sol[i]; cout<<endl; else sol[k]=0; while(sol[k]<n) // succesor sol[k]++; if (valid(k))back(k+1);

main() cout<<"n="; cin>>n; back(1);

Page 209: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 209

Varianta Pascal Varianta C++

procedure back(k:integer); var i:integer; begin if k=n+1 then begin for i:=1 to n do write(sol[i]); writeln; end else for i:=1 to n do begin sol[k]:=i; if valid(k) then back(k+1) end end;

void back(int k) int i; if (k==n+1) for (i=1;i<=n;i++) cout<<sol[i]; cout<<endl; else for (i=1;i<=n;i++) sol[k]=i; if (valid(k)) back(k+1);

Exerciţii 1. Testaţi subprogramul 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 alăturate.

3. Observaţi că ordinea de afişare a soluţiilor depinde de ordinea în care se consideră elementele mulţimilor A1, A2, … Ce modificări trebuie aduse procedurii recursive back astfel încât permutarile de 4 elemente să fie afişate în ordinea: 4321, 4312, 4231, 4213, 4132, 4123, 3421, 3412 … 1243, 1234?

4. Renunţaţi la utilizarea subprogramului valid, utilizând un vector folosit, în care folosit[i] are valoarea 0 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[k]i) trebuie însoţită de memorarea faptului că i este utilizat (folosit[i]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 soluţie (folosit[i]0). Condiţia de validare se reduce în acest caz la:

Dacă folosit[i]=0 atunci ...

5. Urmăriţi toate modalităţile diferite de a aşeza patru obiecte identificate prin 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ţie. Scrieţi un program care afişează numai permutările distincte conform aşezării pe un cerc. Indicaţie: se va considera sol[1]=1 şi se vor permuta doar celelalte elemente.

Page 210: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

210 Capitolul 8. Metoda backtracking

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 în care se cere o singură soluţie.

Implementarea ”ca la carte“ presupune utilizarea unei variabile de

semnalizare (de exemplu, variabila gata) care să fie iniţial 0, la obţinerea soluţiei dorite aceasta primind valoarea 1. Orice succesor va fi condiţionat în plus de valoarea variabilei gata.

De această dată, pentru simplitate, vom renunţa la programarea structurată

ş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 să includeţi fişierul antet “stdlib.h“:

#include<stdlib.h>.

În ambele cazuri, secvenţa care determină oprirea forţată este trecută 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. Problema colorării hărţilor. 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 încât 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; • ţara 5 - culoarea 4.

3

1

2

4

5 Figura 8.4.

Page 211: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 211

ţara i are frontiera comună cu ţara j altfel

Harta este furnizată programului cu ajutorul unei matrice (tablou) An,n:

=0,1,

j)A(i,

Matricea A este simetrică. Pentru rezolvarea problemei se utilizează

vectorul sol, unde sol[k] reţine culoarea ataşată ţării k. Evident, orice soluţie are exact n componente.

Varianta Pascal Varianta C++

var sol:array[1..9] of integer; a:array[1..10,1..10] of integer; n,i,j:integer;

function valid(k:integer):boolean; begin valid:=true; for i:=1 to k-1 do if (sol[k]=sol[i]) and (a[k,i]=1) then valid:=false end;

procedure back(k:integer); var i:integer; begin if k=n+1 then begin for j:=1 to n do write(sol[j]); halt; end else for i:=1 to n do begin sol[k]:=i; if valid(k) then back(k+1) end; end;

begin write('Numarul de tari='); readln(n); for i:=1 to n do for j:=1 to i-1 do begin write('a[',i,',',j,']='); readln(a[i,j]); a[j,i]:=a[i,j] end; back(1) end.

#include <iostream.h> #include <stdlib.h> int n,i,j,sol[10],a[10][10];

int valid(int k) for (int i=1;i<k;i++) if (sol[k]==sol[i] && a[i][k]==1) return 0; return 1;

void back(int k) int i; if (k==n+1) for (int i=1;i<=n;i++) cout<<sol[i]; exit(EXIT_SUCCESS); else for (i=1;i<=4;i++) sol[k]=i; if (valid(k)) back(k+1);

main() cout<<"Numarul de tari="; cin>>n; for (int i=1;i<=n;i++) for (int j=1;j<=i-1;j++) cout<<"a["<<I <<','<<j<<"]="; cin>>a[i][j]; a[j][i]=a[i][j]; back(1);

Page 212: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

212 Capitolul 8. Metoda backtracking

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 în care orice ţară se învecinează cu cel 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 învăţ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 modurile posibile.

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, bca, cab şi cba.

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=3, iar persoanele sunt Ioana, Costel şi Mihaela, atunci soluţiile sunt:

Ioana Costel Mihaela; Ioana Mihaela Costel; Costel Ioana Mihaela; ...

Figura 8.5.

Page 213: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 213

În astfel de cazuri, cele n elemente distincte se memorează într-un vector V, aşa cum vedeţi mai jos:

Atunci când s-a generat o permutare, de exemplu 213, vom afişa V[2]V[1]V[3], adică bac, în primul caz sau Costel Ioana Mihaela, în al doilea caz.

Procedeul de mai sus poate fi folosit pentru oricare altă aplicaţie din combinatorică, în probleme cum ar fi: generarea tuturor submulţimilor unei mulţimi, generarea aranjamentelor, a combinărilor sau a tuturor părţilor unei mulţimi.

Exerciţiu. Scrieţi programul care rezolvă exemplul 2.

Mulţimea permutărilor mulţimii 1,2,...n reprezintă toate funcţiile bijective f:1,2,...,n→1,2,...,n. De exemplu, dacă n=3, permutarea 213 este funcţia f:1,2,3→1,2,3 definită astfel: f(1)=2; f(2)=1; f(3)=3.

8.4.2. Produs cartezian

Enunţ. Se dau n mulţimi: A1, A2,... An, unde Ai=1,2,...,ki, pentru k=1,2,...,n. Se cere produsul cartezian al celor n mulţimi.

Exemplu: A1=1,2, A2=1,2,3, A3=1,2,3.

A1×A2×A3=(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),(2,2,3),(2,3,1),(2,3,2),(2,3,3).

Rezolvare. De la început observăm că este necesar să afişăm toate soluţiile. Să observăm că o soluţie este de forma x1,x2,...,xn, cu x1∈A1, x2∈A2, ..., xn∈An. De aici rezultă necesitatea folosirii unui vector sol, cu n componente, unde sol[1] va conţine numerele naturale între 1 şi k1, sol[2] va conţine numerele naturale între 1 şi k2, ..., sol[n] va conţine numerele naturale între 1 şi kn. Observăm că valorile care pot fi luate sol[1] sunt între 1 şi k1, valorile care pot fi luate sol[2] sunt între 1 şi k2, ..., valorile care pot fi luate sol[n] sunt între 1 şi kn. Pentru a putea reţine aceste valori, vom utiliza un vector a, cu n componente, unde a[1]= k1, a[2]= k2, …, a[n]= kn. Pentru exemplul dat, vectorul a va reţine (2,3,3).

Important! Să observăm că orice valoare reţinută de sol[i] între 1 şi ki îndeplineşte condiţiile de continuare (este validă). Din acest motiv, nu mai este necesar să utilizăm subprogramul valid.

3 2 1

a b c

3 2 1

Ioana Costel Mihaela

Page 214: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

214 Capitolul 8. Metoda backtracking

Mai jos, puteţi observa programul care generează produsul cartezian al mulţimilor date:

Varianta Pascal Varianta C++

var n,i:integer; sol,a:array[1..10] of integer;

procedure back(k:integer); begin if k=n+1 then begin for i:=1 to n do write(sol[i]); writeln end

else begin sol[k]:=0; while sol[k]<a[k] do begin sol[k]:=sol[k]+1; back(k+1) end end end;

begin write('Numarul de multimi='); readln(n); for i:=1 to n do begin write('a[',i,']='); readln(a[i]) end; back(1) end.

#include <iostream.h> int n, sol[10],a[10],i; void back(int k) if (k==n+1) for (i=1;i<=n;i++) cout<<sol[i]; cout<<endl; else sol[k]=0; while(sol[k]<a[k]) sol[k]++; back(k+1);

main() cout<<"Numarul de multimi="; cin>>n; for(int i=1;i<=n;i++) cout<<"a["<<i<<"]="; cin>>a[i]; back(1);

Observaţii

1. Avem k1×k2×...×kn elemente ale produsului cartezian. De aici rezultă că algoritmul este exponenţial.

2. O altă interpretare pentru metoda backtracking: fiind date n mulţimi: A1, A2, ..., An, produsul cartezian al lor A1×A2×...×An 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 îndeplinesc 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.

Page 215: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 215

Deşi algoritmul este exponenţial, există aplicaţii utile, evident, atunci când fiecare mulţime Ai poate lua numai câteva valori şi unde n este suficient de mic.

Exerciţii

1. Tabelarea anumitor funcţii. Se dă funcţia

f:A1×A2×...×An→R,

unde fiecare mulţime Ai este dată de numerele întregi din intervalul [ai,bi] şi

f=c1x1+c2x2+...+cnxn, ci∈R.

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 prima şi ultima literă vocale, litera a doua consoană din mulţimea P, R, S, T, iar a treia literă consoană din mulţimea B, M, R, T, V.

3. Scrieţi programul care generează şi numără câte 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

Enunţ. Fiind dată mulţimea A=1,2,...,n, se cere să se afişeze toate submulţimile ei.

Rezolvare. Să ne amintim că submulţimile unei mulţimi A se pot reprezenta prin vectorul caracteristic V, unde:

∉∈

=Aipentru0,Aipentru1,

V[i]

De exemplu, dacă A=1,2,3, pentru submulţimea 1,3 vom avea V=(1,0,1). De aici, rezultă că problema se reduce la generarea tuturor valorilor posibile pe care le poate reţine vectorul caracteristic.

Aceasta înseamnă că o soluţie este de forma x1,x2,...,xn, unde xi∈0,1. Şi în acest caz, orice valoare ar reţine componenta i, ea nu trebuie să îndeplinească nici o condiţie de continuare, motiv pentru care subprogramul valid nu este necesar.

În continuare, puteţi observa programul care generează toate valorile pe care le poate reţine vectorul caracteristic:

Page 216: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

216 Capitolul 8. Metoda backtracking

Varianta Pascal Varianta C++

var n,i:integer; sol:array[1..10] of integer;

procedure back (k:integer); begin if k=n+1 then begin for i:=1 to n do write(sol[i]); writeln end else begin sol[k]:=-1; while sol[k]<1 do begin sol[k]:=sol[k]+1; back(k+1) end end end;

begin write('n='); readln(n); back(1) end.

#include <iostream.h> int n, sol[10],i;

void back(int k) if (k==n+1) for (i=1;i<=n;i++) cout<<sol[i]; cout<<endl; else sol[k]=-1; while(sol[k]<1) sol[k]++; back(k+1);

main() cout<<"n="; cin>>n; back(1);

Exerciţii 1. Problema nu este rezolvată în totalitate. Programul afişează numai toate valorile pe care le poate lua vectorul caracteristic. Completaţi-l astfel încât programul să afişeze toate submulţimile mulţimii 1,2...n! 2. Se citesc numele a 6 elevi. Afişaţi toate submulţimile mulţimii celor 6 elevi. 3. Să se afişeze toate numerele scrise în baza 10 a căror reprezentare în baza 2 are n cifre, dintre care exact k sunt egale cu 1. Valorile n şi k se citesc de la tastatură (n<12, k<n). De exemplu, pentru n=3 şi k=2, se obţin valorile: 5 şi 6. 4. Realizaţi un program care generează combinaţii de n cifre 0 şi 1 cu proprietatea că în orice grup de 3 cifre consecutive există cel puţin o cifră de 1. De exemplu, dacă n=4, se afişează combinaţiile: 0010, 0011, 0100, 0101, 0110, 0111, 1001, 1010, 1011, 1100, 1101, 1110, 1111. 5. Se citesc două numere naturale n şi s (n<10, s<1000). Să se afişeze mulţimile formate din n numere prime cu proprietatea că suma elementelor din fiecare mulţime este exact s.

Page 217: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 217

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 date! De ce? Fiecare componentă a vectorului caracteristic poate reţine două valori. Prin urmare, numărul de submulţimi este

n

orinde

22...222 =⋅⋅ .

De aici rezultă că algoritmul care generează toate submulţimile mulţimii 1, 2, ..., n este exponenţial.

Uneori, veţi rezolva probleme în care se dă o mulţime şi se cere o submulţime a sa care îndeplineşte anumite caracteristici. În 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 îndeplineş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ăcând parte din submulţime numai numerele pozitive. Altfel, dacă am genera toate submulţimile...

8.4.4. Generarea combinărilor

Fiind dată mulţimea A=1,2,...,n, se cer toate submulţimile ei cu p elemente. Problema este cunoscută sub numele de “generarea combinărilor de n, luate câte p”. Se ştie că numărul soluţiilor acestei probleme este

p!p)!(nn!Cp

n −= .

De exemplu, dacă n=4 şi p=3, soluţiile sunt următoarele:

1,2,3, 1,2,4, 1,3,4 şi 2,3,4. Enunţ. Se citesc n şi p numere naturale, n≥p. Se cere să se genereze toate submulţimile cu p elemente ale mulţimii A=1,2,...,n. Rezolvare. O soluţie este de forma x1,x2,...,xp, unde x1, x2, ..., xp∈A. În plus, x1, x2, ..., xp trebuie să fie distincte. Cum la o mulţime ordinea elementelor nu prezintă importanţă, putem genera elementele ei în ordine strict crescătoare. Această observaţie ne ajută foarte mult în elaborarea algoritmului.

a) Pentru k>1, sol[k]>sol[k-1].

Page 218: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

218 Capitolul 8. Metoda backtracking

b) Pentru fiecare k∈1,2,...,p, sol[k]≤n-p+k. Să presupunem, prin absurd, că această ultimă relaţie nu este respectată. Aceasta înseamnă că ∃k, astfel încât sol[k]>n-p+k. Deci:

sol[k+1]>n-p+k+1, ... sol[p]>n-p+p=n.

Absurd. De aici rezultă că:

1≤sol[1]≤n-p+1, sol[1]<sol[2]≤n-p+2, ... sol[n-1]<sol[n]≤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.

Varianta Pascal Varianta C++

var sol:array[1..9] of integer; n,p:integer;

procedure back(k:integer); var i:integer; begin if k=p+1 then begin for i:=1 to p do write(sol[i]); writeln; end else begin if k>1 then sol[k]:=sol[k-1] else sol[k]:=0; while sol[k]<n-p+k do begin sol[k]:=sol[k]+1; back(k+1); end end end;

begin write('n='); readln(n); write ('p='); readln(p); back(1); end.

#include <iostream.h> int n,p,sol[10];

void back(int k) int i; if (k==p+1) for (i=1;i<=p;i++) cout<<sol[i]; cout<<endl; else if (k>1) sol[k]=sol[k-1]; else sol[k]=0; while(sol[k]<n-p+k) sol[k]++; back(k+1);

main() cout<<"n="; cin>>n; cout<<"p="; cin>>p; back(1);

Examinând raţionamentul propus putem observa că, în anumite cazuri, analiza unei probleme conduce la un algoritm cu mult mai rapid.

Page 219: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 219

Exerciţii 1. Se dau coordonatele din plan a n puncte. Afişaţi coordonatele vârfurilor tuturor pătratelor care au ca vârfuri puncte din mulţimea considerată. 2. Se dau n substanţe chimice. Se ştie că, în anumite condiţii, unele substanţe intră în reacţii chimice cu altele. Fiind date p perechi de forma (i,j) cu semnificaţia că substanţa i intră în reacţie cu substanţa j, se cer toate grupurile de s<n substanţe astfel încât oricare două substanţe din grup nu intră în reacţie.

8.4.5. Generarea aranjamentelor

Se dau două mulţimi A=1,2,...,p şi B=1,2,...,n. Se cer toate funcţiile injective definite pe A cu valori în B. O astfel de problemă este una de generare a aranjamentelor de n luate câte p ( p

nA ).

Exemplu: p=2, n=3. Avem: 12, 21, 13, 31, 23, 32. De exemplu, 21 este funcţia f:A→B dată astfel: f(1)=2; f(2)=1. Avem relaţiile:

)1p1)...(nn(np)!(n

n!Apn +−−=

−= .

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 mulţ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ă observaţie, suntem tentaţi să generăm toate submulţimile cu p elemente ale mulţimii cu n elemente şi, din fiecare astfel de submulţime, să obţinem permutările ei. Exerciţiu!

Pe de altă parte, se poate lucra mult mai eficient. O soluţie este de forma: x1x2...xp, unde x1, x2, ..., xp∈B. În plus, x1, x2, ..., 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ândul lor, 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ţia are p numere, nu n ca în cazul permutărilor.

Page 220: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

220 Capitolul 8. Metoda backtracking

Varianta Pascal Varianta C++

var sol:array[1..9]of integer; n,p:integer;

function valid(k:integer):boolean; var i:integer; begin valid:=true; for i:=1 to k-1 do if sol[k]=sol[i] then valid:=false end;

procedure back(k:integer); var i,j:integer; begin if k=p+1 then begin for j:=1 to p do write(sol[j]); writeln end else for i:=1 to n do begin sol[k]:=i; if valid(k) then back(k+1) end end;

begin readln(n); readln(p); back(1) end.

#include <iostream.h> int n,p,sol[10];

int valid(int k) for (int i=1;i<k;i++) if (sol[k]==sol[i]) return 0; return 1;

void back(int k) int i,j; if (k==p+1) for (j=1;j<=p;j++) cout<<sol[j]; cout<<endl; else for (i=1;i<=n;i++) sol[k]=i; if (valid(k)) back(k+1);

main() cin>>n; cin>>p; back(1);

Exerciţii 1. Se citesc n, p şi apoi n litere distincte. Afişaţi toate cuvintele care se pot forma cu p dintre ele. 2. Se citesc n şi apoi numele mici a n persoane. Ştiind că toate numele care se termină cu a reprezintă nume de fată, celelalte fiind nume de băieţi, să se afişeze toate mulţimile de perechi fată-băiat care se pot forma. Două mulţimi sunt distincte dacă cel puţin una dintre perechi diferă. De exemplu, pentru n=5, Maria, Ana, Doina, Doru, Cosmin, se afişează mulţimile: Maria-Doru, Ana-Cosmin, Ana-Cosmin, Maria-Doru, Maria-Doru, Doina-Cosmin, Doina-Doru, Maria-Cosmin, Ana-Doru, Doina-Cosmin, Doina-Doru, Ana-Cosmin.

Page 221: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 221

3. Cei n acţionari ai unei firme trebuie să organizeze un număr maxim de şedinţe tip masă rotundă la 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=4 şi p=3, atunci sunt posibile 5 configuraţii diferite ale celor 3 acţionari aşezaţi la masa rotundă: 1-2-3; 1-3-2; 1-3-4; 1-4-3; 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 k≤n mulţimi care îndeplinesc condiţiile de mai jos:

a) A1∪A2∪...∪Ak=A;

b) Ai∩Aj=∅, ∀i≠j∈1,2...n. 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 Enunţ. Se citeşte un număr natural, n. Se cer toate partiţiile mulţimii A=1,2,...,n.

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[i]=k, atunci elementul i se găseşte în mulţimea k a partiţiei. Totuşi, nu ştim câte mulţimi sunt în 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[i] va lua valori între 1 şi

1+maxsol[1], sol[2], ..., sol[i-1].

Page 222: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

222 Capitolul 8. Metoda backtracking

Prin această condiţie se evită situaţia în care, de exemplu, vectorul sol reţine (1,3,1). Aceasta ar avea semnificaţia că elementele 1 şi 3 se găsesc în submulţimea 1 a partiţiei, iar elementul 2 se găseşte în submulţimea 3 a partiţiei. În acest caz, lipseşte submulţimea 2 a partiţiei.

Să exemplificăm funcţionarea algoritmului pentru cazul n=3:

- sol=(1,1,1) - A1=1,2,3);

- sol=(1,1,2) - A1=1,2 A2=3;

- sol=(1,2,1) - A1=1,3 A2=2;

- sol=(1,2,2) - A1=1 A2=2,3;

- sol=(1,2,3) - A1=1 A2=2 A3=3.

Să observăm că nici în cazul acestei probleme nu trebuie să verificăm existenţa anumitor condiţii de continuare.

Analizaţi programul astfel obţinut!

Varianta Pascal Varianta C++

var sol:array[0..10]of integer; n,i,j,maxim:integer; procedure tipar; begin maxim:=1; for i:=2 to n do if maxim<sol[i] then maxim:=sol[i]; writeln('Partitie '); for i:=1 to maxim do begin for j:=1 to n do if sol[j]=i then write (j,' '); writeln; end; end; procedure back (k:integer); var i,j,maxprec:integer; begin if k=n+1 then tipar else begin maxprec:=0; for j:=1 to k-1 do if maxprec<sol[j] then maxprec:=sol[j];

#include <iostream.h> int n, sol[10], max[10],i,j,maxim;

void tipar() maxim=1; for (i=2;i<=n;i++) if (maxim<sol[i]) maxim=sol[i]; cout<<"Partitie "<<endl; for (i=1;i<=maxim;i++) for (j=1; j<=n;j++) if (sol[j]==i) cout<<j<<" "; cout<<endl;

void back(int k) int i,j,maxprec; if (k==n+1) tipar(); else maxprec=0; for (j=1;j<=k-1;j++) if (maxprec<sol[j]) maxprec=sol[j]; for (i=1;i<=maxprec+1;i++) sol[k]=i; max[k]=sol[k]; back(k+1);

Page 223: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 223

for i:=1 to maxprec+1 do begin sol[k]:=i; back(k+1) end; end end; begin write('n='); readln(n); back(1); end.

main() cout<<"n="; cin>>n; back(1);

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 vectorului sol şi oricărui conţinut al vectorului sol îi 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 rezolvă prin utilizarea metodei backtracking

8.5.1. Generalităţi

Toate problemele pe care le-am întâlnit până acum admit soluţii 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.

Page 224: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

224 Capitolul 8. Metoda backtracking

Exemple. Fie mulţimea A=1,2...n. Atunci:

a) Toate permutările mulţimii A au lungimea n.

b) Toate submulţimile cu p elemente ale mulţimii A (generarea combinărilor) au lungimea p.

c) Toate soluţiile sub formă de vector ale problemei generării tuturor partiţiilor mulţimii A au lungimea n.

În 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

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 n=4, avem: 4, 31, 22, 211, 13, 121, 112, 1111.

Ordinea numerelor din sumă este importantă. Astfel, se tipăreşte 112 dar şi 211, 121.

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.

Trecem 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[1]+sol[2]+...sol[k]≤n.

3. Avem soluţie atunci când

sol[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].

Page 225: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-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 permanenţă într-o variabilă globală, numită s.

Mai jos, este prezentată funcţionarea algoritmului pentru n=4:

Observaţi modul în care calculăm suma la fiecare pas. De câte ori se trece la componenta următoare (k+1), la s se adună sol[k], de câte ori se face pasul înapoi (se trece la componenta k-1), din s se scade sol[k].

Programul este prezentat în continuare:

Varianta Pascal Varianta C++

var sol:array[1..100] of integer; n,i,s:integer;

procedure back (k:integer); begin if s=n then begin for i:=1 to k-1 do write(sol[i]); writeln; end else begin sol[k]:=0; while sol[k]+s<n do begin sol[k]:=sol[k]+1; s:=s+sol[k]; back(k+1); s:=s-sol[k] end; end end;

begin write('n='); readln(n); back(1) end.

#include <iostream.h> int sol[100], n,i,s;

void back(int k) if (s==n) for (i=1;i<=k-1;i++) cout<<sol[i]; cout<<endl; else sol[k]=0; while (sol[k]+s<n) sol[k]++; s+=sol[k]; back(k+1); s-=sol[k];

main() cout<<"n="; cin>>n; back(1);

s=0, k=1

1 0 0 0

s=1, k=2

1 1 0 0

s=2, k=3

1 1 1 0

s=3, k=4

1 1 1 1

soluţie

s=2, k=3

1 1 2

soluţie

s=1, k=2

1 2 0 0

s=3, k=3

1 2 1

soluţie

s=1, k=2

1 3

soluţie

Page 226: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

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,1 sau 1,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,b] (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.5.3. Plata unei sume cu bancnote de valori date

Enunţ. Se dau suma s şi n tipuri de monede având valori de a1,a2,...,an 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ă.

Iată 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;

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. În aceste condiţii, o soluţie pentru exemplul anterior arată ca alăturat, unde suma 5 se formează cu două monede de 1 şi o monedă de 3.

Ce observăm?

2 0 1 sol

1 2 3 a

Page 227: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 227

1. Există componente ale vectorului sol care reţin 0. Această situaţie corespunde cazului în care moneda respectivă nu este luată în calcul. Din acest motiv, fiecare componentă a vectorului sol va fi iniţializată cu o valoare aflată înaintea tuturor celor posibile, adică cu -1.

2. Orice soluţie are exact n componente (n este numărul de tipuri de monede). Acest număr include toate monedele, chiar şi cele care nu sunt luate în calcul.

3. Ca şi la problema anterioară, vom reţine în permanenţă suma obţinută la un moment dat. Astfel, la pasul k, avem la dispoziţie suma:

s = a[1]*sol[1] + a[2]*sol[2] + ... + a[k-1]*sol[k-1]

4. Avem soluţie dacă:

s = a[1]*sol[1] + a[2]*sol[2] + ... + a[n]*sol[n] = Suma

Exerciţiu. Încercaţi să arătaţi, prin desene, ca la problema anterioară, modul de obţinere a tuturor soluţiilor pentru Suma=5 şi n=3.

În continuare, este prezentat programul:

Varianta Pascal Varianta C++

var sol,a:array[1..100] of integer; n,i,s,Suma:integer;

procedure back (k:integer); begin if s=suma then begin writeln('Solutie'); for i:=1 to k-1 do if sol[i]<>0 then writeln (sol[i],' monede de ',a[i]); writeln; end else begin sol[k]:=-1; while (sol[k]*a[k]+s<Suma) and (k<n+1) do begin sol[k]:=sol[k]+1; s:=s+sol[k]*a[k]; back(k+1); s:=s-sol[k]*a[k] end; end end;

#include <iostream.h> int sol[100], a[100], n,i,s,Suma;

void back(int k) if (s==Suma) cout<<"Solutie "<<endl; for(i=1;i<=k-1;i++) if(sol[i]) cout<<sol[i]<<"monede de " <<a[i]<<endl; cout<<endl; else sol[k]=-1; while(sol[k]*a[k]+s<Suma && k<n+1) sol[k]++; s+=sol[k]*a[k]; back(k+1); s-=sol[k]*a[k];

main() cout<<"suma="; cin>>Suma; cout<<"n="; cin>>n;

Page 228: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

228 Capitolul 8. Metoda backtracking

begin write ('suma='); readln(suma); write('n='); readln(n); for i:=1 to n do begin write ('a[',i,']='); readln(a[i]); end; back(1) end.

for (i=1;i<=n;i++) cout<<"a["<<i<<"]="; cin>>a[i]; back(1);

Exerciţiu. Adaptaţi rezolvarea problemei plăţii unei sumei cu bancnote date, cunoscând, în plus, pentru fiecare valoare ai numărul limită bi de bancnote cu valoarea respectivă disponibile. Astfel, pentru s=100, a=(2,5,50), b=(10,6,3), varinata s=10x5+1x50 nu corespunde cerinţei deoarece nu avem la dispoziţie 10 monede de 5, ci doar 6.

8.5.4. Problema labirintului

Enunţ. Se dă un labirint sub formă de matrice cu m linii şi n coloane. Fiecare 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 l(i,j) un element al matricei. Acesta poate lua valori între 0 şi 15. Se

consideră ieşirile spre nord, est, sud şi vest, luate în această ordine. Pentru fiecare direcţie cu ieşire se reţine 1, iar în caz contrar, se reţine 0. Un şir de patru cifre 1 sau 0 formează un număr în baza 2. Acest număr este convertit în baza 10 şi reţinut în l(i,j). De exemplu, pentru o cameră care are ieşire în nord şi vest, avem 1001(2)=9(10). Exemplu. Alăturat este prezentat un labirint. Acolo unde nu este permisă trecerea dintr-o cameră în alta, se marchează cu o linie oblică. De asemenea, matricea reţine şi valorile corespunzătoare ieşirilor, aşa cum sunt ele cerute de program.

Rezolvare

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 matrice 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).

15 11 10 14

11 12 11 12

11 6 15 7

Page 229: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 229

2. Nu toate soluţiile au aceeaşi lungime, întrucât există trasee de lungime diferită. 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 între 1 şi n). Evident, atunci când s-a găsit o situaţie, aceasta se afişează.

3. Spunem că o cameră este accesibilă dacă există intrare din camera curentă către ea. Atenţie la modul (vedeţi programul) în care testăm dacă o cameră este accesibilă sau nu. Este o operaţie în care se testează conţinutul unui anumit bit. Acesta se obţine efectuând un ŞI logic între două valori. De exemplu, dacă testăm ieşirea spre sud, atunci efectuăm ŞI logic între 0010(2)=2(10) şi valoarea reţinută în matrice pentru camera curentă. Dacă valoarea obţinută este diferită de 0, atunci avem ieşire din camera curentă 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. În caz că a fost vizitată, se face pasul înapoi.

Analizaţi programul:

Varianta Pascal Varianta C++

var sol:array [1..100,1..2] of integer; l:array [0..10,0..10] of integer; m,n,i,j,lin,col:integer;

function vizitat(k,lin, col:integer):boolean; begin vizitat:=false; for i:=1 to k-1 do if (sol[i,1]=lin) and (sol[i,2]=col) then vizitat:=true; end;

procedure tipar(k,lin,col:integer); begin writeln('Solutie'); for i:=1 to k-1 do writeln(sol[i,1],' ', sol[i,2]); if lin=0 then writeln('iesire prin nord') else if lin=m+1 then writeln('iesire prin sud') else if col=0 then writeln('iesire prin vest') else writeln('iesire prin est'); readln; end;

#include <iostream.h> int sol[100][2],l[10][10], m,n,i,j,lin,col;

int vizitat(int k,int lin, int col) int v=0; for (i=1;i<=k;i++) if (sol[i][0]==lin && sol[i][1]==col) v=1; return v;

void tipar(int k,int lin, int col) cout <<" Solutie "<<endl; for (i=1;i<=k-1;i++) cout<<sol[i][0]<<" " <<sol[i][1]<<endl; if (lin==0) cout<<"iesire prin nord" <<endl; else if (lin==m+1) cout<<"iesire prin sud" <<endl; else if (col==0) cout<<"iesire prin vest" <<endl; else cout<<"iesire prin est" <<endl;

Page 230: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

230 Capitolul 8. Metoda backtracking

procedure back(k,lin,col:integer); var i:integer; begin if (lin=0) or (lin=m+1) or (col=0) or (col=n+1) then tipar(k,lin,col) else begin sol[k,1]:=lin; sol[k,2]:=col; for i:=1 to 4 do case i of 1:if (l[lin,col] and 8<>0) and not vizitat(k,lin-1,col) then back(k+1,lin-1,col); 2:if (l[lin,col] and 4<>0) and not vizitat(k,lin,col+1) then back(k+1,lin,col+1);

3:if (l[lin,col] and 2<>0) and not vizitat(k,lin+1,col) then back(k+1,lin+1,col); 4:if (l[lin,col] and 1<>0) and not vizitat(k,lin,col-1) then back(k+1,lin,col-1) end; case end end;

begin write('M='); readln(m); write('N='); readln(n); for i:=1 to m do for j:=1 to n do begin write('l[',i,',',j,']='); readln(l[i,j]) end; write('lin='); readln(lin); write('col='); readln(col); back(1,lin,col) end.

void back(int k, int lin, int col) if (lin==0 || lin==m+1 || col==0 || col==n+1) tipar(k,lin,col); else sol[k][0]=lin; sol[k][1]=col; for (int i=1;i<=4;i++) switch(i) case 1: if (l[lin][col] & 8 && ! vizitat(k, lin-1,col)) back(k+1,lin-1,col); break; case 2: if (l[lin][col] & 4 && ! vizitat(k, lin,col+1)) back(k+1,lin,col+1); break;

case 3: if (l[lin][col] & 2 && ! vizitat(k, lin+1,col)) back(k+1,lin+1,col); break; case 4: if (l[lin][col] & 1 && ! vizitat(k, lin,col-1)) back(k+1,lin,col-1); break;

main() cout<<"M="; cin>>m; cout<<"N="; cin>>n; for (i=1;i<=m;i++) for(j=1;j<=n;j++) cout<<"l["<<i<<"," <<j<<"]="; cin>>l[i][j]; cout<<"Linie="; cin>>lin; cout<<"Coloana= "; cin>>col; back(1,lin,col);

Page 231: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-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 în minte este să

generăm toate ieşirile, ca în program, pentru a o putea depista pe cea de lungime minimă. Ei bine, răspunsul nu este satisfăcător. Nu uitaţi, tehnica backtracking necesită 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 în care fiecare căsuţă reţine valoarea 1 sau 0 (1 semnificând căsuţă plină, prin care nu se poate trece, iar 0 căsuţă liberă, pe unde se poate trece). Ca şi în problema prezentată, deplasarea se poate face dintr-o căsuţă în orice altă căsuţă alăturată, orizontal sau vertical, cu condiţia ca ea să existe şi să fie liberă. Validaţi poziţia iniţială a omului (lin, col), astfel încât aceasta să corespundă unei căsuţe libere. Estimaţi spaţiul de memorie utilizat în această variantă. 2. Realizaţi o variantă a rezolvării de la 1, adăugând câte o linie sau coloană suplimentară pe fiecare margine a labirintului, astfel încât să nu se mai testeze ca celula în 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

Enunţ. Se dă un teren sub formă de matrice cu m linii şi n coloane. Fiecare element al matricei reprezintă un subteren cu o anumită altitudine dată de valoarea reţinută de element (număr natural). Într-un astfel de subteren, de coordonate (lin,col) se găseşte o bilă. Ştiind că bila se poate deplasa în orice porţiune de teren aflată la nord, est, sud sau vest, de altitudine strict inferioară porţiunii pe care se găseşte bila. Se cere să se găsească toate posibilităţile ca bila să părăsească terenul. Exemplu. Fie terenul alăturat. Iniţial, bila se află în subterenul de coordonate (2,2). O posibilitate de ieşire din teren este dată de drumul: (2,2), (2,3), (3,3), (3,4). În program, altitudinile subterenului vor fi reţinute de matricea t.

Analiza problemei şi rezolvarea ei. Problema seamănă cu cea anterioară, deci putem gândi că dacă înlocuim testul de intrare într-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.

6 8 9 3

9 7 6 3

5 8 5 4

8 3 7 1

Page 232: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

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 Varianta C++

var sol:array [1..100,1..3] of integer; t:array [0..10,0..10] of integer; m,n,i,j,lin,col:integer; procedure tipar(k:integer); begin writeln('Solutie'); for i:=1 to k-1 do writeln(sol[i,2],' ', sol[i,3]); end; procedure back(k,lin,col:integer); begin if (lin=0) or (lin=m+1) or (col=0) or (col=n+1) then tipar(k) else begin sol[k,1]:=0; sol[k,2]:=lin; sol[k,3]:=col; while sol[k,1]<4 do begin sol[k,1]:=sol[k,1]+1; case sol[k,1] of 1:if t[lin-1,col]< t[lin,col] then back(k+1,lin-1,col); 2:if t[lin,col+1]< t[lin,col] then back(k+1,lin,col+1); 3:if t[lin+1,col]< t[lin,col] then back(k+1,lin+1,col); 4:if t[lin,col-1]< t[lin,col] then back(k+1,lin,col-1); end; case end end end;

begin write('M='); readln(m); write('N='); readln(n);

#include <iostream.h> int sol[100][3],t[10][10], m,n,i,j,lin,col;

void tipar(int k) cout<<"Solutie "<<endl; for(i=1;i<=k-1;i++) cout<<sol[i][1]<<" " <<sol[i][2]<<endl;

void back(int k, int lin, int col) if (lin==0 || lin==m+1 || col==0 || col==n+1) tipar(k); else sol[k][0]=0; sol[k][1]=lin; sol[k][2]=col; while (sol[k][0]<4) sol[k][0]++; switch(sol[k][0]) case 1: if(t[lin-1][col]<t[lin][col]) back(k+1,lin-1,col); break; case 2: if(t[lin][col+1]<t[lin][col]) back(k+1,lin,col+1); break; case 3: if(t[lin+1][col]<t[lin][col]) back(k+1,lin+1,col); break; case 4: if(t[lin][col-1]<t[lin][col]) back(k+1,lin,col-1); break;

main() cout<<"m="; cin>>m; cout<<"n="; cin>>n;

Page 233: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 233

for i:=1 to m do for j:=1 to n do begin write('t[',i,',',j, ']='); readln(t[i,j]) end; write('lin=');readln(lin); write('col=');readln(col); back(1,lin,col) end.

for (i=1;i<=m;i++) for (j=1;j<=n;j++) cout<<"t["<<i<<","<<j<<"]="; cin>>t[i][j]; cout<<"lin="; cin>>lin; cout<<"col="; cin>>col; back(1,lin,col);

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

Enunţ. Se consideră o tablă de şah nxn şi un cal plasat în colţul din stânga, sus. Se cere să se afişeze o posibilitate de mutare a acestei piese de şah, astfel încât să treacă o singură dată prin fiecare pătrat al tablei. Alăturat, observaţi o soluţie pentru n=5.

Analiza problemei şi rezolvarea ei. Fiind dată o poziţie în care se găseşte calul, acesta poate sări în 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 în:

(lin+x[0],col+y[0])...(lin+x[7],col+y[7]). 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. În rest, s-a procedat ca la problema anterioară.

Varianta Pascal Varianta C++

const x:array[1..8] of integer=(-1,1,2,2,1,-1,-2,-2); y:array[1..8] of integer=(2,2,1,-1,-2,-2,-1,1);

var n:integer; st: array[1..1000,1..2] of integer; t: array[-1..25,-1..25] of integer; procedure back(k,lin, col:integer); var i,linie,coloana:integer;

#include <iostream.h> #include <stdlib.h> const int x[8]=-1,1,2,2,1,-1, -2,-2;

const int y[8]=2,2,1,-1,-2,-2, -1,1;

int n,sol[1000][2],t[25][25]; void back(int k,int lin, int col) int linie,coloana,i;

582314251813692274192415

12172211032011161

Page 234: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

234 Capitolul 8. Metoda backtracking

begin if k=n*n then begin for i:=1 to k-1 do writeln(st[i,1],' ', st[i,2]); writeln(lin,' ',col); halt; end else begin st[k,1]:=lin; st[k,2]:=col; for i:=1 to 8 do begin linie:=lin+x[i]; coloana:=col+y[i]; if (linie<=n) and (linie>=1) and (coloana<=n) and (coloana>=1) and (t[linie,coloana]=0) then begin t[linie,coloana]:=1; back(k+1,linie, coloana); t[linie,coloana]:=0; end; end end end; begin write ('n='); readln(n); back(1,1,1); end.

if (k==n*n) for (i=1;i<=k-1;i++) cout<<sol[i][0]<<" " <<sol[i][1]<<endl; cout<<lin<<" "<<col; exit(EXIT_SUCCESS); else sol[k][0]=lin; sol[k][1]=col; for (i=0;i<=7;i++) linie=lin+x[i]; coloana=col+y[i]; if (linie<=n && linie>=1 && coloana<=n && coloana>=1 && t[linie][coloana]==0) t[linie][coloana]=1; back(k+1,linie,coloana); t[linie][coloana]=0; main() cout<<"n="; cin>>n; back(1,1,1);

Page 235: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 235

Probleme propuse 1. 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

de k persoane, din care l 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, astfel încât 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ţine din aceasta astfel încât 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ărilor?

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ările de n persoane luate câte p?

8. Se citesc n numere naturale distincte. Se cere o submulţime cu p elemente

astfel încât suma elementelor sale să fie maximă în raport cu toate submulţimile cu acelaşi număr de elemente.

9. Să se determine 5 numere de câte n cifre, fiecare cifră putând fi 1 sau 2, astfel

încât oricare dintre aceste 5 numere să coincidă exact în m poziţii şi să nu existe o poziţie în care să apară aceeaşi cifră în toate cele 5 numere.

Page 236: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

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. “Attila ş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ţia 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 în toate modurile posibile elementele mulţimii 1,2,...,n

astfel încât numerele i, i+l, ..., i+k să fie unul după celălalt şi în această ordine (l=1,i+k≤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ţiile 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 şirurile de N paranteze

care se închid corect.

Exemplu: pentru N=6: ((( ))),(()()),()()(),()(()),(())(). 17. Se dau N puncte albe şi N puncte negre în plan, de coordonate întregi. 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 2≤i≤N,

există 1≤j≤i astfel încât

V(i)-V(j)=1.

Page 237: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 237

Exemplu: pentru N=4, permutările cu proprietatea de mai sus sunt:

2134, 2314, 3214, 2341, 3241, 3421, 4321.

19. O trupă cu N actori îşi propune să joace o piesă cu A acte astfel încât:

oricare două acte să aibă distribuţie diferită;

în 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 cu N componente întregi, se cere:

să se determine toate subşirurile crescătoare de lungime [N/5];

să se calculeze p(1)+p(2)+...+p(k), unde p(k) reprezintă numărul subşirurilor 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 maximă 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.

Page 238: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

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 elimină 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, în 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(n2). Dar dacă sortăm numerele, atunci le putem afişa pe cele distincte dintr-o singură parcurgere. Sortarea se poate efectua în O(n×log(n)), iar parcurgerea în O(n). Prin urmare, complexitatea este O(n×log(n)). 26. d)

Explicaţie: pe fiecare nivel al stivei se caută succesorii în ordinea n, n-1, ..., 1.

Page 239: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

239

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ă-l 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ă-l notăm cu un cerculeţ în care să înscriem numărul lui (de la 1 la 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ă-l 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 cazul 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 neorientat.

1

2

5

6

3 4

Figura 9.1. Exemplu de graf neorientat

Page 240: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

240 Capitolul 9. Introducere în teoria grafurilor

Desigur, această abordare este intuitivă. Teoria grafurilor este fundamentată 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

V = v1, v2,... vn este o mulţime finită şi nevidă. Elementele mulţimii V se numesc noduri (vârfuri).

. Un graf neorientat este o pereche ordonată G=(V,E), unde:

E este o mulţime finită de perechi neordonate de forma (vi, vj), unde i≠j, şi vi,vj∈V. 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,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 cu m numărul muchiilor.

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 vi,vj∈G sunt adiacente dacă există muchia (vi, vj)∈E. Vom spune că muchia (vi,vj)∈E este incidentă la nodurile vi şi vj.

În exemplul dat anterior, nodurile 1 şi 5 sunt adiacente, dar nodurile 2 şi 5 nu sunt adiacente. Muchia (4,5) este incidentă la nodurile 4 şi 5.

1 Definiţia este restrictivă, în unele lucrări veţi întâlni definiţii mai puţin restrictive, de exemplu, poate exista o muchie de la un nod la el însuşi sau nu se cere ca mulţimea nodurilor să fie finită.

1

2

5

6

3 4

Figura 9.2. Alt exemplu de graf neorientat

Page 241: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-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 0 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)=0 (6 este nod izolat).

O relaţie utilă: fie un graf neorientat cu n noduri şi m muchii. Dacă notăm cu d1, d2, ..., dn gradele celor n noduri, atunci avem relaţia:

2m....dddd n321 =+++

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 relaţia anterioară.

Pentru a înţelege bine noţiunile prezentate în acest paragraf, ne vom referi la exemplele din 9.1.1:

- fie afirmaţia: gradul nodului i este k. Pentru exemplul 1., ea are 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 şi j sunt adiacente. Pentru exemplul 1., ea are semnificaţia că oraşele i şi j sunt unite printr-o şosea care nu trece prin alte 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 insulă), 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 au nodurile sau muchiile şi ce înseamnă gradul unui nod.

Page 242: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

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:

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, ai,j au semnificaţia:

∉∈

=Ej)(i,pentru0,Ej)(i,pentru1,

a ji,

2 Modul de alegere a structurii îl veţi înţelege pe parcursul studiului acestui capitol.

6 1 2 1 3 1 5 2 3 3 4 4 5

Fişierul text:

Figura 9.3. Exemplu de graf neorientat

1

2

5

6

3 4

Page 243: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 243

Pentru graful din figura 9.3, matricea de adiacenţă este prezentată în continuare:

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 0:

,...,2,1,0, nia ii ∈∀= .

2. Matricea de adiacenţă este simetrică:

,...,2,1,,,, njiaa ijji ∈∀= .

Evident, deoarece muchia (i,j) coincide cu muchia (j,i).

3. Suma elementelor de pe linia i, i∈1,2,...,n, are ca rezultat gradul nodului i, d(i), pentru că astfel se obţine suma nodurilor j, j ∈1,2,...,n, pentru care există muchie de la i la j, adică suma muchiilor incidente la i.

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 ∈1,2,...,n, are ca rezultat gradul nodului j, d(j).

! Raţionamentul este asemănător cu cel de la observaţia precedentă.

5. 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 0.

Subprogramele pe care le vom utiliza pentru această modalitate de memorare sunt prezentate în continuare:

=

000000001001010100001011000101010110

6,6A

Page 244: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

244 Capitolul 9. Introducere în teoria grafurilor

Varianta Pascal Varianta C++

type mat_ad=array[1..50,1..50] of integer; procedure CitireN (Nume_Fis:string; var A:Mat_ad; var n:integer); var f:text; i,j:byte; begin Assign(f,Nume_Fis); Reset(f); Readln(f,n); while(not eof(f)) do begin readln(f,i,j); A[i,j]:=1; A[j,i]:=1; end; close(f); end;

void CitireN (char Nume_fis[20], int A[50][50], int& n) int i,j; fstreamf (Nume_fis,ios::in); f>>n; while (f>>i>>j) A[i][j]=A[j][i]=1; f.close();

Programul următor citeşte datele referitoare la un graf şi afişează matricea sa de adiacenţă:

Varianta Pascal Varianta C++

uses grafuri; var A:mat_ad; n,i,j:integer; begin CitireN('Graf.txt',A,n); for i:=1 to n do begin for j:=1 to n do write (A[i,j],' '); writeln; end; end.

#include "grafuri.cpp" int A[50][50],n; main() CitireN("Graf.txt",A,n); for (int i=1;i<=n;i++) for (int j=1;j<=n;j++) cout<<A[i][j]<<" "; cout<<endl;

B. Memorarea grafului prin liste de adiacenţă

Listele de adiacenţă reprezintă o altă formă de memorare a grafurilor, în care pentru fiecare nod se cunoaşte lista nodurilor adiacente cu el. De exemplu, pentru graful din figura 9.4., listele de adiacenţă sunt:

Figura 9.4.

1

2

5

6

3 4

1 -> 2,3,5 2 -> 1,3 3 -> 1,2,4 4 -> 3,5 5 -> 1,4 6 ->

Page 245: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 245

În acest caz, se utilizează un vector cu n componente, pe care îl vom numi Start şi o matrice T cu 2 linii şi 2m coloane. Semnificaţiile sunt:

Start – pentru fiecare nod i, Start[i] specifică coloana din T unde începe lista nodurilor adiacente cu i. Dacă reţine 0, înseamnă că nodul i nu are noduri adiacente.

T(0,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 0, atunci acesta este ultimul elerment din lista succesorilor.

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 găseşte la indicele 8. 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ă, pentru T(1,4)=0 (T[1][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 în lista nodurilor adiacente cu i.

Consideră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 0, 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ă în T, se incrementează k; 2. T(0,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]; 4. Start[i] 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.

2

1

3

3

1

2

1

4

5

5

1

6

3

7

4

9

2

8

3

10

5

11

4

12

0 1 0 0 3 0 2 8 4 0 10 6

T[0]

T[1]

5

1

9

3

7

2

11

4

12

5

0

6

Start

Page 246: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

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):

Varianta Pascal Varianta C++

type lista=array[0..1,1..50]of integer; pornire=array[1..50] of integer; ... procedure Citire_LA_Astatic (Nume_fis:string;var T:Lista; var Start:pornire;var n:integer); var i,j,k:integer; f:text; begin k:=0; Assign(f,Nume_Fis); Reset(f); Readln(f,n); while(not eof(f)) do begin readln(f,i,j); k:=k+1; T[0,k]:=j; T[1,k]:=Start[i]; Start[i]:=k; k:=k+1; T[0,k]:=i; T[1,k]:=Start[j]; Start[j]:=k; end; close(f); end.

void Citire_LA_Astatic (char Nume_fis[20], int T[2][50], int Start[50], int& n) int i,j,k=0; fstream f(Nume_fis,ios::in); f>>n; while (f>>i>>j) k++; T[0][k]=j; T[1][k]=Start[i]; Start[i]=k; k++; T[0][k]=i; T[1][k]=Start[j]; Start[j]=k; f.close();

Programul următor citeşte datele despre un graf şi afişează, pentru fiecare nod, lista nodurilor adiacente:

Varianta Pascal Varianta C++ uses grafuri; var Start:pornire; T:lista; n,i,man:integer; begin Citire_LA_Astatic ('Graf.txt',T,Start,n); for i:=1 to n do begin writeln ('Noduri adiac. cu ',i); man:=Start[i]; while (man<>0)do begin write(T[0,man],' '); man:=T[1,man]; end; writeln end; end.

#include "grafuri.cpp" int T[2][50],Start[50], n,i,man; main() Citire_LA_Astatic ("Graf.txt",T,Start,n); for (int i=1;i<=n;i++) cout<<"Noduri adiac. cu "<< i<<endl; man=Start[i]; while (man) cout<<T[0][man]<<" "; man=T[1][man]; cout<<endl;

Page 247: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-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:

Exerciţiu. Realizaţi un subprogram care citeşte şi memorează datele referitoare la muchiile unui graf memorat prin lista muchiilor.

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-l 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 Kn, unde n este numărul de noduri ale grafului.

Varianta Pascal Varianta C++

type muchie=record x,y:integer; end;

var V:array[1..50] of muchie;

struct muchie int x,y; ;

muchie V[50];

Page 248: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

248 Capitolul 9. Introducere în teoria grafurilor

Alăturat, aveţi un graf complet cu 4 noduri (K4):

Relaţii utile: 1. Într-un graf complet, gradul oricărui nod este n-1. Evident, din fiecare nod, pleacă (sosesc) n-1 muchii.

2. Într-un graf complet, avem relaţia: 2

1)n(nm −= , unde m este numărul de

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 2

1)n(nC2

n

−= .

3. Avem 2 21)n(n−

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:

21)n(n −

şi corespunde unui graf complet. Se ştie că, fiind dată o mulţime A cu n elemente, avem 2n submulţimi disjuncte ale acesteia (aici este inclusă şi submulţimea vidă şi A). Prin urmare, avem:

2 21)n(n−

submulţimi ale numărului maxim de muchii. Ori, fiecărei submulţimi de muchii îi corespunde un graf neorientat, pentru că nodurile sunt aceleaşi.

9.1.5. Graf parţial, subgraf Definiţia 9.5. Un graf parţial al unui graf neorientat dat G=(V,E) este un graf G1=(V,E1), unde E1⊆E.

1

3 2

4 Figura 9.5. Exemplu de graf complet

Page 249: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 249

Figura 9.6. Obţinerea unui

graf parţial

Figura 9.7. Obţinerea unui

subgraf

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:

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ă î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?

Definiţia 9.6. Un subgraf al unui graf neorientat G=(V,E) este un graf G1=(V1,E1), unde V1⊂V, E1⊂E, iar muchiile din E1 sunt toate muchiile din E care sunt incidente numai la noduri din mulţimea V1.

Un subgraf al unui graf G este el însuşi sau se obţine din G prin suprimarea anumitor noduri şi a tuturor muchiilor incidente cu acestea. Priviţi exemplul de mai jos:

G=(V,E)

1

3 2

4

1

3 2

4

G1=(V,E1)

G=(V,E)

1

3 2

4

1

3

4

G1=(V1,E1)

rezultă

rezultă

Page 250: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

250 Capitolul 9. Introducere în teoria grafurilor

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. Între timp, anumite firme se desfiinţează. Aceasta înseamnă că în G vom elimina anumite noduri şi muchiile incidente lor, obţinând un subgraf al lui G, 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 noduri? Indicaţie: care este numărul de submulţimi ale mulţimii 1,2,...,n? Întrucât mulţimea

nodurilor, V, este nevidă, vom face abstracţie de mulţimea vidă.

9.1.6. Parcurgerea grafurilor neorientate

Prin parcurgerea grafurilor3

înţelegem o modalitate de vizitare a nodurilor 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ţiile parcurgerilor şi ne vom mărgini numai la prezentarea algoritmilor de parcurgere. În paragrafele următoare, veţi găsi 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ăm vizitat.

Vizităm apoi toate nodurile adiacente cu el - fie ele j1, j2, ... 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 în cazul grafurilor conexe. Cum noţiunea nu 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.

Page 251: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 251

=vizitatfostainodul

vizitatfostanuinodul,1,0

s[i]

Pentru graful alăturat avem: Nod pornire 1: 1 3 6 2 7 5 4 Nod pornire 3: 3 7 6 1 2 5 4 Nod pornire 6: 6 3 1 7 2 5 4

1

2

5

6 3

4 7

Figura 9.8.

Exemple de parcurgeri BF ale aceluiaşi graf, pornind de la noduri diferite:

⇒ 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;

- s_c - indicele ultimei componente a cozii;

- coada - vectorul care memorează coada propriu-zisă;

- s - vector ce reţine nodurile vizitate:

În 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ţă:

Page 252: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

252 Capitolul 9. Introducere în teoria grafurilor

1. Programul următor parcurge BF un graf memorat prin liste de adiacenţă:

2. Programul următor parcurge BF un graf memorat prin matricea de adiacenţă:

Varianta Pascal Varianta C++

uses grafuri; var n,i_c,sf_c,i:integer; coada,s:array[1..50] of integer; A:mat_ad;

procedure bf(nod:integer); begin i_c:=1; sf_c:=1;

#include "grafuri.cpp" int n, coada[50],s[50],i_c=1, sf_c=1,A[50][50],i;

void bf(int nod) coada[i_c]=nod; s[nod]=1;

Varianta Pascal Varianta C++

uses grafuri;

var n,i_c,sf_c,p:integer; coada,s:array[1..50] of integer; T:lista; Start:pornire;

procedure bf(nod:integer); begin i_c:=1;sf_c:=1; coada[i_c]:=nod; s[nod]:=1; while i_c<=sf_c do begin p:=Start[coada[i_c]]; while p<>0 do begin if s[T[0,p]]=0 then begin sf_c:=sf_c+1; coada[sf_c]:=T[0,p]; s[T[0,p]]:=1; end; p:=T[1,p]; end; writeln(coada[i_c]); i_c:=i_c+1; end end;

begin Citire_LA_Astatic('Graf.txt',T,Start,n); bf(3); end.

#include "grafuri.cpp"

int n, coada[50],s[50],i_c=1, sf_c=1,T[2][50],Start[50],p;

void bf(int nod) coada[i_c]=nod; s[nod]=1; while (i_c<=sf_c) p=Start[coada[i_c]]; while (p) if (s[T[0][p]]==0) sf_c++; coada[sf_c]=T[0][p]; s[T[0][p]]=1; p=T[1][p]; cout<<coada[i_c]<<endl; i_c++; main() Citire_LA_Astatic("Graf.txt",T,Start,n); bf(6);

Page 253: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 253

coada[i_c]:=nod; s[nod]:=1; while (i_c<=sf_c) do begin i:=1; while i<=n do begin if ((A[coada[i_c],i]=1) and (s[i]=0)) then begin sf_c:=sf_c+1;; coada[sf_c]:=i; s[i]:=1; end; i:=i+1; end; writeln(coada[i_c]); i_c:=i_c+1; end end;

begin CitireN('Graf.txt',A,n); bf(1); end.

while (i_c<=sf_c) i=1; while (i<=n) if (A[coada[i_c]][i]==1 && s[i]==0) sf_c++; coada[sf_c]=i; s[i]=1; i++; cout<<coada[i_c]<<endl; i_c++;

main() CitireN("Graf.txt",A,n); bf(1);

9.1.6.2. Parcurgerea în adâncime (DF - 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.

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

1

2

5

6 3

4 7 Figura 9.9.

Page 254: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

254 Capitolul 9. Introducere în teoria grafurilor

Exemple de implementări ale parcurgerii DF 1. Programul următor parcurge DF un graf memorat prin liste de adiacenţă:

Varianta Pascal Varianta C++

uses grafuri; var n:integer; s:array[1..50] of integer; T:lista; Start:pornire;

procedure df(nod:integer); var p:integer; begin writeln(nod,' '); p:=Start[nod]; s[nod]:=1; while p<>0 do begin if s[T[0,p]]=0 then df(T[0,p]); p:=T[1,p]; end end;

begin Citire_LA_Astatic('Graf.txt',T, Start,n); df(1); end.

#include "grafuri.cpp" int s[50],n; T[2][50],Start[50];

void df(int nod) int p; cout<<nod<<" "; p=Start[nod]; s[nod]=1; while (p) if (s[T[0][p]]==0) df(T[0][p]); p=T[1][p];

main() Citire_LA_Astatic("Graf.txt", T,Start,n); df(1);

2. Programul următor parcurge DF un graf memorat prin matricea de adiacenţă:

Varianta Pascal Varianta C++

uses grafuri; var n:integer; s:array[1..50]of integer; A:mat_ad;

procedure df_r(nod:integer); var k:integer; begin write(nod,' '); s[nod]:=1; for k:=1 to n do if (A[nod,k]=1) and (s[k]=0) then df_r(k); end;

begin CitireN('Graf.txt',A,n); df_r(1); end.

#include "grafuri.cpp" int s[50],A[50][50],n;

void df_r(int nod) int k; cout<<nod<<" "; s[nod]=1; for (k=1;k<=n;k++) if(A[nod][k]==1 && s[k]==0) df_r(k);

main() CitireN("Graf.txt",A,n); df_r(1);

Page 255: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-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 i a matricei 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 tuturor prietenilor, aceştia, la rândul lor, transmit informaţia tuturor prietenilor lor, ş.a.m.d. Întrebarea este: informaţia ajunge la persoana b?

Pentru exemplul 4.

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.

Fiind date două triunghiuri, 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 l şi l cu b.

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,v2)∈E, (v2,v3)∈E, ..., (vp-1,vp)∈E. De

altfel, un lanţ poate fi definit prin succesiunea de muchii (v1,v2)∈E, (v2,v3)∈E, ..., (vp-1,vp)∈E. Vârfurile v1 ş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.

Page 256: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

256 Capitolul 9. Introducere în teoria grafurilor

Figura 9.10.

dacă dacă

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.

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.

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âne de rezolvat problema modului în care reţinem lanţul de la a la b. Să observăm că fiecare metodă de parcurgere a grafului, pornind de la un nod j, selectează un altul i, dacă cele două noduri sunt adiacente şi dacă nodul i este vizitat pentru prima dată. Pentru a reţine selecţiile astfel efectuate, vom utiliza un vector T, iar elementele acestuia au semnificaţia următoare:

=selectatfostanu 0,

luialdescendenteste j,T[i]

iji

Să 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)=0, 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ţine valoarea 0. Pentru ca drumul să nu fie afişat în ordine inversă 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:

Varianta Pascal Varianta C++

uses grafuri; var n,a1,b:integer; s,T:array[1..50] of integer; A:mat_ad;

procedure refac (nod:integer); begin if nod<>0 then begin refac (T[nod]); write(nod,' '); end end;

#include "grafuri.cpp" int s[50],A[50][50],n,T[50],a,b;

void refac (int nod) if (nod!=0) refac (T[nod]); cout<<nod<<" ";

1

2 4 3

5

Page 257: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 257

procedure df_r(nod:integer); var k:integer; begin s[nod]:=1; for k:=1 to n do if (A[nod,k]=1) and (s[k]=0) then begin T[k]:=nod; df_r(k); end end; begin CitireN('Graf.txt',A,n); write('a='); readln(a1); write('b='); readln(b); df_r(a1); if (T[b]<>0) then refac(b); end.

void df_r(int nod) int k; s[nod]=1; for (k=1;k<=n;k++) if(A[nod][k]==1 && s[k]==0) T[k]=nod; df_r(k); main() CitireN("Graf.txt",A,n); cout<<"a="; cin>>a; cout<<"b="; cin>>b; df_r(a); if (T[b]!=0) refac(b);

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ţul obţinut are lungimea minimă. Prin natura ei, parcurgerea BF selectează nodurile în ordinea "depărtării" lor faţă de nodul de la care a început parcurgerea. Astfel, la început 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ă între nodurile a şi b:

Varianta Pascal Varianta C++

uses grafuri; var n,a1,b,i_c,sf_c,i:integer; s,T,coada:array[1..50] of integer; A:mat_ad;

procedure refac (nod:integer); begin if nod<>0 then begin refac (T[nod]); write(nod,' '); end end;

#include "grafuri.cpp" int n,coada[50],s[50], i_c=1, sf_c=1, A[50][50],i,T[50],a,b;

void refac (int nod) if (nod!=0) refac (T[nod]); cout<<nod<<" ";

Page 258: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

258 Capitolul 9. Introducere în teoria grafurilor

în caz contrar dacă există lanţ de la i la j

procedure bf(nod:integer); begin i_c:=1; sf_c:=1; coada[i_c]:=nod; s[nod]:=1; while i_c<=sf_c do begin i:=1; while i<=n do begin if (A[coada[i_c],i]=1) and (s[i]=0) then begin sf_c:=sf_c+1; coada[sf_c]:=i; s[i]:=1; T[i]:=coada[i_c]; end; i:=i+1; end; i_c:=i_c+1; end end; begin CitireN('Graf.txt',A,n); write('a='); readln(a1); write ('b='); readln(b); bf(a1); if T[b]<>0 then refac(b); end.

void bf(int nod) coada[i_c]=nod; s[nod]=1; while (i_c<=sf_c) i=1; while (i<=n) if (A[coada[i_c]][i]==1 && s[i]==0) sf_c++; coada[sf_c]=i; s[i]=1; T[i]=coada[i_c]; i++; i_c++;

main() CitireN("Graf.txt",A,n); cout<<"a="; cin>>a; cout<<"b="; cin>>b; bf(a); if (T[b]!=0) refac(b);

Matricea lanţurilor. Î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 fiecare 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ţurilor, iar elementele ei au semnificaţia:

( )

=0,1,

ji,L

Page 259: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 259

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 nodul 1. Pentru toate nodurile j vizitate, vom avea L(1,j)=1, completând astfel prima linie a matricei. Apoi, vom parcurge din nou, graful, pornind de la nodul 2. Pentru toate nodurile j, vizitate, 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 lanţurilor este simetrică (de ce?). Lăsăm ca exerciţiu scrierea acestui program. Întrebare: 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 1: se poate ajunge cu maşina din orice oraş în oricare altul?

b) pentru exemplul 4: 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,y∈V, există un lanţ în care extremitatea iniţială este x şi extremitatea finală este y.

Exemple 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,5], ş.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 nodurile 1 şi 4 nu există nici un lanţ.

Un graf cu un singur nod este, prin definiţie, conex. Aceasta pentru că nu există două noduri diferite pentru care să se pună problema existenţei unui lanţ.

1

2 3

4 5

1

2 3

4

5

Figura 9.11.

Figura 9.12.

Page 260: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

260 Capitolul 9. Introducere în teoria grafurilor

Problema 9.3. Fiind dat un graf G=(V,E), să se scrie un program care să decidă dacă graful dat este sau nu conex.

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ţi scrie acest program?

9.1.9. Componente conexe Analizăm, din nou, exemplele de la 9.1.1.

a) Pentru exemplul 1: 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 acestei mulţimi, atunci nu putem ajunge cu maşina de la el la 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 G1=(V1,E1) un subgraf al său. Atunci G1=(V1,E1) este o componentă conexă a grafului G=(V,E) dacă sunt îndeplinite condiţiile de mai jos:

a) Oricare ar fi x,y∈V1, există un lanţ de la x la y.

b) Nu există un alt subgraf al lui G, G2=(V2,E2) cu V1⊂V2 care îndeplineşte condiţia a). 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. 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 componentă conexă.

1

2 3

4

5

Figura 9.13.

1

2 3

4

5

Figura 9.14.

Page 261: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 261

Observaţii

1. Câte componente conexe poate avea un graf neorientat cu n 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 C1, C2, ... Cp. Fie p numărul lor. Vom adăuga p-1 muchii, prima uneşte un nod din C1 cu unul din C2, a doua uneşte un nod din C2 cu unul din C3, ..., ultima uneşte un nod din Cp-1 cu unul din Cp. Problema (de programare).9.4. Fiind dat un graf neorientat, se cere să se afişeze nodurile fiecărei componente conexe.

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ţine. Pentru fiecare nod vizitat, s[i] 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.

Varianta Pascal Varianta C++

uses grafuri; var n,i:integer; s:array[1..50]of integer; A:mat_ad;

procedure df_r(nod:integer); var k:integer; begin write (nod,' '); s[nod]:=1; for k:=1 to n do if (A[nod,k]=1) and (s[k]=0) then df_r(k); end;

begin CitireN('Graf.txt',A,n); for i:=1 to n do if s[i]=0 then begin writeln('Comp conexa'); df_r(i); writeln; end end.

#include "grafuri.cpp" int s[50],A[50][50],n,i;

void df_r(int nod) int k; cout<<nod<<" "; s[nod]=1; for (k=1;k<=n;k++) if((A[nod][k]==1)&& (s[k]==0)) df_r(k);

main() CitireN("Graf.txt",A,n); for (i=1;i<=n;i++) if (s[i]==0) cout <<"Comp conexa"<<endl; df_r(i); cout<<endl;

Page 262: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

262 Capitolul 9. Introducere în teoria grafurilor

9.1.10. 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 am 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 iniţial 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 figura 9.15. avem:

a. [1,2,3,1] este ciclu elementar.

b. [1,2,1] 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,1] nu este ciclu, pentru că nu conţine numai muchii distincte.

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.

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 nodul 1, 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].

Astfel, s-a obţinut ciclul [1,2,3,1].

1

2 3

4

5

Figura 9.15.

1

2 3

4

Figura 9.16.

Page 263: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 263

Programul următor testează dacă un graf conţine sau nu cicluri. Să observăm că, odată vizitat un nod, accesat prin intermediul muchiei (nod,k), se şterge din matricea de adiacenţă muchia (k, nod), pentru că altfel ar fi selectată şi această muchie şi s-ar ajunge în situaţia să fie semnalat un ciclu fals.

Varianta Pascal Varianta C++

uses grafuri,wincrt;

var n:integer; s:array[1..50]of integer; A:mat_ad; gasit:boolean;

procedure df(nod: integer); var k:integer; begin s[nod]:=1; for k:=1 to n do if A[nod,k]=1 then begin A[k][nod]:=0; if s[k]=0 then df(k) else gasit:=true; end end;

begin CitireN('Graf.txt',A,n); df(1); if gasit then writeln('Da') else writeln('Nu') end.

#include "grafuri.cpp"

int s[50],A[50][50],gasit,n;

void df(int nod) int k; s[nod]=1; for(k=1;k<=n;k++) if (A[nod][k]==1) A[k][nod]=0; if (s[k]==0) df(k); else gasit=1;

main() CitireN("Graf.txt",A,n); df(1); if (gasit) cout<<"Da"; else cout<<"Nu";

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…

Page 264: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

264 Capitolul 9. Introducere în teoria grafurilor

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ă să 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 teoriei 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 cu 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 5 noduri.

Problema 9.6. Se citeşte un graf. Să se scrie un program care verifică dacă este arbore.

Rezolvare. Conexitatea ştim să o verificăm. Dacă într-o parcurgere, DF (BF) 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 stabili dacă graful este conex sau nu.

Varianta Pascal Varianta C++

uses grafuri; var n,i,suma:integer; s:array[1..50]of integer; A:mat_ad; gasit:boolean;

procedure df(nod:integer); var k:integer; begin s[nod]:=1; for k:=1 to n do if A[nod,k]=1 then begin A[k,nod]:=0; if s[k]=0 then df(k) else gasit:=true; end end;

#include "grafuri.cpp" int s[50],A[50][50],gasit,n,i, suma;

void df(int nod) int k; s[nod]=1; for(k=1;k<=n;k++) if (A[nod][k]==1) A[k][nod]=0; if (s[k]==0) df(k); else gasit=1;

1

5

3

2

4

Figura 9.17.

Page 265: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 265

begin CitireN('Graf.txt',A,n); df(1); suma:=0; for i:=1 to n do suma:=suma+s[i]; if suma<>n then writeln('Nu este conex') else if gasit then writeln('Are ciclu') else writeln('Arbore'); end.

main() CitireN("Graf.txt",A,n); df(1); suma=0; for (i=1;i<=n;i++) suma+=s[i]; if (suma!=n) cout<<"Nu este conex"; else if (gasit) cout<<"Are ciclu"; else cout<<"Arbore";

Teorema 9.1. Fie G un graf neorientat cu n noduri. G este arbore dacă şi numai dacă are n-1 muchii şi nu conţine cicluri.

Demonstraţie ⇒ 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=1, numărul muchiilor este 0 (se verifică, are n-1 muchii). Vom presupune proprietatea adevărată pentru arbori cu n noduri (adică au n-1 muchii). Fie un arbore cu n+1 noduri. Există cel puţin un nod terminal (nod care are o singură 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 incidentă. 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 G1, 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).

Rezultă că fiecare dintre ele este arbore. Fie mi numărul muchiilor şi ni numărul nodurilor fiecărui arbore Gi. Avem mi=ni-1. Dar m1+m2+...+mp=n-1. Rezultă: n1-1+n2-1+...+np-1=n-1, deci n1+n2+...+np=n+p-1. Dar G are n noduri. Rezultă: n=n+p-1, deci p=1. În concluzie, există o singură componentă conexă, care nu conţine cicluri. Rezultă că G este arbore.

Page 266: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

266 Capitolul 9. Introducere în teoria grafurilor

9.1.11.2. Noţiunea de arbore parţial

Să presupunem că î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 să se poată circula, astfel încât să nu rămâ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-1. Cum graful rămâne conex, în urma eliminării anumitor muchii se obţine, aşa cum rezultă din teorema 9.1. (dată în paragraful anterior), un arbore.

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.

Pentru a rezolva problema, vom folosi, 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.

Varianta Pascal Varianta C++ uses grafuri,wincrt; var n:integer; s:array[1..50]of integer; A:mat_ad; procedure df_r(nod:integer); var k:integer; begin s[nod]:=1; for k:=1 to n do if (A[nod,k]=1) and (s[k]=0) then begin writeln(nod,' ',k); df_r(k); end; end; begin CitireN('Graf.txt',A,n); df_r(1); end.

#include "grafuri.cpp" int s[50],A[50][50],n;

void df_r(int nod) int k; s[nod]=1; for (k=1;k<=n;k++) if (A[nod][k]==1 && s[k]==0) cout<<nod<<" "<<k<<endl; df_r(k);

main() CitireN("Graf.txt",A,n); df_r(1);

1

2 3

4 5

1

2 3

4 5

Figura 9.18.

Page 267: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-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 k≥0 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ă aibă în agendă numă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ţiunile 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ţiuni decizionale. Aici sunt excluse instrucţiunile repetitive. Acestea rezultă în urma instrucţiunilor decizionale şi a ordinii de executare a instrucţiunilor. Pentru a prezenta ordinea în care se execută instrucţiunile, 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 semnificaţia generală că există o relaţie de la i la j (atenţie, nu şi de la j la i).

Pentru exemplul 1, arcul (i,j) are semnificaţia că i are pe agenda telefonică numărul lui j. Pentru exemplul 2, arcul (i,j) are semnificaţia că este necesară cunoaşterea noţiunii 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ă se efectueze instrucţiunea j.

Procedând aşa cum am arătat, obţinem un 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, v2, ..., vn este o mulţime finită de elemente numite vârfuri sau noduri.

A este o mulţime de arce. Vom nota un arc prin perechea ordonată (vi,vj) cu i≠j.

1

2

5

6

3 4

Figura 9.19. Exemplu de graf orientat

Page 268: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

268 Capitolul 9. Introducere în teoria grafurilor

De exemplu, pentru graful din figura 9.19., avem V=1,2,3,4,5,6 şi A=(1,6),(6,1),(6,5),(4,5),(2,1),(2,3).

Observaţii

1. În cazul grafurilor orientate, dacă există arcul (vi,vj) nu înseamnă că există automat şi arcul (vj,vi). Din acest motiv, în definiţie, s-a precizat că un arc este o pereche ordonată de forma (vi,vj).

2. Din definiţie, rezultă că nu există arce de la un nod la el însuşi. Astfel, un arc a fost definit (vi,vj) cu i≠j.

3. Să observăm că mulţimea arcelor A, respectă relaţia A⊂V×V, unde V×V 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,vj) există arcul (vj,vi). Alăturat, puteţi observa un graf neorientat (stânga) reprezentat ca un graf orientat (dreapta). 5. Grafurile orientate se mai numesc şi digrafuri.

Definiţia 9.14. În graful orientat G=(V,A), vârfurile distincte vi,vj∈G sunt adiacente dacă există cel puţin un arc care le uneşte.

Astfel, avem următoarele cazuri:

a) Există numai arcul (vi, vj)∈A - în acest caz spunem că arcul (vi, vj)∈A este incident spre exterior cu vi şi spre interior cu vj.

b) Există numai arcul (vj, vi)∈A - în acest caz spunem că arcul (vj, vi)∈A este incident spre interior cu vi şi spre exterior cu vj.

c) Există arcul (vi, vj)∈A şi arcul (vj,vi)∈A.

Definiţia 9.15. Într-un graf orientat, prin gradul exterior al unui vârf v vom î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).

Pentru vârful i din figura alăturată, avem: d+(i)=3 şi d-(i)=2.

1

2

1

2

Figura 9.20.

i

Figura 9.21.

Page 269: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 269

O relaţie utilă: fie un graf orientat cu n vârfuri şi m arce. Avem relaţia:

m(n)d...(2)d(1)d(n)d...(2)d(1)d =+++=+++ −−−+++ .

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 l înseamnă că numărul persoanei i este pe agenda telefonică a l persoane.

Pentru exemplul 2, dacă gradul exterior al nodului i este k, înseamnă că noţiunea i este necesară pentru înţelegerea a altor k noţiuni, iar dacă gradul interior al nodului i este l, înseamnă pentru a înţelege noţiunea i, este necesar ca alte l 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 l, înseamnă că instrucţiunea i se poate executa după executarea uneia din cele l instrucţiuni.

O relaţie utilă: avem 2)1(

4−nn

grafuri orientate cu n noduri.

Demonstraţia se face prin inducţie. Dacă n=1, 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,v1) sau putem avea ambele arce (v1,v2) şi (v2,v1). În total, avem 4 grafuri orientate, valoare care rezultă şi din formulă, dacă înlocuim n cu 1.

Presupunem formula adevărată, adică dacă sunt n vârfuri, avem

2)1(

4−nn

grafuri orientate. Trebuie să demonstrăm că dacă sunt n+1 vârfuri, avem

2)1(

4+nn

grafuri orientate. Adăugăm vârful n+1. 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+1 noduri este

2)1(

2)1(

2)1(

4444+

+−−

==×nnnnn

nnn

.

Definiţia 9.17. Un graf orientat este complet dacă oricare două vârfuri i şi j (i≠j) sunt adiacente.

Page 270: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

270 Capitolul 9. Introducere în teoria grafurilor

O relaţie utilă: avem 2)1(

3−nn

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ărul 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:

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, ai,j au semnificaţia:

∉∈

=Aj)(i,pentru0,Aj)(i,pentru1,

a ji,

Pentru graful din figura 9.22., matricea de adiacenţă este prezentată alăturat.

Observaţii

1. Întrucât, din modul în care a fost definit graful, rezultă că nu există arce de la un nod la el însuşi, rezultă că elementele de

000000000000010000001000000100010110

6 1 2 1 3 1 5 2 3 3 4 4 5

1

2

5

6

3 4

Figura 9.22.

Fişierul text:

Page 271: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 271

pe diagonala principală reţin 0 (ai,i=0, oricare ar fi i∈1,2,...,n).

2. Matricea de adiacenţă nu este în mod obligatoriu simetrică.

3. Suma elementelor de pe linia i, i ∈1,2,...,n are ca rezultat gradul exterior al nodului i, d+(i).

4. Tot aşa, suma elementelor de pe coloana i, i ∈1,2,...,n are ca rezultat gradul interior al nodului i, d-(i).

5. 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 mulţime de 0.

Subprogramele pe care le utilizăm sunt:

Varianta Pascal Varianta C++

procedure CitireO (Nume_Fis:string; var A:Mat_ad; var n:integer); var f:text; i,j:byte; begin Assign(f,Nume_Fis); Reset(f); Readln(f,n); while(not eof(f)) do begin readln(f,i,j); A[i,j]:=1; end; close(f); end;

void CitireO(char Nume_fis[20], int A[50][50], int& n) int i,j; fstream f(Nume_fis,ios::in); f>>n; while (f>>i>>j) A[i][j]=1; f.close();

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 Varianta C++

procedure Citire_LA_AstaticO (Nume_fis:string;var T:Lista; var Start:pornire; var n:integer); var i,j,k:integer; f:text;

void Citire_LA_AstaticO (char Nume_fis[20], int T[2][50], int Start[50], int& n) int i,j,k=0;

Page 272: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

272 Capitolul 9. Introducere în teoria grafurilor

Figura 9.23. Obţinerea unui

graf parţial

begin k:=0; Assign(f,Nume_Fis); Reset(f); Readln(f,n); while(not eof(f)) do begin readln(f,i,j); k:=k+1; T[0,k]:=j; T[1,k]:=Start[i]; Start[i]:=k; end; close(f); end;

fstream f(Nume_fis,ios::in); f>>n; while (f>>i>>j) k++; T[0][k]=j; T[1][k]=Start[i]; Start[i]=k; f.close();

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 graf G1=(V,A1), unde A1⊆A.

Un graf parţial al unui graf dat, este el însuşi, sau se obţine din G prin

suprimarea anumitor arce.

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 cu m arce?

Definiţia 9.19. Un subgraf al unui graf orientat G=(V,A) este un graf G1=(V1,A1), unde V1⊂V, A1⊂A, iar arcele din A1 sunt toate arcele din A care sunt incidente numai la vârfuri din mulţimea V1.

3

G=(V,A)

1

3 2

4

1

2

4

G1=(V,A1)

rezultă

Page 273: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 273

Figura 9.24. Obţinerea unui

subgraf

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.

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, ceilalţi 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ţiu! 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,v2,...,vp] este o succesiune de vârfuri (v1,v2)∈A, (v2,v3)∈A, ..., (vp-1,vp)∈A. Vârfurile v1 ş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.

G=(V,A)

1

3 2

4

1

3

4

G1=(V1,A1)

rezultă

Page 274: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

274 Capitolul 9. Introducere în teoria grafurilor

în caz contrar dacă există drum de la i la j

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=[v1,v2,...,vp] este o succesiune de vârfuri astfel încât între oricare vârfuri distincte din vi,vi+1 există fie arcul (vi,vi+1), fie arcul (vi+1,vi).

Un lanţ L=[v1,v2,...,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 l-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

Aşa cum am definit pentru grafuri neorientate matricea lanţurilor, similar, se poate forma pentru grafuri orientate matricea drumurilor:

: 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.

( )

=0,1,

ji,D

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)=1. Astfel, se completează linia i.

Exerciţiu! Scrieţi programul care, pornind de la un graf, afişează matricea drumurilor.

1

2

5

4 3

6

7

Figura 9.25.

Page 275: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-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,y∈V, ∃ drum de la x la y şi drum de la y la x. Definiţia 9.21. Subgraful G1=(V1,A1) al grafului G=(V,A) este o componentă tare conexă dacă:

1. ∀ x, y ∈V1, ∃ drum de la x la y şi drum de la y la x.

2. Nu există un alt subgraf al lui G, G2=(V2,A2) cu V1⊂V2 care îndeplineşte condiţia 1.

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ă.

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.

Graful alăturat are patru componente tare conexe:

- subgraful care conţine 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

1

2 3

4

6

5

7

Figura 9.26.

Page 276: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

276 Capitolul 9. Introducere în teoria grafurilor

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 l, există atât drum de la k la l (de la k la i şi de la i la l) cât şi drum de la l la k (de la l la i şi de la i la k). Mulţimea nodurilor cu această proprietate este maximală în raport 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 la i 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 0.

pentru fiecare vârf i

dacă suc[i]=0

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[i]≠pred[i] vor reţine 0;

se incrementează nrc. se afişează vârfurile fiecărei componente conexe.

Mai jos, puteţi observa evoluţia vectorilor suc şi pred:

1

1

1

3

1

2

1

4

0

5

0

6

0

7

1 1 1 0 0 0 0

suc

pred

1

1

1

3

1

2

0

4

0

5

0

6

0

7

1 1 1 0 0 0 0

suc

pred

1

1

1

3

1

2

2

4

0

5

0

6

0

7

1 1 1 2 2 2 0

suc

pred

1

1

1

3

1

2

2

4

0

5

0

6

0

7

1 1 1 2 0 0 0

suc

pred

1

1

1

3

1

2

2

4

3

5

0

6

3

7

1 1 1 2 3 0 3

suc

pred

1

1

1

3

1

2

2

4

3

5

4

6

3

7

1 1 1 2 3 4 3

suc

pred

Page 277: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 277

Programul este prezentat în continuare:

Varianta Pascal Varianta C++

uses grafuri; var suc,pred:array[1..50] of byte; A:mat_ad; n,nrc,i,j:integer;

procedure df_r1(nod:byte); var k:byte; begin suc[nod]:=nrc; for k:=1 to n do if (A[nod,k]=1) and (suc[k]=0) then df_r1(k); end;

procedure df_r2(nod:byte); var k:byte; begin pred[nod]:=nrc; for k:=1 to n do if (A[k,nod]=1) and (pred[k]=0) then df_r2(k); end;

begin CitireO('Graf.txt',A,n); nrc:=1; for i:=1 to n do if suc[i]=0 then begin suc[i]:=nrc; df_r1(i); df_r2(i); for j:=1 to n do if suc[j]<>pred[j] then begin suc[j]:=0; pred[j]:=0; end; nrc:=nrc+1 end; for i:=1 to nrc-1 do begin writeln ('Componenta ',i); for j:=1 to n do if suc[j]=i then write (j,' '); writeln; end; end.

#include "grafuri.cpp" int suc[50], pred[50], A[50][50], n,nrc,i,j; void df_r1(int nod) int k; suc[nod]=nrc; for (k=1;k<=n;k++) if (A[nod][k]==1 && suc[k]==0) df_r1(k); void df_r2(int nod) int k; pred[nod]=nrc; for (k=1;k<=n;k++) if (A[k][nod]==1 && pred[k]==0) df_r2(k); main() CitireO("Graf.txt",A,n); nrc=1; for (i=1;i<=n;i++) if (suc[i]==0) suc[i]=nrc; df_r1(i); df_r2(i); for (j=1;j<=n;j++) if(suc[j]!=pred[j]) suc[j]=pred[j]=0; nrc++; for (i=1;i<nrc;i++) cout<<"Componenta"<<i<<endl; for (j=1;j<=n;j++) if (suc[j]==i)

cout<<j<<" "; cout<<endl;

Page 278: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

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ă ∀x,y∈V, ∃ lanţ de la x la y. Definiţia 9.23. Subgraful G1=(V1,A1) al grafului G=(V,A) este o componentă conexă dacă:

1. ∀ x, y ∈V1, ∃ lanţ de la x la y.

2. Nu există un alt subgraf al lui G, G2=(V2,A2) cu V1⊂V2 care îndepli-neşte condiţia 1.

Probleme propuse

1. O cunoştinţă mi-a zis: la mine în birou suntem 5 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 persoană are cel puţin un prieten. 2. Fiecare persoană este prietenă cu oricare alta din grup. 3. Există persoane care nu au decât un singur prieten. 4. Există k persoane din grup astfel încât oricare dintre ele este prietenă cu toate celelalte k-1.

a. Graful asociat are noduri terminale. b. Graful asociat conţine un subgraf complet cu k noduri.

c. Graful asociat este complet.

d. Graful asociat nu are noduri izolate.

4. Se dau n drepte şi m relaţii de forma: di||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.

Page 279: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 279

1. Orice firmă colaborează cu toate celelalte. 2. Anumite firme îşi întrerup relaţia de colaborare. 3. Anumite firme dau faliment şi nu mai funcţionează. 4. O firmă colaborează cu toate celelalte. 5. Cu ajutorul unor firme interme-diare dintre cel n firme, firma i poate face afaceri cu firma j.

a. Există un lanţ de la i la j.

b. Graful asociat are un nod de grad n-1.

c. Graful asociat se transformă într-un graf parţial al său. d. Graful asociat este complet.

e. Graful asociat se transformă într-un subgraf al său.

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ărul 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 bazine 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; c) În cazul în care toate persoanele lucrează într-o firmă, unele persoane din grup sunt şefi pentru alte persoane din grup; d) Unele persoane din grup sunt prietene cu alte persoane din grup.

Exerciţiile 8 şi 9 se referă la graful din figura 9.27. 8. Care este matricea de adiacenţă a grafului?

1101000110000100100001001101000110100000110101111

0101000100000100000001000101000100100000010101110

0101000100000101000001000101000100100000010101110

0101000100000111111111000101000100100000010101110

a) b) c) d)

1 2

3

4 5

6 7

Figura 9.27.

Page 280: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

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] este un ciclu.

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.

9.7. Nodul 1 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 subgraf al grafului din problemă.

10. Care dintre matricele de mai jos ar putea fi matricea de adiacenţă a grafului alăturat?

11. Care dintre matricele de mai jos poate fi matricea de adiacenţă a unui graf neorientat?

a) b)

0110010101110100010101010

0010000101110100010101010

0100011101010100010101010

0111010100110101010100010

c) d)

0110011101110100010101010

a)

0110010101110100010100010

1111111111111111111111111

0110010101110100010101010

b) c) d)

1 2

3

4 6

7 Figura 9.28.

1

2

3

4

2 3 4

1

1 4

1 3

Figura 9.29.

Figura 9.30.

Page 281: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 281

12. Câte muchii are graful neorientat reprezentat de matricea de adiacenţă alăturată? 13. Care este numărul maxim de componente conexe pe care le poate avea un graf neorientat cu 5 noduri şi 4 muchii? 14. Care dintre matricele de mai jos este matricea de adiacenţă a unui subgraf al grafului din figura 9.31?

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 0 pe care îl reţine matricea de adiacenţă a unui graf neorientat cu n noduri şi m muchii?

17. Care este numărul minim şi numărul maxim de noduri izolate pe care îl poate avea un graf neorientat cu 10 noduri şi 10 muchii?

18. Care este numărul de grafuri neorientate cu 5 noduri?

19. Precizaţi care dintre afirmaţiile urmă-toare sunt adevărate şi care sunt false. Toate afirmaţiile se referă la graful din figura 9.32.

19.1. "6 1 2 3 5 4 7" reprezintă o parcurgere în adâncime a grafului. 19.2. "3 2 5 1 4 6 7" reprezintă o parcurgere în lăţime a grafului.

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.

0111110111110001100111010

0000000000000000

1000000000010010

0000000100010110

0111101111011110

a) b) c) d)

Figura 9.31.

1 2

3

4 5

6 7

Figura 9.32.

Page 282: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

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. Există 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 dă un graf neorientat memorat sub forma matricei de adiacenţă. 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ţă în matrice de adiacenţă.

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 listă liniară simplu înlănţuită implementată static.

Page 283: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-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 în 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ţimi, este o relaţie de echivalenţă dacă respectă următoarele trei condiţii:

• oricare ar fi x∈A, x R x (x este echivalent cu x), proprietate numită reflexivitate;

• oricare ar fi x,y∈A, din x R y, rezultă y R x, proprietate numită simetrie;

• oricare ar fi x,y,z∈ mulţimii A, din x R y şi y R z, rezultă x R z, proprietate numită tranzitivitate.

Se citeşte o mulţime de numere între 0 şi 255 prin citirea a n 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 5), (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: Pi(xi,yi) cu 0≤xi,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 vizualizeze 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 la i la j.

1. Persoana i nu a împrumutat cu bani alte persoane din grup. 2. Persoana i nu a împrumutat bani de la alte persoane din grup.

a) gradul interior al nodului i este 0.

b) gradul exterior al nodului i este 0.

Page 284: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

284 Capitolul 9. Introducere în teoria grafurilor

34. Se dau n mulţimi de numere naturale: A1, A2...An. Acestor mulţimi li se asociază un graf orientat astfel: dacă mulţimea Ai este inclusă în mulţimea Aj, în graful asociat vom avea arcul (Ai,Aj). Nu vom considera cazul de incluziune a unei mulţimi în ea însăşi. Stabiliţi corespondenţa dintre operaţiile din stânga şi cele din dreapta.

1. Ai⊂Aj⊂Ak.

2. Ai⊂Aj; Ak⊂Aj. 3. A1⊂A2⊂... An-1⊂An.

4. A1=A2=... An-1=An.

a) De la A1 la An există un lanţ de lungime n-1.

b) De la Ak la Ai există un lanţ de lungime 2.

c) Graful este tare conex.

d) De la Ai la Ak 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 0; d) 0 0.

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) 1 2.

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) 1. 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.

1

3

2

Figura 9.33.

1

3

2

4

Figura 9.34.

1

3 4

5

2

Figura 9.35.

Page 285: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-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 adiacenţă ş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 matricea de adiacenţă ş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 neorientat. 46. Se dau listele de adiacenţ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. 48. 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 0 în caz contrar. Să se afişeze distanţele minime de la camera de coordonate (l,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(X1,Y1) şi B(X2,Y2) de pe tablă şi, în caz afirmativ, să se tipărească numărul minim de mutări necesare. Se citesc: N, X1, 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

Page 286: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

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 găsesc î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 3 1 2 2 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. ! Este 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 muchii. 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 di||dj şi dj||dk ⇒ di||dk rezultă că toate dreptele sunt paralele între ele. 5. 1-d, 2-e, 3-c, 4-b, 5-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 graful să fie conex. Cum are 3 componente conexe, sunt suficiente 2 ţevi.

Programul va afişa: 1 2 2 3

Page 287: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-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 în 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. F; 9.5. F; 9.6. A; 9.7. F; 9.8. A; 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, eliminaţ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.

16. n2-2m. Matricea de adiacenţă are n2 elemente. Am văzut faptul că suma tuturor cifrelor de 1 (adică a gradelor vârfurilor) este 2m (unde m este numărul de muchii). 17. 0 şi 5.

18. 210.

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)=1 A(i,l)=1

A(k,j)=1 A (k,l)=1

Page 288: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

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)=1. 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)=0, 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. Deducem, astfel, că marcarea este corectă. Algoritmul se termină când coada este vidă. În 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 afiş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, i2, ..., ik sunt unite printr-un drum: atunci interschimbările (i1, i2), (i2, i3), ..., (ik-1, ik), (ik-1, ik-2), ..., (i2, i1) inversează conţinuturile elementelor de indice i1 şi ik, lăsând conţinuturile celorlalte elemente de indici i2, ..., ik-1 nemodificate. O parcurgere în lăţime determină distanţa minimă între două noduri.

Page 289: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

289

Anexa 1

Memento

A.1. Limbajul Pascal

A.1.1. Tipuri standard

A) Tipuri întregi

Nume tip Semnificaţie Ocupă (biţi) Valori admise

shortint întreg scurt 8 de la -128 la 127

integer întreg 16 de la -32768 la 32767

longint întreg lung 32 de la -2147483648 la 2147483647

byte număr natural scurt 8 de la 0 la 255

word cuvânt 16 de la 0 la 65535

B) Tipul caracter - un caracter se notează între apostrofuri.

Exemple: 'a','A','1' (caracterul '1' nu trebuie confundat cu numărul 1). C) Tipuri reale

Nume tip Semnificaţie Ocupă (biţi) Valori admise (în modul)

real virgulă mobilă, simplă precizie 48 [-1,7×1038, -2,9×10-29] ∪

[2,9×10-29, 1,7×1038]

single virgulă mobilă, simplă precizie 32 [-3,4×1038, -1,5×10-45] ∪

[1.5×10-45, 3,4×1038]

double

virgulă mobilă, dublă precizie 64 [-1,7×10308, -5×10-324] ∪

[5×10-324, 1,7×10308]

extended virgulă mobilă, format lung 80 [-1,1×104932, -3,4×10-4932]

∪ [3,4×10-4932, 1,1×104932]

comp virgulă mobilă 64 [-9,2×1018, 9,2×1018]

D) Tipul logic – boolean – poate reţine doar două valori true şi false.

Page 290: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

290 Anexa 1. Memento

A.1.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 numerelor reale se găseşte în intervalul [3.4∗10-4352,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'. D) 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 = expresie; ... identificator = expresie;

Expresiile care intervin în definirea constantelor trebuie să poată fi evaluate la momentul compilării programului.

Exemplu: const NrMaxValori = 30; Dim = NrMaxValori*2-1; Mesaj = 'Nu exista solutie'#10#13; PI = 3.14;

A.1.3. Operatori

A.1.3.1. Prioritatea operatorilor

Iată 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.

Page 291: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 291

Grupa 3 (operatorii din această grupă se numesc şi operatori aditivi): 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 binari ( +, -, *, /, DIV, MOD). Operatorul +. Are semnificaţia de adunare. Operanzii săi sunt de tip întreg

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.

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 semnificaţia de înmulţire. Operanzii sunt de tip întreg sau

real. Dacă cel puţin unul din operanzi este de tip real, rezultatul este de tip real, altfel rezultatul este de tip întreg.

Operatorul /. Are semnificaţia de împărţire. Operanzii pot fi de tip întreg sau

real dar, întotdeauna rezultatul este de tip real. Operatorul DIV. Are semnificaţia de împărţire întreagă. Operanzii sunt în

mod obligatoriu de tip întreg. Este obligatoriu ca fiecare operand să fie separat cu cel puţin un spaţiu de operator. Rezultatul este de tip întreg.

Operatorul DIV 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 -).

Page 292: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

292 Anexa 1. Memento

Operatorul MOD. Are semnificaţia de rest al împărţirii 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 DIV, rezultatul este corect numai dacă ambii operanzi sunt pozitivi. Fie a MOD b. Rezultatul se obţine astfel:

a - a DIV b (R=D-Î×C).

A.1.3.3. Operatori relaţionali

Operatorul < (mai mic). 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 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 FALSE.

A.1.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 simplă: rezultatul este TRUE numai dacă ambii operanzi au valoarea TRUE (altfel rezultatul este FALSE).

Operatorul OR (sau). Regula este simplă: 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.

Page 293: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 293

Dacă operanzii sunt de tip întreg, se pleacă de la reprezentarea binară a numerelor. Operatorul NOT (negare) este unar. Transformă toţi biţii 1 în 0 şi invers. Operatorul AND (ŞI) este binar. Se face Ş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 0.

Operatorul 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 0.

Operatorul 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 0.

A.1.4. Instrucţiuni 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:=expresie, unde v este numele unei variabile.

Principiul de executare este urmă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ţiunea IF. Există două forme ale acestei instrucţiuni:

Forma 1.

IF expresie logică THEN instrucţiune1 ELSE instrucţiune2

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.

Page 294: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

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. Instrucţiunea 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; p1,[p2,...,pn2]: instrucţiune2; z1,[z2,...,znp]: instrucţiunep [ELSE instrucţiune] END

Aici, c1,...,cn1,...,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:

WHILE 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 expresia, dacă aceasta are valoarea TRUE se execută din nou instrucţiunea; procedeul continuă până când, la evaluarea expresiei, se obţine FALSE.

Page 295: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 295

7. Instrucţiunea REPEAT. Această instrucţiune reproduce structura REPEAT UNTIL şi are forma generală:

REPEAT i1; i2; ... in UNTIL expresie logică

Aici, i1, i2, ..., 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ă := expresie1 TO expresie2 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. Pasul 2. 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ă în 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.

Page 296: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

296 Anexa 1. Memento

Pasul 2.2. Se execută instrucţiunea subordonată.

Pasul 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 la 2.2.

• Dacă valoarea reţinută de variabila de ciclare este egală cu valoarea obţinută în urma evaluării expresiei 2, executarea instrucţiunii FOR se încheie.

Forma 2.

FOR variabilă := expresie1 DOWNTO expresie2 DO instrucţiune

În acest caz, variabila de ciclare scade la fiecare pas.

A.1.5. Câteva funcţii utile

sinus:ℜ→[-1,1]; are forma generală:

function Sin(X: Real): Real; cosinus:ℜ→[-1,1]; are forma generală:

function Cos(X: Real): Real; cosinus (x) arctangent:ℜ→(-π/2,π/2); are forma generală:

function ArcTan(X: Real): Real; arctangent (x)

Atenţie: argumentul trebuie exprimat în radiani. Celelalte funcţii uzuale se obţin prin aplicarea formulelor trigonometrice.

Funcţia exponenţială f:ℜ→ℜ+, unde f(x)=ex (e este un număr iraţional,

e≈2.71) are forma generală:

function Exp(X: Real): Real; Funcţia logaritmică f:ℜ+→ℜ, 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:

ln(A).Bln(Aln(B);ln(A)B)ln(A:avem 0,B A,pentru B ⋅=+=⋅> )

Page 297: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 297

Din faptul că funcţia logaritmică este inversa funcţiei exponenţiale şi din a doua relaţie de mai sus, deducem pentru x>0, y oarecare:

( ) ln(x)yxlny eexy ⋅== .

Aceasta înseamnă că putem calcula xy - pentru că funcţia putere nu există în Pascal: exp(y*ln(x)). Funcţia pătratică f:ℜ→ℜ+, unde f(x)=x2:

function Sqr(X: Real): Real; unde x este o valoare întreagă sau reală. Funcţia radical f:ℜ+→ℜ+, unde: xf(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.0, int(-2.75) returnează -2.0.

Funcţia de trunchiere function Trunc(X: Real): Longint; returnează valoarea trunchiată a argumentului. Funcţia „parte fracţionară” 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ă x .

Page 298: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

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

Nume tip Semnificaţie Ocupă (biţi) Valori admise

unsigned char caracter fără semn 8 de la 0 la 255

char caracter 8 de la -128 la 127 unsigned

int întreg fără semn 16 de la 0 la 65535

int întreg 16 de la -32768 la 32767

unsigned long

întreg lung fără semn 32 de la 0 la 4.294.967.295

long întreg lung cu semn 32 de la -2.147.483.648 la 2.147.483.647

B) Tipuri reale

Nume tip Semnificaţie Ocupă (biţi) Valori admise (în modul)

float virgulă mobilă, simplă precizie 32 [3.4 ×10-38,3.4×1038]

double virgulă mobilă, dublă precizie 64 [1.7×10-308 ,1.7×10+308]

long double

virgulă mobilă, dublă precizie

format lung 80 [3.4×10-4932, 1.1 ×10+4932]

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 0 nesemnificativ. Exemplu: 0123. Se reţine numărul întreg 123(8).

• hexazecimale (în baza 16). Acestea sunt precedate de 0X sau 0x. Exemplu: pentru 0X1A2 adică 1A2(16) sau 0x1a2, adică 1A2(16).

Page 299: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 299

2. Constante caracter. Acestea se trec între două caractere apostrof ('). Exemple: 'A', '1', 'a'. 3. Secvenţe escape. O secvenţă escape începe prin caracterul '\' (backslash). Să considerăm o constantă caracter 'a'. Codul său este 97(10)=141(8)=61(16). 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' sau '\x61'. În cazul când se foloseşte codul scris în baza 16, acesta este precedat de caracterul 'x'. Uneori, pentru anumite caractere, se pot utiliza şi semne speciale, aşa cum rezultă din exemplele următoare.

• backslash: '\\','\134','\x5c'; • newline: '\n','\12','\xa'; • apostrof: '\'','\47','\x27'; • bel: '\a','\7','\x7'; • cr: '\r','\15','\xd'.

4. Caractere albe (whitespaces). 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, 0.3, -2.5E-12, adică, 2.5×10-12.

6. Constante şir de caractere

Exemplu: "'acesta este un 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.

Page 300: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

300 Anexa 1. Memento

A.2.3. Operatori

A.2.3.1. Prioritatea operatorilor (în ordine descrescătoare)

Priviţi următorul tabel:

dssdsddsdsdsdsdsdsdsdsdsdsdssddeletenewsizeoftypecastds

→→>>=<<=====−=+===→→→→→→→===→>=><=<→>><<→−+→→>−→−−++−+→>−

,16|^&/*15

:?14||13

&&12|11^10&9

!8765

%/*4**.3

)(*~!2.::][)(1

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; • / (binar), pentru împărţire; • % (binar), restul împărţirii întregi.

prioritate operator asociativitate

Page 301: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 301

Observaţii

1. Operatorul ”/” (împărţire) acţionează în mod diferit în funcţie de operanzi:

a) dacă ambii sunt de tip întreg, rezultatul este întreg ş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 întregi, se procedează astfel:

a) se face împărţirea întreagă a celor două valori care sunt considerate în 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ă şi 0, în caz contrar.

A.2.3.4. Operatori de egalitate

Aceştia sunt: • == pentru egalitate;

• != pentru inegalitate.

În cazul în care relaţia indicată de operator este respectată, expresia returnează 1, altfel returnează 0.

Page 302: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

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 în faţa operandului) sau postfixaţi (aplicaţi după operand).

Dacă operatorul este prefixat, variabila este incrementată (decrementată) înainte ca valoarea reţinută de ea să intre în calcul.

Dacă operatorul este postfixat, variabila este incrementată (decrementată) după ce valoarea reţinută de ea intră în calcul.

A.2.3.6. Operatori logici

Există trei operatori logici:

• ! - negare logică; • && - şi logic; • || - sau logic.

Operatorul negare logică acţionează astfel: dacă operandul este o valoare diferită de 0, rezultatul este 0, altfel rezultatul este 1.

Operatorul şi logic (binar) acţionează astfel: dacă ambii operanzi sunt diferiţi de 0, rezultatul este 1, altfel el este 0.

Operatorul sau logic (binar) acţionează astfel: dacă cel puţin unul din operanzi este o valoare diferită de 0, rezultatul este 1, altfel rezultatul este 0.

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; • | sau pe biţi; • ^ sau exclusiv pe biţi; • ~ negare pe biţi (operator unar).

Aceşti operatori acţionează numai asupra operanzilor de tip întreg.

Page 303: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 303

Operatorul ”<<” este binar. El are rolul de a deplasa către stânga conţinutul tuturor biţilor operandului din stânga sa, cu un număr de poziţii egal cu valoarea reţinută de al doilea operand. Poziţiile rămase libere (în dreapta) vor reţine valoarea 0.

Operatorul ”>> ” este binar. El are rolul de a deplasa către dreapta conţinutul tuturor biţilor operandului din stânga 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 stânga) vor reţine valoarea 0. Dacă al doilea operand reţine valoarea m, o astfel de deplasare este echivalentă cu împărţirea întreagă cu 2m. Î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 ”& ”, ”|” , ”^”, 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.

OP1 OP2 OP1&OP2 OP1^OP2 OP1|OP2 0 0 0 0 0

1 0 0 1 1

0 1 0 1 1

1 1 1 0 1

Operatorul ”~” (negare pe biţi) are rolul de a inversa conţinutul biţilor (dacă

un bit conţine 0, 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 într-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:

v=v1=v2=...=vn=expresie

unde v, v1, …, vn sunt variabile.

Page 304: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

304 Anexa 1. Memento

În acest caz, principiul de executare este următorul:

• se evaluează expresia; • valoarea obţinută este atribuită variabilei vn (eventual convertită - dacă

este cazul); • conţinutul variabilei vn este atribuit variabilei vn-1 (eventual, se

efectuează conversia necesară); • . . .

• conţinutul variabilei v1 este atribuit variabilei v (eventual, se efectuează conversia necesară).

Pentru atribuiri se mai pot utiliza şi operatorii: ”*=”, ”/=”, ”%=”, ”+=”, ”-=”, ”<<=”, ”>>=”, ”&>”, ”^=” sau ”|=”.

O atribuire de forma: v op expresie, are acelaşi rezultat ca

v=v op expresie

(diferenţa este că, în primul caz, se generează un cod maşină eficient).

A.2.3.9. Operatorul ',' (virgulă)

C++ permite programatorilor să scrie mai multe expresii separate prin virgulă, ca mai jos:

exp1,exp2, ..., expn;

Întrucât, după cum rezultă din tabel, operatorul virgulă se asociază de la stânga la dreapta, expresiile se evaluează î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ţinută în urma evaluării ultimei expresii (evident, tipul acestei valori este şi tipul expresiei).

A.2.3.10. Operatorul condiţional

Se foloseşte în expresii de genul: exp1?exp2:exp3

Principiul de executare este următorul:

• se evaluează exp1;

• dacă aceasta produce o valoare diferită de 0, se evaluează exp2 şi exp3 este ignorată (nu se evaluează);

• altfel, se evaluează exp3 şi exp2 este ignorată. În ansamblu, expresia este de tipul lui exp2 sau exp3 şi produce valoarea

exp2 sau exp3 (în funcţie de cea care se evaluează).

Page 305: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-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++ şi o atribuire este o expresie. 2. Instrucţiunea ”if” se poate utiliza în următoarele două forme:

Forma 1.

if (expresie) instrucţiune1 else instrucţiune2

Principiul de executare este următorul:

• se evaluează expresia; • dacă valoarea produsă de aceasta este diferită de 0, se execută

instrucţiune1; • dacă valoarea produsă este 0 se execută instrucţiune2.

Forma 2.

if (expresie) instrucţiune

Principiul de executare este următorul:

• se evaluează expresia; • dacă valoarea produsă de aceasta este diferită de 0, se execută

instrucţiunea subordonată.

Page 306: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

306 Anexa 1. Memento

3. Instrucţiunea compusă. Se utilizează în cazul în care se doreşte ca mai multe instrucţiuni să fie tratate de compilator ca o singură instrucţiune. Este de forma de mai jos, unde i1, i2, ..., in sunt instrucţiuni:

i1; i2; : : in;

4. Instrucţiunea ”switch” are forma generală:

switch (expresie) case exp1: secvenţă instrucţiuni1; break; case exp2: secvenţă instrucţiuni2; break; ................................. case expn: secvenţă instrucţiunin; break; [default: secvenţă instrucţiunin+1];

unde:

− expresie are semnificaţia: expresie de tip întreg; − expi sunt expresii constante de tip întreg; − instrucţiunii reprezintă o secvenţă oarecare de instrucţiuni.

Principiul de executare:

• se evaluează expresia;

• dacă aceasta produce o valoare egală cu cea produsă de expi, se execută, în ordine, instrucţiunii şi se trece la instrucţiunea următoare, altfel se execută numai secvenţa instrucţiunin+1.

Alternativa default este facultativă. În absenţă, în cazul în care nu există

coincidenţă de valori, se trece la instrucţiunea 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 0, se execută instrucţiunea subordonată, apoi se revine la pasul 1, altfel se trece la instrucţiunea următoare.

Page 307: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-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 0, execuţia instrucţiunii do se termină, altfel se trece la pasul 1.

7. Instrucţiunea ”for” are forma generală:

for (expresieiniţializare; expresietest; expresieincrementare) instrucţiune După cum se observă, între paranteze se găsesc trei expresii:

− expresieinitializare 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ă).

− expresietest se foloseşte pentru a testa dacă se execută instrucţiunea subordonată - dacă expresia produce la evaluare o valoare diferită de 0, instrucţiunea subordonată for se execută.

− expresieincrementare se foloseşte pentru incrementarea variabilei de ciclare.

Principiul de executare:

• pasul 1: se evaluează expresieiniţializare (un caz special este acela în care aceasta conţine şi declaraţia variabilei de ciclare);

• pasul 2: se evaluează expresiatest. În cazul în care aceasta produce o valoare diferită de 0, 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 x (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).

Page 308: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

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 ].,0[]1,1[:)arccos( π→−x

Funcţia asin are forma generală: double asin(double x); şi

calculează valoarea funcţiei ].2

,2

[]1,1[:)arcsin( ππ−→−x

Funcţia atan are forma generală: double atan(double x); şi

calculează valoarea funcţiei ).2

,2

(:)( ππ−→ℜxarctg

Funcţia atan2 are forma generală: double atan2(double y,double x)

şi calculează ).(xyarctg Rezultatul este în intervalul ).,( ππ− Motivul?

Funcţia floor are forma generală double floor(double x); şi calculează valoarea rotunjită a lui x (rotunjirea se face în minus).

Exemple: floor (123.78)=123, floor (-23,34)=-24.

Funcţia ceil are forma generală double ceil(double 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); şi calculează valoarea funcţiei ].1,1[:)cos( −→ℜx

Funcţia sin are forma generală double sin(double x); şi calculează valoarea funcţiei ].1,1[:)sin( −→ℜx

Funcţia tan are forma generală double tan(double x); şi calculează

valoarea funcţiei .2

:)( ℜ→

Ζ∈+⋅−ℜ kkxtg ππ

Funcţia exp are forma generală double exp(double x); şi calculează funcţia *: +ℜ→ℜxe .

Funcţia log are forma generală double log(double x); şi calculează funcţia )(log)ln(,:)ln( * xxundex e=ℜ→ℜ+ .

Funcţia log10 are forma generală double log10(double x); şi calculează funcţia ).(log)lg(,:)lg( 10

* xxundex =ℜ→ℜ+

Funcţia pow are forma generală double pow(double x, double y); şi calculează yx .

Page 309: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

309

Figura A.1. Exemplu de reţea de calculatoare legată la Internet

Internet

Router

Switch 1 Switch 2

Subreţeaua 1 Subreţeaua 2

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, în 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.1. Reţele de comunicaţie

Comunicaţia între diversele dispozitive electronice din zilele noastre reprezintă poate cea mai răspândită 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. În continuare, vom prezenta o schemă de principiu care descrie o reţea de calculatoare, legată la Internet:

Page 310: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

310 Anexa 2 - Aplicaţii practice ale grafurilor

Observaţii Structura anterioară este de tip arbore. Pe fiecare nivel însă, protocoalele de

comunicaţie efectuează operaţii specifice pentru asigurarea transmisiei bidirecţionale între fiecare dispozitiv terminal (calculator).

Router-ul este un dispozitiv electronic care decide calea (drumul optim) pe

care vor fi trimise informaţiile de la un calculator din Subreţeaua 1, către un altul din Subreţeaua 2. La nivel local, Switch-ul decide la rândul său, în funcţie de adresa MAC (Media Access Control, identificator unic pe glob) a fiecărei 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 calculatoare se poate reprezenta sub forma unui graf. Comunicaţia optimă (calea cea mai scurtă între două noduri) este realizată cu ajutorul protocoalelor specializate de routare, cum ar fi: IP (Internet Protocol), NAT (Network Address Translation), RIP (Routing Information Protocol), etc.

Protocoale de routare

Un protocol de routare are rolul de a obţine şi de a trimite informaţiile topologice ale reţelei către Router-e, permiţându-le acestora să ia decizii la nivel local. Fiecare Router deţine o serie de liste, numite tabele de routare, în care sunt memorate adresele (fizice şi logice) tuturor nodurilor care au legătură fizică directă cu el şi drumurile optime deja cunoscute şi parcurse. Aceste liste trebuie reactualizate frecvent pentru a preveni anumite modificări topologice ale reţelei.

Router-ele utilizează protocoalele de comunicaţie care au la bază algoritmi

de optimizare ce trebuie să determine cea mai bună cale. Când ne referim la drumul cel mai bun, avem în vedere numărul de “hopuri” (din engleză, ”hops”) pe care trebuie să le parcurgă datele până la destinaţie sau un alt punct intermediar sau durata/viteza de trimitere a informaţiilor.

Există două tipuri de algoritmi de routare mai importante, utilizate în funcţie

de modalitatea router-ului de a reţine şi de a analiza informaţiile structurale ale reţelei:

• Algoritmi de routare globali. Fiecare router reţine toate informaţiile despre celelalte router-e existente în 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 destinatarii (mesaj de tip broadcast). Fiecare router îi va răspunde cu un mesaj în 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ţându-se un mesaj de tip echo (“ecou”) către router-ele determinate anterior. Răspunsul primit de la

Page 311: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 311

Figura A.2. Exemplu de reţea

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. În 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 în 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:

î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, împă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 încât obiectivele propuse să se atingă în mod optim şi la timp.

Router 1 Router 2 Router 3

L1 L2

Page 312: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

312 Anexa 2 - Aplicaţii practice ale grafurilor

Figura A.3. Exemplu de graf de activităţi

O aplicaţie foarte răspândită a grafurilor orientate o constituie simularea proiectelor complexe ce presupun o multitudine de activităţi distincte, efectuate în 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 execuţie a întregii lucrări. Acesta reprezintă drumul cel mai lung de la faza iniţială 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ăţii. Exemple: proiectarea unei componente, implementarea unui algoritm, etc. În 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 paralel

: mai multe activităţi desfăşurate în 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.

Page 313: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 313

În figura A.3, drumul critic este format din nodurile: 1, 2, 7, 5 şi 6. Timpul 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, în 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 Vi (vârfurile) evenimentele şi cu A[i,j] (arcul de la Vi la Vj) activităţile. Vom defini:

ti - data aşteptată a unui eveniment Vi ca fiind drumul cel mai lung de la V1 la Vi (cea mai mare distanţă);

ti* - data limită a unui eveniment Vi ca fiind diferenţa între tn (data aşteptată a lui Vn) şi drumul maxim de la Vi la Vn.

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 în 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.

Cele două valori asociate evenimentului Vi determină un interval de fluctuaţie, notat cu [ti, ti*], ce specifică perioada de timp în care poate avea loc evenimentul Vi, fără a schimba timpul total asociat proiectului (drumul critic).

În urma unor calcule uşoare, se poate observa că pentru toate evenimentele ce aparţin drumului critic, ti = ti*. Considerând cunoscute toate datele aşteptate şi cele limită pentru graf,

definim în continuare două noţiuni privitoare la arce: ML[i,j] – marginea liberă a unei activităţi, ca fiind tj-ti-d(A[i,j]), ce

semnifică durata cu care se poate întârzia începerea activităţii A[i,j], fără a modifica data de aşteptare a evenimentului Vj;

MT[i,j] – marginea totală a unei activităţi, ca fiind tj*-ti-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 Vj.

Arcele ce formează drumul critic au aceste două valori nule (nu le este permisă nici o întârziere).

Page 314: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

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 lucră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ǎ reprezintǎ 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 chimicǎ ş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 rep rezintǎ atomii, iar arcele semnificǎ legǎturile dintre atomi.

Mai jos, este prezentat un exemplu de graf molecular neorientat pentru o hidrocarburǎ (lipC4):

Pentru graful neorientat prezentat anterior, se pot asocia urmǎtoarele trei matrice:

7 5

2 1

3

4

6

Figura A.4. Exemplu de graf molecular asociat

Page 315: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

Manual de informatică pentru clasa a XI-a 315

0 0 1 0 0 0 0

0 0 1 0 0 0 0

1 1 0 0 0 0 1

0 0 0 0 1 0 1

0 0 0 1 0 1 0

0 0 0 0 1 0 1

0 0 1 1 0 1 0

0 2 1 3 4 3 2

2 0 1 3 4 3 2

1 1 0 2 3 2 1

3 3 2 0 1 2 1

4 4 3 1 0 1 2

3 3 2 2 1 0 1

2 2 1 1 2 1 0

0 2 1 5 4 5 2

2 0 1 5 4 5 2

1 1 0 4 3 4 1

5 5 4 0 3 2 3

4 4 3 3 0 3 2

5 5 4 2 3 0 3

2 2 1 3 2 3 0

matricea de adiacenţǎ matricea drumurilor matricea drumurilor minime (distanţa) maxime (Detour)

Observăm faptul că acest graf asociat conţine cicluri, lucru obişnuit în structurile moleculare. Drumurile maxime sunt totuşi determinate, cu precizarea că în programele specializate se evită ciclarea algoritmului prin utilizarea unei condiţii de stop.

Dupǎ ce au fost determinate aceste matrice, mai multe caracteristici topologice importante pot fi obţinute direct. Prezentǎm doar douǎ dintre ele:

- indicele de drum (Detour) – se obţine din matricea drumurilor maxime:

∑∑= =

∆⋅=n

i

n

jij

1 121 )(ω ,

unde ij)(∆ este un element al matricei Detour.

- indicele Weiner – introdus în anul 1947 de chimistul Harry Weiner pentru a studia structura molecularǎ:

∑∑= =

⋅=n

i

n

jijdGW

1 121 )()( ,

unde ijd )( reprezintǎ un element al matricei drumurilor minime.

Existǎ mai mult de 400 de astfel de indici topologici şi sunt folosiţi în determinarea similaritǎţ ilor structurale între molecule, extrem de utile în analiza moleculară.

Exemplul prezentat anterior este foarte simplu, dar imaginaţi-vǎ cât de uşor poate fi pentru un chimist sǎ analizeze aceste date, pentru o formulǎ chimicǎ complexǎ, simulând totul direct pe calculator… Teoria grafurilor este folositǎ cu succes în chimie şi geneticǎ, iar simularea experimentelor cu aj utorul unui PC diminueazǎ considerabil timpul necesar de lucru.

Page 316: Doru Popescu Anastasiu · Vlad Huţanu Tudor Sorin. INFORMATICĂ (filiera teoretică, profilul real, specializarea . matematică-informatică) şi (filiera vocaţională, profil

316

Anexa 3

Tabela codurilor ASCII

Cod Caracter Cod Caracter Cod Caracter Cod Caracter Cod Caracter Cod Caracter 000 (nul) 022 (syn) 044 , 066 B 088 X 110 n 001 (soh) 023 ¥ (etb) 045 - 067 C 089 Y 111 o 002 (stx) 024 ↑ (can) 046 . 068 D 090 Z 112 p 003 ♥ (etx) 025 ↓ (em) 047 / 069 E 091 [ 113 q 004 ♦ (eot) 026 → (eof) 048 0 070 F 092 \ 114 r 005 ♣ (enq) 027 ← (esc) 049 1 071 G 093 ] 115 s 006 ♠ (ack) 028 (fs) 050 2 072 H 094 ^ 116 t 007 • (bel) 029 ↔ (gs) 051 3 073 I 095 _ 117 u 008 _ (bs) 030 (rs) 052 4 074 J 096 ` 118 v 009 (tab) 031 (us) 053 5 075 K 097 a 119 w 010 (lf) 032 (spaţiu) 054 6 076 L 098 b 120 x 011 (vt) 033 ! 055 7 077 M 099 c 121 y 012 (np) 034 " 056 8 078 N 100 d 122 z 013 (cr) 035 # 057 9 079 O 101 e 123 014 (so) 036 $ 058 : 080 P 102 f 124 | 015 (si) 037 % 059 ; 081 Q 103 g 125 016 (dle) 038 & 060 < 082 R 104 h 126 ~ 017 (dc1) 039 ' 061 = 083 S 105 i 127 018 (dc2) 040 ( 062 > 084 T 106 j 019 ‼ (dc3) 041 ) 063 ? 085 U 107 k 020 ¶ (dc4) 042 * 064 @ 086 V 108 l 021 § (nak) 043 + 065 A 087 W 109 m

Codul ASCII extins

Cod Caracter Cod Caracter Cod Caracter Cod Caracter Cod Caracter Cod Caracter 128 Ç 149 ò 170 ¬ 191 212 233 Θ 129 ü 150 û 171 ½ 197 213 234 Ω 130 é 151 ù 172 ¼ 193 214 235 δ 131 â 152 _ 173 ¡ 194 215 236 ∞ 132 ä 153 Ö 174 « 195 216 237 ∅ 133 à 154 Ü 175 » 196 217 238 ∈ 134 å 155 ¢ 176 197 218 239 ∩ 135 ç 156 £ 177 198 219 240 ≡ 136 ê 157 ¥ 178 199 220 241 ± 137 ë 158 _ 179 200 221 242 ≥ 138 è 159 180 201 222 243 ≤ 139 ï 160 á 181 202 223 244 ⌠ 140 î 161 í 182 203 224 α 245 ⌡ 141 ì 162 ó 183 204 225 ß 246 ÷ 142 Ä 163 ú 184 205 226 Γ 247 ≈ 143 Å 164 ñ 185 206 227 π 248 ° 144 É 165 Ñ 186 207 228 Σ 249 • 145 æ 166 ª 187 208 229 σ 250 − 146 Æ 167 º 188 209 230 µ 251 √ 147 ô 168 ¿ 189 210 231 τ 252 ⁿ 148 ö 169 _ 190 211 232 φ 253 ² 254 255