Sunteți pe pagina 1din 54

Limbajul C++

1. Caracteristicile limbajului de programare


Algoritmi din pseudocod trebuie implementaţi într-un limbaj de programare (de exemplu C++), cu
ajutorul unor instrucţiuni specifice limbajului de programare.
Transcrierea algoritmului în limbaj de programare se face cu un editor de texte, obţinându-se un fişier
numit program sursă, care în C++ are extensia .cpp.
După crearea programului sursă urmează compilarea, adică traducerea instrucţiunilor din limbaj de
programare în instrucţiuni din limbaj maşină. Se obţine astfel un program obiect care are extensia .obj.
Apoi se crează programul executabil cu extensia .exe.
Limbajul C++ conţine nişte biblioteci standard care conţin diverse funcţii (de citire şi scriere sau
matematice)
2. Vocabularul limbajului
a) Setul de caractere:
- litere mari şi mici ale alfabetului latin (A … Z, a … z)
- cifrele de la 0 la 9
- caractere speciale: spaţiul, +, -, *, /, =, %, !, #, ”, ^, <, >, (, ), [, ], {, }, ., ,, :, ;, _, $, @.
b) Identificatorii reprezintă o succesiune de litere, cifre sau caracterul special _ din care din care prima
nu trebuie să fie cifră sau _. Cu ajutorul identificatorilor se asociază nume constantelor, variabilelor,
procedurilor etc.
Exemple: a1, un_numar. Contraexemplu: 1ar, mt&, _we
Ca o recomandare, folosiţi litere mici pentru variabile şi litere mari pentru constante (limbajul face
distincţie între literele mari şi cele mici).
c) Separatori şi comentarii
- separatori: spaţiile, caracterul CR (sfârşit de linie), , sau ;
- comentarii: /* comentariu */ (poate ocupa mai multe linii) sau // comentariu (poate ocupa o
singură linie). Comentariile nu sunt luate în considerare de către limbajul de programare.
d) Cuvintele cheie sunt identificatori cu o semnificaţie precisă pentru limbaj. Acestea nu pot fi folosite
într-un alt context.
De exemplu: main, int, char, float, double, unsigned, signed, short, long, void, sizeof, typedef,
struct, const, enum, if, else, switch, case, default, for, while, do, break, continue, return etc.
3. Structura programelor C++
- Un program este alcătuit din una sau mai multe funcţii
- Funcţia principală (rădăcina) nu poate lipsi din program, execuţia programului începând automat cu
ea. Ea se foloseşte o singură dată în program.

#include<nume_fisier_antet.h> //apelarea directivelor de prepocesare; nu se termină cu ;


void main()
{
declararea tipurilor de date, a constantelor, a variabilelor etc.;
/* bloc */
instrucţiuni;
}

Observaţii:
- void reprezintă faptul că nu întoarce nici un rezultat
- între () se scriu opţional anumiţi parametrii
- nu are importanţă nici plasarea cuvintelor pe linie, nici spaţiile dintre ele
- este permis ca tipul funcţiei să lipsească (main()); în acest caz se presupune că funcţia returnează un
rezultat întreg
1
- la sfârşitul fiecărei instrucţiuni se pune ;

4. Tipuri de date:
- tipuri standard (predefinite):
- char – memorarea caracterelor conform codului ASCII
- int – memorarea numerelor întregi
- float şi double – memorarea numerelor reale
- void – tip neprecizat
În C++ nu există tipul logic (boolean). Orice valoare diferită de 0, a oricărui tip, este considerată ca
fiind TRUE, iar orice valoare 0, a oricărui tip, este considerată ca fiind FALSE.
Aceste tipuri pot fi modificate prin declaraţii suplimentare de tip: short (scurt), long (lung), signed
(cu semn) şi unsigned (fără semn).

Tip de dată Dimensiune Domeniu de definiţie


char 8 biţi -128 ÷ 127
unsigned char 8 biţi 0 ÷ 255
int 16 biţi -32.768 ÷ 32.767
unsigned int sau unsigned 16 biţi 0 ÷ 65.535
long int sau long 32 biţi -2.147.483.648 ÷ 2.147.483.647
unsigned long int sau 32 biţi 0 ÷ 4.294.967.205 (pentru numere de 9 cifre)
unsigned long
float 32 biţi ±3,4*10-37 ÷ ±3,4*1037 (cu o precizie de 6 cifre)
double 64 biţi ±1,7*10-308 ÷ ±1,7*10308 (cu o precizie de 10 cifre)
long double 80 biţi ±3,4*10-4932 ÷ ±1,1*104932 (cu o precizie de 15 cifre)

- tipuri definite de utilizator

5. Constantele: valorile rămăn nemodificate pe tot parcursul programului


- Constante literale în program (valori efective folosite ):
- numerice întregi – numai numere pozitive
- numerice reale
- caracter – caracter delimitat de apostrofuri („a‟, „A‟, „1‟) sau prin codul ASCII al
caracterului sau prin secvenţe escape, delimitate de apostrofuri şi încep cu caracterul \.
Ultima situaţie se foloseşte în special la caractere care au o semnificaţie specială (backslash
– „\\‟; apostrof – „/‟‟; semn de întrebare – „/?‟)
- şir de caracter – grup de caractere delimitat prin ghilimele (”abcd”). Atenţie: „a‟ este diferit
de ”a”
- Constante simbolice (identificatori folosiţi în locul valorilor efective)

6. Declararea variabilelor
tip_dată nume; sau tip_dată listă_nume;
Exemple:
- int a;
char c;
- int a, b, c;
float x, y;
- int a, b = 1, s = 0;
char c = „a‟, x = 97; (se face iniţializarea variabilei)

2
7. Declararea constantelor simbolice
a) Declararea unei constante
const [tip_dată] nume = valoare; (ce este între [ ] este opţional)
Exemple:
- const int A = 5; sau const A = 5;
- const PI = 3.14;
Atenţie: const PI = 3.14;
float pi = 3,14
pi se va putea modifica, în schimb PI rămâne nemodificată.
b) Declararea unui set de constante
enum [nume_set] {listă_constante};
Exemple:
- enum logic {NU, DA}; NU = 0, DA = 1
- enum set {A=1, B=2, C=5};

8. Declararea tipurilor de date definite de utilizator


typedef definiţie_tip nume_tip;
Exemple:
- typedef unsigned char byte;
……
byte a;
- typedef enum {NU, DA} boolean;
……
boolean b;

9. Fişiere antet

Fişier antet Necesar pentru


iostream.h cin>>; cout<<; endl
conio.h clrscr(); getch()
fstream.h eof(); close(); tellp(); seekp(); clear(); get(); getline()
stdio.h rename(); remove()
iomanip.h setw(); setprecision(); setfill(); setioflags(); resetiosflags(); oct(); dec(); hex()
math.h abs(); floor(); ceil(); sqrt(); pow(); exp(); sin(); cos(); tan()
stdlib.h randomize(); rand()

10. Operaţiile de citire şi scriere


a) Citirea: cin>>a1>>a2>>…>>an; a1, a2, …, an sunt variabile de un tip oarecare
Se citesc pe rând a1, a2, …, an; în cazul în care pentru o anumită variabilă se introduce o valoare care
nu coincide cu tipul ei, citirea se blochează (următoarele variabile nu mai sunt citite)
b) Scrierea: cout<<a1<<a2<<…<<an; a1, a2, …, an sunt variabile sau constante
Se scriu variabilele a1, a2, …, an una după alta, fără spaţii.
Dacă se doreşte separarea datelor la afişare, atunci se tipăresc separate prin spaţii ” ”. (de exemplu,
cout<<a<<” ”<<b;).
Dacă se doreşte scrierea datelor pe rânduri diferite, atunci se foloseşte constanta endl. (de exemplu,
cout<<a<<endl<<b;)

3
11. Operatori C++
Expresia este o succesiune de operatori şi operanzi legaţi între ei după reguli specifice limbajului.
Expresiile pot fi:
- matematice – rezultatul este un număr întreg sau real
- logice – rezultatul este 0 sau 1
- de tip text – rezultatul este un şir de caractere
- fără tip – conţin apelul unei funcţii care nu returnează nici un rezultat
Operanzii pot fi: variabile sau constante, funcţii care furnizează un rezultat (de exemplu sqrt(x))
Operatorii sunt simboluri care determină executarea unor operaţii.
a) Operatori aritmetici:
- unari: +, –
- binari: +, –, *, / (div dacă operanzii sunt întregi, împărţire dacă măcar unul dintre operanzi este
real), % (mod)
b) Operatorii relaţionali: <, <=, >, >=, == (egal), != (diferit)
Exemple:
- 3>5 (rezultatul expresiei este 0)
- 3<5 (rezultatul expresiei este 1)
- 3 + 7>=11 – 1 (rezultatul expresiei este 0)
- 3==3 (rezultatul expresiei este 1)
- 3!=3 (rezultatul expresiei este 0)
c) Operatori de incrementare şi decrementare: ++ (se adună 1 la valoarea operandului), -- (se scade
1 din valoarea operandului)
Aceşti operatori pot fi:
- postfixaţi: a++, a--: variabila este incrementată sau decrementată după ce valoarea reţinută de ea
intră în calcul
Exemplu:
- int a=1;
cout<<1+a++; se afişează 2, iar a primeşte valoare 2
- int a=1, b=3;
cout<<a++*b++ se afişează 3, iar a primeşte valoarea 2 şi b valoarea 4
- prefixaţi: ++a, --a: variabila este incrementată sau decrementată înainte ca valoarea reţinută de ea
să intre în calcul
Exemplu:
- int a=1;
cout<<1-++a; a primeşte valoare 2 şi se afişează -1
- int a=1, b=3;
cout<<++a*++b a primeşte valoarea 2 şi b valoarea 4 şi se afişează 8
Obsservaţie: 1+++a este o expresie incorectă
d) Operatori logici
- unari: ! (negarea logică/non) – dacă operandul are o valoare diferită de 0, rezultatul este 0, altfel
rezultatul este 1. De exemplu: float a=2; !a=0 sau float a=0; !a=1
- binari: - && (şi logic/and) – dacă ambii operanzi sunt diferiţi de 0, rezultatul este 1, altfel
rezultatul este 0.
De exemplu: int a=1, b=3; a&&b=1 sau int a=0, b=1; a&&b=0
- || (sau logic/or) – dacă cel puţin unul dintre operanzi are o valoare diferită de 0,
rezultatul este 1, altfel rezultatul este 0.
De exemplu: int a=0, b=3; a||b=1 sau int a=0, b=0; a||b=0
4
Observaţie: În cazul lui && şi ||, al doilea operand nu mai este evaluat, dacă prin
evaluarea primului operand se poate decide valoarea rezultatului
e) Operatori pe biţi:
- unari: ~ (negare pe biţi)
- binari: - <<, >> (deplasare la stânga, respectiv la dreapta)
Exemplu: - a=60(10)=0…0111100(2), n=3
a>>n
Deci a=0…0000111 (ultimii n biţi se pierd, poziţiile rămase libere în stânga
sunt completate cu 0)
- a=15(10)=0…0001111(2), n=3
Deci a=0…1111000 (primii n biţi se pierd, poziţiile rămase libere în dreapta
sunt completate cu 0)
- & (şi pe biţi): a=5(10)=0…0101(2), b=7(10)=0…0111(2). Deci a&b=0…0101
- | (sau pe biţi): a=5(10)=0…0101(2), b=7(10)=0…0111(2). Deci a|b=0…0111
- ^ (sau exclusiv pe biţi): a=5(10)=0…0101(2), b=7(10)=0…0111(2). Deci a^b=0…0010
f) Operatori de atribuire
- atribuire simplă: nume_variabilă=expresie;
- se evaluează expresia, iar valoarea obţinută se atribuie variabilei. Atenţie la
declararea variabilelor.
- atribuire multiplă: nume_1=nume_2=….=nume_n=expresie;
- se evaluează expresia, iar valoare obţinută se atribuie variabilei nume_n, apoi
valoarea variabilei nume_n se atribuie variabilei nume_n_1 ş.a.m.d.
- atribuire operator: nume_variabilă operator=expresie;
- operatorul poate fi operator aritmetic (+=, -=, *=, /=) sau operator logic pe
biţi (<<=, >>=, &=, |=, ^=)
- se evaluează expresia, se aplică operatorul între variabilă şi valoarea
expresiei, iar rezultatul se atribuie variabilei
Exemple: - a+=b+c este echivalent cu a=a+b+c
- a&=b este echivalent cu a=a&b
g) Operatorul condiţional: expresie_1 ? expresie_2 : expresie_3
- se evaluează expresie_1
- dacă valoarea obţinută este diferită de 0 (este adevărată), atunci se
evaluează expresie_2, iar expresie_3 este ignorată. Se returnează
valoarea expresie_2.
- dacă valoarea obţinută este 0 (este falsă), atunci se evaluează expresie_3,
iar expresie_2 este ignorată. Se returnează valoarea expresie_3.
h) Operatorul virgulă: expresie_1, expresie_2, ….., expresie_n
Se evaluează expresie_1, apoi expresie_2, ş.a.m.d. Rezultatul şi tipul
rezultatului este dat de valoarea ultimei expresii.
i) Operatorul dimensiune: - unar: sizeof(expresie) sau sizeof(tip_dată) (returnează numărul de octeţi
utilizaţi pentru a memora valoarea expreiei sau a tipului de dată precizat)

5
Prioritatea operatorilor:

Nivel Categorie Operatori


1 Prioritate maximă – apelul de funcţie ()
2 Operatorii unari +, -
3 Operatorii de incrementare, decrementare, negarea, negarea pe biţi, ++, --, !, ~, sizeof, (tip)
dimensiunea în octeţi, conversie explicită de tip
4 Operatori aritmetici de multiplicare *, /, %
5 Operatori aritmetici adiţionali +, -
6 Operatori de deplasare pe biţi <<, >>
7 Operatorii relaţionali pentru inegalităţi <, <=, >, >=
8 Operatorii relaţionali pentru egalitate ==, !=
9 Şi pe biţi &
10 Sau exclusiv pe biţi ^
11 Sau pe biţi |
12 Şi &&
13 Sau ||
14 Operator condiţional ?:
15 Operatori de atribuire =, +=, -=, *=, /=, &=,
^=,|=, <<=, >>=
16 Operatorul virgulă ,

12. Instrucţiuni în C++

a) Instrucţiunea expresie:
expresie;
Exemplu: a=a+2;

b) Instrucţiunea compusă:
{
instrucţiune1;
instrucţiune2;
……..
}
Exemplu: {
cout<<”nr=”;
cin>>nr;
}

c) Instrucţiunea if … else:

Pseudocod C++
dacă expresie atunci acțiune1 if (expresie) instrucțiune1; [else instrucțiune2];
altfel acțiune2
sf_dacă

- se evaluează expresia
- dacă valoarea acesteia este diferită de 0 (este adevărată), atunci se execută instrucţiune1, altfel se
execută instrucţiune2
6
Exemplu: if (a>b) cout<<a; else cout<<b;

d) Instrucţiunea switch … case:

Pseudocod C++
în_caz_că selector switch (expresie)
cazul1: acțiune1; {
cazul2: acțiune2; case exp_1: instrucțiune_1; break;
................. case exp_2: instrucțiune_2; break;
cazuln: acțiunen; ....................
altfel acțiunen+1; case exp_n: instrucțiune_n; break;
sf_în_caz_că [default instrucțiune_n+1]
}

- se evaluează expresia, care trebuie să returneze un rezultat întreg


- exp_1, exp_2, …., exp_n trebuie să fie constante întregi sau expresii constante cu valoare întreagă
- dacă valoarea expresiei este egală cu exp_1, atunci se execută instrucţiune1 şi iese din switch
- dacă valoarea expresiei este egală cu exp_2, atunci se execută instrucţiune2 şi iese din switch
- ……
- dacă valoarea expresiei este egală cu exp_n, atunci se execută instrucţiunen şi iese din switch
- altfel se execută instrucţiune_n+1 şi iese din switch
- instrucţiunea break, întrerupe execuţia instrucţiunii switch … case
Exemplu: swich (n)
{
case 1: e=a+b;
case 2: e=a-b;
default cout<<”Ati ales nr gresit!”;
}

e) Instrucţiunea for:

Pseudocod C++
pentru contorvi, vf execută for (exp_inițializare; exp_test; exp_inc_dec) instrucțiune;
acțiune;
sf_pentru
P1: se evaluează exp_iniţializare
P2: se evaluează exp_test; dacă valoarea expresiei este diferită de 0 (este adevărată) se execută
instrucţiune, altfel se iese din for
P3: se evaluează exp_inc_dec şi se revine la pasul P2
Exemplu: for (i=1; i<=n; i++) s=s+1;

f) Instrucţiunea while:

Pseudocod C++
cât_timp condiție execută while (expresie) instrucțiune;
acțiune;
sf_cât_timp

7
P1: se evaluează expresia
P2: dacă valoarea expresiei este diferită de 0 (este adevărată) se execută instrucţiune şi se revine la
pasul P1, altfel se iese din while
Exemplu: while (n!=0) n=n/10;

g) Instrucţiunea do….while:

Pseudocod C++
repetă do
acțiune; instrucțiune;
până_când condiție while (expresie);

P1: se execută instrucţiune


P2: evaluează expresia; dacă valoarea expresiei este diferită de 0 (este adevărată) şi se revine la
pasul P1, altfel se iese din do…while
Exemplu: do
n=n/10;
while (n==0);

13. Funcţii matematice:


a) abs(x) – modul din x, unde x număr întreg (int)
b) fabs(x) - modul din x, unde x număr real (double)
c) labs(x) - modul din x, unde x număr întreg lung (long int)
d) ceil(x) – parte întreagă din x, unde x double
e) floor(x) – rotunjirea lui x, unde x double
f) sqrt(x) – radical de ordinul 2 din x, x signed double
g) sin(x) – sin x
h) cos(x) – cos x
i) tan(x) – tg x
j) exp(x) = ex, e  2,78…
k) pow(x, y) = xy, x, y double

8
Structuri de date

Structura de date este o colecţie de date între elementele căreia se defineşte un anumit tip de relaţie care
determină metodele de localizare şi prelucrare a datelor.

1. Fişiere text

Se numeşte fişier o colecţie de date omogene (adică de acelaşi tip), stocate pe suport extern şi accesată
printr-un nume (numele fişierului).
Până acum, în programele făcute am citit datele de la tastatură (cu cin) şi am afişat rezultatele pe ecran
(cu cout). Avem şi o altă opţiune de a citi datele dintr-un fişier şi de a reţine rezultatele programului înt-un
alt fişier. Pentru aceasta, vom utiliza fişierele text. Acest tip de fişiere se caracterizează prin:
- datele sunt memorate sub forma unei succesiuni de caractere
- fiecare caracter este memorat prin utilizarea codului ASCII
- un fişier text se termină întotdeauna cu caracterul EOF (end of file). Dacă dorim să-l introducem de la
tastatură, se tastează CTRL+Z
- este alcătuit din una sau mai multe linii. O linie, cu excepţia ultimei, se termină prin caracterul
newline (\n)
- fişierele text pot fi citite şi modificate cu orice editor de texte
- fişierul are două nume: - nume fizic (extern) – numele cunoscut de sistemul de operare, cel sub care
este salvat în director
- nume logic (intern) – numele cunoscut în cadrul programului
(identificatorul din program)
- fişierul poate fi închis sau deschis pentru citire sau scriere. În cazul desciderii fişierului pentru citire,
cursorul se poziţionează pe primul caracter din fişier. În cazul deschiderii fişierului pentru scriere,
cursorul se poziţionează pe primul caracter din fişier, dacă fişierul este gol (atunci se creează) sau pe
marcajul EOF, dacă se face operaţie de adăugare. La orice operaţie cursorul se deplasează câte o
poziţie.

Orice program care lucrează cu fişiere trebuie să conţină:


# include <fstream.h>
Forma generală de declarare a fişierelor:
fstream nume_logic(char* nume_fizic, ios:: constantă_întreagă);

Constanta_întreagă poate fi:


- in – fişierul se deschide pentru citire
- out – fişierul se deschide pentru scriere
- ate – după deschidere, salt la sfârşitul fişierului
- app – fişierul se deschide pentru a se scrie la sfârşitul lui
- trunc – dacă fişierul care se deschide există, în locul său se creează altul (fişierul existent se pierde)
- nocreate – deschide fişierul doar dacă acesta există (nu este permisă crearea lui)
- noreplace – fişierul se deschide pentru consultare, dacă el există

Exemple:
fstream f(“c:\\date.in”, ios:: in);
fstream g(“c:\\date.out”, ios:: out);

9
Funcţii utile în prelucrarea unui fişier:
- eof() – se foloseşte în detectarea sfârşitului de fişier. Se returnează 0 sau 1 (fals sau adevărat)
- close() – închide un fişier deschis
- tellp() / tellg() – furnizează poziţia cursorului faţă de începutul fişierului la scriere / citire. Se
returnează o valoare de tip long
- seekp(x, y) / seekg(x, y) – deplasează cursorul x poziţii faţă de poziţia de referinţă y, la scriere / citire.
y poate fi: beg (început de fişier), cur (poziţia curentă) sau eof (sfârşit de fişier). x este de tip long şi
poate fi pozitiv (deplasarea se face la dreapta lui y) sau negativ (deplasarea se face la stânga lui y).
- clear() – deblochează citirea, în cazul în care s-a ajuns la EOF.
- get(x, n, ‘d’) – extrage un caracter dintr-un şir de caractere x până când a întâlnit sfârşitul de linie sau
sfârşitul de fişier, au fost citite n caractere sau a citit caracterul ‘d’, folosit ca delimitator.
- get() – extrage următorul caracter sau marcajul de sfârşit de fişier.

Apelarea acestor funcţii se face astfel: nume_logic_fişier . nume_funcţie(...);


Exemplu: f.eof(); f.close();

Exemple de scriere / citire în / din fişiere:


A. Citiri şi scrieri fără format
1. Citire de la tastatură şi scriere în fişier:
char c;
fstream f(”date.out”, ios:: out);
while (cin>>c) f<<c;
f.close();
Se citesc caractere de la tastatură până când se apasă CTRL+Z, caractere care se depun în fişier.

2. Citire din fişier şi afişare pe ecran:


char c;
fstream f(”date.in”, ios:: in);
while (f>>c) cout<<c;
f.close();
Se citesc caractere din fişier până când se ajunge la marcajul EOF, caractere care se afişează pe
ecran.

3. Citire dintr-un fişier şi afişare în alt fişier


char c;
fstream f(”date.in”, ios:: in);
fstream g(”date.out”, ios:: out);
while (f>>c) g<<c;
f.close();
g.close();
B. Citiri şi scrieri cu format
Citire:
cin >> manipulator1 >> manipulator2 >>….. >> manipulator_n >> variabilă; (de la tastatură)
f >> manipulator1 >> manipulator2 >>….. >> manipulator_n >> variabilă; (din fişier)
Scriere:
cout << manipulator1 << manipulator2 <<….. << manipulator_n << variabilă/constantă; (pe ecran)
f << manipulator1 << manipulator2 <<….. << manipulator_n << variabilă/constantă; (în fişier)

10
Pentru a avea acces la manipulatori trebuie să folosim fişierul antet iomanip.h.

Manipulatori pentru operaţia de scriere:


- setprecision(x) – stabileşte numărul de poziţii pentru partea fracţionară (x – numărul zecimalelor)
- setw(x) – stabileşte numărul de poziţii pe care se face afişarea (x – lăţimea câmpului pentru afişare)
- setfill(x) – stabileşte caracterul (x) de umplere a poziţiilor libere ale câmpului (poziţiile unde nu este
scrisă data).
Exemplu:
a = 0.12345;
cout << setw(5) << setprecision(2) << setfill(„0‟) << x;

Se va afişa 00.12

Alţi manipulatori:
- setiosflags(ios :: const1 | ios :: const2 | … | ios :: const_n) – setează biţii precizaţi de constante
- resetiosflags(ios :: const1 | ios :: const2 | … | ios :: const_n) – resetează biţii precizaţi de constante
Constantele pot fi:
- skipws – caracterele albe care preced valoarea care urmează să fie citită sunt sărite
- left – afişarea datelor se face cu aliniere la stânga
- right – afişarea datelor se face cu aliniere la dreapta
- internal – dacă lăţimea câmpului este mai mare decât data, atunci semnul numărului se
afişează cu aliniere la stânga, iar numărul, cu aliniere la dreapta
- dec – se face conversia numărului în zecimal
- oct – se face conversia numărului în octal
- hex – se face conversia numărului în hexazecimal
- showbase – se afişează baza de numeraţie
- showpoint – forţează afişarea punctului zecimal
- uppercase – în cazul afişării în hexazecimal, se vor utiliza literele mari A, B, …., F
- showpos – valorile numerice sun precedate de semn
- scientific – afişarea valorilor reale se face prin utilizarea formei ştiinţifice (1e-8)
- fixed – afişarea valorilor reale se face prin utilizarea formei normale (– 12.45)
Exemplu:
a = 30
cout << setiosflags(ios :: hex | ios :: uppercase) << a;

Se va afişa 1E

11
2. Tablouri unidimensionale (vectori)
Din punct de vedere matematic, vectorul este o funcţie v : {1, 2, …, n} → M, unde M este o mulţime
i → vi
Exemplu:
v = (2, 4, 7, 9, 12) este un vector cu 5 elemente numere naturale, v1 = 2, v2 = 4, …, v5 = 12

În C, primul element are indicele 0.


Exemplu:
v = (2, 4, 7, 9, 12) → v0 = 2, v1 = 4, v2 = 7, v3 = 9, v4 = 12

Declararea unui vector: tip_dată nume[nr_elemente]

Exemple: int a[20]; vectorul a are maxim 20 de elemente numere întregi


float b[10]; vectorul b are maxim 10 de elemente numere reale
char v[30]; vectorul v are maxim 30 de elemente caractere

Declararea unui vector cu atribuirea unor valori iniţiale: tip_dată nume[nr_elemente] = {listă_valori}

Exemplu: int a[5]={10, 20, 30, 40, 50}; 10 20 30 40 50


a[0] a[1] a[2] a[3] a[4]
Citirea şi afişarea unui vector
…..
int v[100], n, i;
cout<<”nr. de elemente”; cin>>n;
for (i = 0; i<n; i++)
{
cout<<”v[”<<i+1<<”]=”; cin>>v[i];
}
for (i = 0; i<n; i++) cout<<v[i]<<” “;
…….

Observaţii:
- Pentru că ştim câte elemente are un vector, trebuie să folosim întotdeauna for.
- Nu este permisa atribuire de genul a = b (a şi b vectori). Atribuirea se face pe componente.
- Pentru parcurgerea unui vector trebuie să folosim întotdeauna for.

3. Tablouri bidimensionale (matrice)


Din punct de vedere matematic, matricea este o funcţie a : {1, 2, …, m} {1, 2, …, n}→ M, M mulţime
(i, j) → aij

( ) matrice cu m linii şi n coloane

Exemplu:
. / matrice 2 3 (2 linii şi 3 coloane) cu numere întregi

12
Observaţie:
- aij reprezintă elementul de pe linia i şi coloana j.
- matricea cu m linii şi n coloane are m∙n elemente

Declararea unei matrice: tip_dată nume[nr_1][nr_2]

Exemple: int a[2][4]; matricea a are 8 elemente numere întregi dispuse pe 2 linii şi 4 coloane
float b[3][5]; matricea b are 15 elemente numere reale dispuse pe 3 linii şi 5 coloane

Declararea unui vector cu atribuirea unor valori iniţiale: tip_dată nume[nr_1][nr_2] = {listă_valori}

Exemplu: int a[2][3]={1, 2, 3, 4, 5, 6}; . /

Citirea şi afişarea unei matrice


…..
int a[10][10], m, n, i, j;
cout<<”nr. de linii”; cin>>m;
cout<<”nr. de coloane”; 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;
}
…….

13
Algoritmi specifici tablourilor

1. Vectori
a) Inserarea unui element în vector
Să se insereze numărul x pe poziția k în vectorul v.
Exemplu:
x = 7, k = 3
(3, 1, 4, 2) → (3, 1, 7, 4, 2)
….
for (i=n-1; i>k; i--) v[i] = v[i-1];;
v[k] = x;
for (i=0; i<n+1; i++) cout<<v[i];
….
b) Ștergerea unui element din vector
Să se șteargă elementul de pe poziția k din vectorul v.
Exemplu:
k=3
(3, 1, 4, 2) → (3, 1, 2)
….
for (i=k; i<n-1; i++) v[i] = v[i+1];;
for (i=0; i<n-1; i++) cout<<v[i];
….
c) Metode de sortare
Sortarea prin interschimbare:
Algoritmul:
- se parcurge vectorul inversând elementele alăturate care nu sunt în ordine crescătoare
(descrescătoare)
- procedeeul se repetă până când are loc o parcurgere în care nu se fac inversări
Exemplu:
(3, 1, 4, 2) → (1, 3, 2, 4) → (1, 2, 3, 4)
…..
do
{
gasit = 0;
for (i=0; i<n-1; i++)
{ if (a[i]>a[i+1]) {
aux = v[i];
v[i] = v[i+1];
v[i+1] = aux;
gasit = 1;}
}
}
while (gasit == 1)
….
Sortarea prin aflarea minimului (maximului)
Algoritmul:

14
- se află minimul dintre elementele vectorului începând cu prima poziție și se trece acest minim pe
prima poziție prin interschimbare
- se determină minimul dintre elementele vectorului începând cu a doua poziție și se trece acest
minim pe a doua poziție prin interschimbare ș. a. m. d.
Exemplu:
(3, 1, 4, 2) → (1, 3, 4, 2) → (1, 2, 4, 3) → (1, 2, 3, 4)
….
for (i=0; i<n-1; i++)
{
min = v[i]; k = i;
for (j=i+1; j<n; j++)
if (v[j]<min) {min = v[j]; k = j;}
aux = v[k];
v[k] = v[i];
v[i] = aux;
}
…..
Sortarea prin numărare (a unui vector cu elemente diferite)
Algoritmul:
- se folosește un vector auxiliar k, ce păstrează în k[i] numărul de elemente din vectorul v mai mici
decât v[i], număr ce va repoziționa pe v[i] în vectorul de ieșire w.
Exemplu:
v = (3, 1, 4, 2) → k = (2, 0, 3, 1) → w = (1, 2, 3, 4)
….
for (i=0; i<n; i++) k[i] = 0;
for (i=0; i<n; i++)
for (j=0; j<n; j++)
if (v[j]<v[i]) k[i] = k[i] + 1;
for (i=0; i<n; i++) w[k[i]] = v[i];
…..
d) Căutarea binară
Fie un vector cu n elemente sortat crescător. Să se decidă dacă un număr nr se găsește sau nu în
vector.
Algoritmul:
- li = 0; ls = n-1
- se află indicele elementului din mijloc k = (li + ls) / 2
- apar trei situații: - dacă v[k] = nr, atunci căutarea se termină cu succes
- dacă v[k]<nr, atunci li = k+1
- dacă v[k]>nr, atunci ls = k-1
- căutarea se termină când numărul a fost găsit sau când li>ls (căutare fără succes)
…..
li = 0; ls = n – 1; gasit = 0;
while ((li<=ls) && (gasit == 0))
{
k = (li + ls) / 2;
if (v[k] = nr)
{
15
cout<<” gasit pe pozitia ”<<k+1;
gasit = 1;
}
else
if (v[k]<nr) li = k + 1;
else ls = k – 1;
}
if (gasit == 0) cout<<”nu a fost gasit”;
….
e) Interclasarea
Fie doi vectori cu m, respectiv n elemente, sortați crescător (descrescător). Să se creeze și să se
afișeze vectorul format din cele m+ n elemente, la rândul său fiind sortat crescător (descrescător).
…..
i = 0; j = 0; k = 0;
while ((i<m) && (j<n))
if (v[i] < u[j]) {w[k]=v[i]; k=k+1; i=i+1;};
else { w[k]=u[j]; k=k+1; j=j+1;}
if (i<m) for (j=i; j<m; j++) {w[k]=v[j]; k=k+1;};
if (j]<n) for (i=j; i<n; i++) {w[k]=u[i]; k=k+1;};
for (i=0; i<k; i++) cout<<w[i];
….

2. Matrice
Discutăm despre diagonale doar în matrice pătratică (nr. de linii = nr. de coloane)
a) Diagonala principală (DP)
a[i][i] – elementul de pe diagonala principală și linia i
b) Diagonala secundară (DS)
a[i][n-i-1] – elementul de pe diagonala secundară și linia i
c) Elementele de sub diagonala principală (SDP)
for (i=1; i<n; i++)
for(j=0; j<i; j++) a[i][j] …
d) Elementele de deasupra diagonalei principale (DDP)
for (i=0; i<n-1; i++)
for(j=i+1; j<n; j++) a[i][j] …
e) Elementele de sub diagonala secundară (SDS)
for (i=1; i<n; i++)
for(j=n-i; j<n; j++) a[i][j] …
f) Elementele de deasupra diagonalei secundare (DDS)
for (i=0; i<n-1; i++)
for(j=0; j<n-i-1; j++) a[i][j] …

16
Eficiența algoritmilor

Eficiența unui algoritm este evaluată prin timpul necesar pentru executarea algoritmului.
Un algoritm este eficient dacă folosește cel mai puțin resursele calculatorului și anume:
- memoria internă: - trebuie să alegem corect tipul de dată pentru fiecare variabilă de memorie
- trebuie să rezolvăm problema folosind cât mai puține variabile de memorie
- procesorul: - timpul de utilizare a procesorului depinde de timpul necesar pentru executarea
algoritmului.
Timpul de execuție se poate face prin:
a) numărul de operații elementare (aritmetice, comparații, o operație de atribuire sau un grup bine
precizat (finit) de astfel de operații) ale algoritmului
b) timpul mediu al algoritmului
Operația de bază este o operație elementară sau o succesiune de operații elementare a căror execuție nu
depinde de valorile datelor de intrare.
Pentru a compara doi algoritmi folosind numărul de operații elementare, trebuie să stabilim operația de
bază și să numărăm în cazul fiecărui algoritm de câte ori se execută.
Mărimile care pot fi folosite în compararea a doi algoritmi sunt:
- eficiența – timpul de calcul estimat prin numărul de execuții ale operației de bază. Se notează cu T(n),
unde n este dimensiunea datelor de intrare
- ordinul de complexitate – timpul de calcul estimat prin ordinul de mărime al numărului de execuții ale
operației de bază. Se notează cu O(f(n)), unde f(n) este termenul determinant din funcția T(n)
Exemplu:
T(n) = 3n2 + 3n + 2 → O(n) = n2

Tipuri de algoritmi:

Ordin de complexitate Tipul algoritmului


O(n) Algoritm liniar
O(nm) Algoritm polinomial (m=2→pătratic; m=3→cubic)
O(kn) Algoritm exponențial, inclusiv O(n!)
O(logn) Algoritm logaritmic
O(nlogn) Algoritm liniar logaritmic

Tipul algoritmului este important deoarece de le depinde timpul de execuție, respectiv eficiența
algoritmului.
Exemplu:
Presupunem că dimensiunea datelor de intrare este n și că pentru executarea operației de bază sunt
necesare 10-9 s. Astfel:
O(n) n=10 n=20 n=30
log2n 3,32 4,32
n 10-8 s 2∙10-8 s 3∙10-8 s
2 -7 -7
n 10 s 4∙10 s 9∙10-7 s
2n 210∙10-9 s 220∙10-9 s 230∙10-9 s

Deci:
- Cei mai rapizi algoritmi sunt cei logaritmici
- Dacă pentru rezolvarea unei probleme există algoritmi polinomiali și exponențiali, se preferă cei
polinomiali
- Dacă pentru rezolvarea unei probleme există mai mulți algoritmi polinomiali, se preferă cel cu gradul
mai mic
Concluzie:
Ordinul de complexitate este o funcție dependentă de dimensiunea datelor de intrare și este determinat de
structurile repetitive care se execută.
17
Astfel:
Structura repetitivă Ordinul de complexitate
for (i=0; i<n;i=i+k) f(n) = n/k → O(n) = n
for (i=0; i<n;i=i*k) f(n) = logkn → O(n) = logn
for (i=0; i<n;i=i/k) f(n) = logkn → O(n) = logn
for (i=0; i<n;i++) O(n) = n2
for (j=0; j<n;j++)

18
4. Șiruri de caractere
a) Implementarea șirurilor de caractere în C++
Șirul de caractere este o structură de date care este formată dintr-o mulțime ordonată de caractere, în
care fiecare caracter se identifică prin poziția sa în cadrul mulțimii.
În C++ implementarea șirurilor de caractere se face sub forma unui tablou unidimensional (vector) al
cărui elemente sunt de tip caracter (reprezentat prin codul său ASCII).
Exemplu:
char s[256]; - definește un șir cu maxim 256 de caractere (256 reprezintă lungimea fizică)
Lungimea logică se poate marca prin folosirea caracterului NULL, specificat prin constanta caracter „\0‟
(care are codul ASCII 0)
La declarare, unui șir de caractere i se poate atribui o constantă între ghilimele.
Exemplu:
char s[256] = “Buna ziua”;
Astfel s-a declarat un șir de caractere cu lungime maximă de 256 caractere (256 de octeți rezervați)
din care au fost ocupați doar 10 (9 pentru “Buna ziua” și 1 pentru caracterul NULL, adăugat automat
(lungimea logică))
Observație:
Lungimea unui șir de caractere reprezintă numărul de caractere din șir, mai puțin caracterul NULL.
Șirul vid sau șirul nul este șirul care are lungimea 0.
Declararea unui șir nul:
char s[256] = “”;
Limbajul C++ permite ca un vector de caractere să fie adresat începând de la un anumit octet al său.
Astfel:
- când scriem s, adresăm vectorul începând cu primul octet (caracter)
- când scriem s+1, adresăm vectorul începând cu al doilea octet (caracter)
- când scriem s+2, adresăm vectorul începând cu al treilea octet (caracter)
Exemplu:
s = ”informatică”
s+1 = ”nformatică”
s+5 = ”matică”
b) Citirea și scrierea șirurilor de caractere
Modalități de citire a șirului de caractere:
- cin>>s; Dezavantajul este că nu pot fi citite spațiile
- cin.get(sir, n, ch); – citește maxim n – 1 caractere, inclusiv spațiile, până la întâlnirea caracterului ch.
Exemple:
1. cin.get(s, 5) – se citește un șir s de maxim 4 caractere (la final se apasă Enter)
2. cin.get(s, 9, „#‟); - se citesc maxim 8 caractere până la întâlnirea caracterului #
3. cin.get(); - se folosește între două cin.get cu parametrii pentru a descărca din fluxul de date ultimul
caracter citit la primul șir (delimitatorul)
Scrierea șirurilor de caractere:
cout<<s;
c) Prelucrarea șirurilor de caractere
Prelucrarea șirurilor de caractere se poate face prin mai multe metode:
- prin parcurgerea caracterelor din șir ca elemente ale unui vector, folosind indicii
- folosind funcțiile de sistem implementate în biblioteca <string.h> și <stdlib.h>:
- strlen(șir); - returnează lungimea șirului
- strcpy(șir2, șir1); - se copiază șir1 în șir2 (șir2←șir1)

19
- strcat(șir1, șir2); - sunt adăugate toate caracterele din șir2 în șir1, inclusiv Null
(concatenarea șirurilor, șir1←șir1 + șir2)
- strncat(șir1, șir2, n); - sunt adăugate maxim n caractere din șir2 în șir1
- strcmp(șir1, șir2); - compară două șiruri de caractere (conform codului ASCII)
= 0 , rezultă că sunt egale
>0, rezultă că șir1 este mai mare decât șir2
<0, rezultă ca șir1 este mai mic decât șir2
- strrev(șir); - inversează șirul dat
- strlwr(șir); - transformă literele mari în litere mici în șirul dat
- strupr(șir); - transformă literele mici în litere mari în șirul dat
- strchr(șir, ch); - returnează adresa către prima apariție a caracterului ch în șirul dat
- strrchr(șir, ch); - returnează adresa către ultima apariție a caracterului ch în șirul dat
- strstr(șir, subșir); - returnează adresa de început a subșirului, dacă se găsește; altfel
returnează valoarea Null
- strspn(șir1, șir2) – returnează numărul de caractere consecutive din șir1, care se găsesc în
șir2
- strcspn(șir1, șir2) – returnează numărul de caractere consecutive din șir1, care nu se găsesc
în șir2
- strpbrk(șir1, șir2) – returnează adresa către primul caracter din șir1 care se găsește și în șir2
- atoi(șir) – convertește șirul într-o valoare numerică întreagă (int)
- atol(șir) – convertește șirul într-o valoare numerică întreagă (long)
- atof(șir) – convertește șirul într-o valoare numerică reală (double)
- ecvt(n, m, &p, &s) – convertește numărul real n într-un șir de caractere. m reprezintă
numărul de caractere ale șirului (dacă numărul are mai puține cifre decât m, se
completează la dreapta cu 0), p reprezintă poziția punctului zecimal, iar s, semnul
d) Câțiva algoritmi de prelucrare a subșirurilor de caractere
- Extragerea unui subșir sb dintr-un șir s (cunoscând poziția k de unde începe subșirul sb, precum și
lungimea sa, m)
….
if (k <= strlen(s)) if (m>=strlen(s)-n+1) strcat(sb, s+k-1);
else strcat(sb, s+k-1, m);
….
- Ștergerea unui subșir sb dintr-un șir s
….
char …., *p // pentru adresa unei poziții
….
p = strstr(s, sb);
if (p) strcpy(p, p+strlen(sb));
….
- Inserarea unui subșir sb într-un șir s în poziția n
….
char …., *p // pentru adresa unei poziții
….
p = &s[n-1];
strcpy(aux, p);
strcpy(p, sb);
strcpy(p+strlen(sb), aux);
20
….
- Înlocuirea unui subșir sb1cu alt subșir sb2 în șirul s
….
char …., *p // pentru adresa unei poziții
….
p = strstr(s, sb);
strcpy(p, p+strlen(sb1));
strcpy(aux, p);
strcpy(p, sb2);
strcpy(p+strlen(sb2), aux);
….

5. Structura înregistrare
Înregistrarea este structura de date formată dintr-un ansamblu de date neomogene (date elementare sau
structuri de date) între care există o legătură de conținut.
Elementele structurii se numesc câmpuri și pot fi identificate după un nume.
Înregistrarea = colecție de câmpuri
Exemplu:
numele înregistrării elev
numele câmpului nume pren nota1 nota2 media
tipul câmpului șir de caractere șir de caractere întreg întreg real
valoarea Morar Ana 10 9 9,50

a) Declararea variabilei de tip înregistrare

struct [<nume structura>]


{ <tip de data 1> <nume 11>... <nume 1n>;
<tip de data 2> <nume 21>... <nume 2n>;
.......
<tip de data m> <nume m1>... <nume mn>;
};
Obs.
- <nume structura> nu este obligatorie
- declararea unei structuri se termină cu ;
Exemplu:
1. struct elev
{char nume, pren;
unsigned int nota1, nota2;
float media;
};
elev x;
2. struct punct {int x, y;};
punct A, B;

b) Accesul la câmpurile înregistrării

<nume_variabilă_înregistrare>.<nume_câmp>

Exemplu:
1. x.nume
x.media
21
2. A.x
B.y
c) Înregistrări imbricate
În unele cazuri, unul dintre câmpurile înregistrării poate să fie și el, la rândul lui, de tip înregistrare.
Exemplu:
struct data_nasterii {unsigned zi, luna, an};
struct persoana
{char nume;
data_nasterii data_n;
unsigned varsta};
persoana pers;

sau

struct persoana
{char nume;
struct data_nasterii {unsigned zi, luna, an} data_n;
unsigned varsta};
persoana pers;

Accesul la câmpul an se face astfel: pers.data_n.an

d) Tablouri de înregistrări
Exemplu:
struct elev
{char nume[20], pren[10]; //declarare înregistrare
unsigned nota1, nota2;
float media};

elev clasa[25]; //declarare vector de înregistrări

clasa[i].nume //accesare nume al i-lea elev

22
6. Subprograme

Definiție
Subprogramul (funcția) este o secvență de instrucțiuni care rezolvă o anumită sarcină și care poate fi
descrisă separat de blocul rădăcină (main) și lansată în execuție din cadrul unui bloc, ori de câte ori este
nevoie.
Necesitatea folosirii subprogramelor:
- O secvență de instrucțiuni se repetă de mai multe ori în cadrul unui program
- Rezolvarea unei anumite sarcini este necesară în mai multe programe
- Orice problemă poate fi descompusă în subprobleme (module)
Avantajele folosirii subprogramelor:
- Se face economie de memorie internă
- Se favorizează lucrul în echipă pentru aplicații mari
- Depanarea și actualizarea aplicației se fac mai ușor
- Crește portabilitatea programelor
Elementele unui subprogram
- Prototipul subprogramului
- Apelul subprogramului
- Definiția subprogramului

# include <iostream>
using namespace std;

tip_rezultat scrie(); prototipul subrogramului

int main() (modul apelant)


{ declarări – variabile globale
scrie(); apelul subrogramului (modul apelat)
}

tip_rezultat scrie() antetul subprogramului


{
declarări proprii (variabile, constante, etc.) – variabile locale
instruncțiuni corpul subrogramului
[return expresie] (este opțional)
}

Observație:
a. Tip_rezultat nu poate fi de tip tablou. Pentru ca funcția să furnizeze ca rezultat un tablou acesta
trebuie declarat ca înregistrare, înainte de a declara funcția

Exemplu:
struct x{float a[10][10];};
………………
x tablou(int v, unsigned n);
b. Prototipul subprogramului se termină cu ; spre deosebire de antetul subprogramului.

Clasificare subprograme
a. Funcția procedurală – subprogramul care returnează una, mai multe sau nici o valoare prin
intermediul parametrilor

nume_subprogram ([listă parametrii]);


Exemplu: clrscr(); strcpy(s1, s2);

23
b. Funcția operand – subprogramul care returnează un rezultat prin chiar numele său, și eventual și alte
rezultate, prin intermediul parametrilor

nume_expresie = nume_funcție ([listă parametrii]);

Exemple: x = sqrt(a); i < strlen(s);

Observații:
- În listă, parametrii sunt separați prin virgulă
- Parametrii pot fi variabile, expresii sau constante și se mai numesc argumentele funcției
- În cazul în care funcția operand returnează un singur rezultat, parametrii din listă sunt parametrii de
intrare, altfel ei pot fi și parametrii de ieșire sau de intrare-ieșire
- Funcția operand poate fi apelată și ca o funcție procedurală, pierzându-și astfel valoarea returnată prin
numele ei
- În cazul funcției operand, ultimainstrucțiune din corpul subprogramului trebuie să fie return. La
întălnirea lui return execuția subprogramului încetează și se redă controlul modulului apelant.
- Parametrii care se găsesc în antetul funcției se numesc parametrii formali, iar cei care se găsesc în
instrucțiunea de apel se numesc parametrii efectivi. Parametrii formali și parametrii efectivi trebuie
să coincidă ca numărul, ca tip și ca ordine. Nu este obligatoriu ca numele parametrilor formali să
coincidă cu numele parametrilor efectivi.

Transmiterea parametrilor
a. Transmiterea prin valoare
- Se utilizează în situația în care dorim ca subprogramul să lucreze cu acea valoare, dar în prelucrare, nu
ne interesează ca parametrul efectiv să rețină valoarea modificată în subprogram
Exemplu:
# include <iostream>
using namespace std;
void test(int n);
int main()
{ int n=1;
test(n); returnează 1
cout<<n;} 1
void test(int n)
{ n++;
cout<<n<<endl;}
b. Transmiterea prin referință
- Se utilizează în situația în care dorim ca în prelucrare, parametrul efectiv să rețină valoarea modificată
în subprogram
Exemplu:
# include <iostream>
using namespace std;
void interschimbare(int &a, int &b);
int main()
{ int x=2, y=4;
interschimbare(x, y); returnează 4 2
cout<<x<<” “<<y;}
void interschimbare(int &a, int &b)
{ int aux;
aux=a; a=b; b=aux;}

24
Alegerea modului de implementare a subprogramului
- Se recomandă folosirea funcțiilor operand atunci când subprogramul furnizează un singur rezultat
- Se recomandă folosirea funcțiilor procedurale atunci când subprogramul nu furnizează nici un
rezultat sau furnizează mai multe rezultate
Exemple:
1. Să se calculeze n!
Este de preferat să folosim o funcție operand (asta nu înseamnă că nu putem folosi o funcție
procedurală)
....
int fact(int n)
{
int p=1, i;
for(i=1;i<=n;i++) p=p*I;
return p;
}

int main()
{
int n;
cout<<”n=”;cin>>n;
cout<<fact(n);
return 0;
}

2. Fie trei numere întregi a, b, c. Să se ordoneze crescător.


Este de preferat să folosim o funcție procedurală.
....
int schimba(int &x, int &y) \\ parametrii transmiși prin referință
{
int aux;
aux=x; x=y; y=aux;
}

int main()
{
int a, b, c;
cout<<”a=”;cin>>a;
cout<<”b=”;cin>>b;
cout<<”c=”;cin>>c;
if (a>b) schimba(a,b);
if (b>c) schimba(b,c);
if (a>b) schimba(a,b);
cout<<a<<” “<<b<<” “<<c;
return 0;
}

Tablouri de memorie și subprograme

Dacă parametrii de ieșire sau de intrare-ieșire ai unui subprogram sunt de tip vector, matrice sau șir de
caractere, nu se folosește transferul de referință, deoarece identificatorii acestor tipuri de variabile sunt
adrese de memorie. Prin urmare, dacă se folosește transferul prin valoare, orice modificare făcută în vector,
matrice sau șir de caractere va fi vizibilă și din modulul apelant.

25
Atunci când transmitem un vector sau un șir de caractere ca parametru nu trebuie să precizăm lungimea
fizică a vectorului sau șirului de caractere la declararea parametrului.parantezele pătrate care urmează după
nume informează compilatorul că acest parametru este un vector sau un șir de caractere.
Atunci când transmitem o matrice ca parametru, la declararea parametrului nu trebuie să precizăm
numărul de rânduri, dar trebuie să precizăm numărul de coloane.
Exemplu:
1. Fie un vector cu n elemente și un număr x. Să se numere de câte ori apare x în vector.
....
int citeste(int v[ ], int &n)
{
int i;
cout<<”n=”;cin>>n;
for(i=0;i<n;i++) {cout<<”v[”<<i+1<<”]=”; cin>>v[i];}
}
int numara(int v[ ], int n, int x)
{
int k=0;
for(i=0;i<n;i++) if (v[i]==x) k++;
return k;
}

int main()
{
int v[100], x;
citeste(v,n);
cout<<”x=”;cin>>x;
cout<<numara(v,n,x);
return 0;
}

2. Fie un șir de caractere și un caracter c. Să se afișeze de câte ori apare caracterul c în șirul dat.
.....
int numara(char s[ ], char c)
{
int k=0;
for(i=0;i<strlen(s);i++) if (s[i]==c) k++;
return k;
}

int main()
{
char s[100], c;
cout<<”s=”;cin.get(s,100);cin.get();
cout<<”c=”;cin>>c;
cout<<numara(s,c);
return 0;
}

3. Fie o matrice pătratică de dimensiune n. Să se calculeze suma elementelor sale.


....
int citeste(int a[ ][10], int &n)
{
int i, j;

26
cout<<”n=”;cin>>n;
for(i=0;i<n;i++)
for(j=0;j<n;j++) {cout<<”a[”<<i+1<<”,”<<j+1<<”]=”; cin>>a[i][j];}
}
int suma(int a[ ][10], int n)
{
int s=0;
for(i=0;i<n;i++)
for(j=0;j<n;j++) s=s+a[i][j];
return s;
}

int main()
{
int a[10][10], n;
citeste(a,n);
cout<<suma(a,n);
return 0;
}

7. Recursivitate
Recursivitatea e un concept fundamental în matematică și informatică.
Un obiect (o noțiune) e recursiv(ă) dacă e folosit în propria sa definiție.
Exemplu din matematică: șiruri recurente
- progresie aritmetică: x0 = a, xn = xn−1 + r, pentru n > 0
- șirul lui Fibonacci: f0 = 1, f1 = 1, fn = fn−1 + fn−2, pentru n > 1.

Exemple intuitive:
- În anumite restaurante am întâlnit anunțul: Azi nu se fumează.
- Orice militar aflat în ciclul 2 de instruire afirmă: “când eram în ciclul 1, cei din ciclul 2 m-au chinuit
inutil. Așa fac și eu cu cei din ciclul 1.”

O gândire recursivă exprimă concentrat o anumită stare, care se repetă la infinit. Acestă gândire se aplică
în elaborarea algoritmilor recursivi, dar cu o modificare esențială: adăugarea condiției de terminare (altfel
nu am mai vorbi de algoritmi, întrucât algoritmul trebuie să fie finit).
În elaborarea algoritmilor recursivi se aplică raționamentul: ce se întâmplă la un nivel, se repetă la
orice nivel. Subprogramul care se autoapelează trebuie să conțină instrucțiunile corespunzătoare unui nivel.
Pentru orice algoritm recursiv există unul iterativ care rezolvă aceeași problemă. Nu întotdeauna alegerea
unui algoritm recursiv reprezintă un avantaj.

Exemplu:
1. Să se calculeze n!.

Varianta iterativă: Varianta recursivă:

( ) {
( )
int fact(int n)
{ int fact(int n)
int i, p=1; {
for(i=1;i<=n;i++) p=p*i; if (n==0) return 1; \\ condiția de terminare
return p; else return n*fact(n-1);
} }

27
Modul de calcul: (pentru n=4)

p=1 4!=4*3! 4!=4*6=24 →rezultatul final


p=p*1=1 3!=3*2! 3!=3*2=6
p=p*2=2 2!=2*1! 2!=2*1=2
p=p*3=6 1!=1*0! 1!=1*1=1
p=p*4=24
0!=1
fact(4)=24 valoarea cunoscută
sau

fact(4) = 4*fact(3) = 4*3*fact(2) = 4*3*2*fact(1) =


4*3*2*1*fact(0) = 4*3*2*1*1=24

2. Să se afișeze cifrele unui număr


Varianta iterativă: Varianta recursivă:
int cifre(int n) int cifre(int n)
{ { int c;
while(n!=0) c = n%10;
{cout<<n%10; cout<<c;
n=n/10;} if (n>0) cifre(n/10);
} }
Modul de calcul pentru varianta recursivă:
cifre(124)

scrie 4
cifre(12)

scrie 2
cifre(1)

scrie 1
cifre(0)

Observații:
1. Variabila locală este unică pentru un apel al subprogramului. Dacă subprogramul este apelat recursiv
de n ori, vor exista n imagini ale aceleiași variabile locale, câte una pentru fiecare apel. Fiecare
imagine va avea un conținut diferit, corespunzător apelului
2. Adâncimea recursivității este numărul de apeluri succesive ale unui subprogram recursiv.
Pentru exemplul anterior, adâncimea recursivității este 4.
Implementarea recursivă a unui algoritm este eficientă numai dacă adâncimea recursivității nu este
mare.
3. Recursivitatea în cascadă este un tip de recursivitate în care pentru calcularea unui termen
subprogramul se autoapelează de două sau de mai multe ori.
Exemplu:

Șirul lui Fibonacci: ( ) {


( ) ( )
( )
( ) {
( ) { ( )
( ) ( )
( )
( ) {
{ ( )
28
Adâncimea recursivității este 9, iar cu cât n va fi mai mare cu atât adâncimea recursivității vai fi mai
mare.
4. Recursivitatea directă și indirectă.
a) În recursivitatea directă există un singur proces A, care își generează singur existența pe baza unor
condiții.
void A()
.............
{
…………….
A();
` ……………
}
b) În recursivitatea indirectă, există două procese A și B, care își generează reciproc apariția.
void B();
void A();
void main() {…………………..}
void A()
.............
{
…………….
B();
` ……………
}
void A()
.............
{
…………….
B();
` ……………
}
Exemplu:
Să se afișeze termenul n din următoarele șiruri: an = an-1 + bn-1 și bn = (bn-1)2 – (an-1)2, unde n, a0, b0 se
citesc de la tastatură.
.....
double an(int, float, float);
double bn(int, float, float);
void main()
{
int n; double a, b;
cout<<”n=”;cin>>n;
cout<<”a=”;cin>>a;
cout<<”b=”;cin>>b;
cout<<”a(“<<n<<)=”<<an(n, a, b)<<endl;
cout<<”b(“<<n<<)=”<<bn(n, a, b);
}
double an(int n, float a, float b)
{
if (n!=0) return a + b;
else return an(n-1, a, b) + bn(n-1, a, b)
}
double bn(int n, float a, float b)
{
if (n!=0) return b*b – a*a;

29
else return pow(bn(n-1, a, b), 2) – pow(an(n-1, a, b), 2)
}

5. Soluțiile recursive sunt mult mai clare, mai scurte și mai ușor de urmărit. Soluția recursivă este mult
mai avantajoasă decât cea iterativă, în următoarele cazuri:
- soluțiile problemei sunt definite recursiv
- cerințele problemei sunt formulate recursiv
În unele situații este foarte greu de definit o soluție iterativă, cum este cazul algoritmilor cu
recursivitate indirectă, și atunci se preferă algoritmul recursiv.
Alegerea unuia dintre cei doi algoritmi – iterativ sau recursiv – pentru rezolvarea unei probleme
depinde de programator, în funcție de avantajele și dezavantajele fiecăruia.

Implementarea recursivă a aloritmilor elementari


1. Determinarea minimului (maximului) dintre n numere citite
# include<iostream>
int min(int a, int b)
{
if (a>b) return b; else return min(b, a);
}
int main()
{
int nr, x, n, i;
cout<<”n=”; cin>>n;
cout<<”nr=”; cin>>nr;
x = nr;
for(i=2; i<=n; i++)
{
cout<<”nr=”; cin>>nr;
x= min(x,a);
}
cout<<”minum=”<<x;
}
2. Calculul c.m.m.d.c a două numere
a) Algoritmul lui Euclid

( ) {
( )

int cmmmdc(int a, int b)


{
if (a==0) return b;
else if (b ==0) return a;
else return cmmdc(b, a%b)
}
b) Algoritmul cu scăderi repetate

( ) { ( )
( )

int cmmmdc(int a, int b)


{
if (a==0) return b;

30
else if ((b ==0) || (a==b)) return a;
else if (a>b) return cmmdc(a – b, b);
else return cmmdc(a, b – a);
}
3. Prelucrarea cifrelor unui număr
a) Suma cifrelor unui număr
( ) {
( )

int suma_cif(int n)
{
if (n==0) return 0;
else return n%10 + suma_cif(n/10);
}
b) Obținerea inversului unui număr
( ) { ( ( ))

int inv(int n, int nr)


{
if (n<10) return n + nr;
else return inv(n/10, 10*(nr + n%10));
}

4. Testarea unui număr prim

( ) {
( )
sau
( ( ))
( ) { ( ( ))
( ) ( ( ))

..... .....
int prim(int n, int i) int prim(int n, int i)
{ {
if (i==1) return 1; if (i==ceil(sqrt(n))) return 1;
else if (n % i == 0) return 0; else if (n % i == 0) return 0;
else prim(n, i – 1); else prim(n, i + 1);
} }
int main() int main()
{ {
int n; int n;
cout<<”n=”; cin>>n; cout<<”n=”; cin>>n;
if prim(n,ceil(sqrt(n))) cout<<”nr. este if prim(n, 2) cout<<”nr. este prim”;
prim”; else cout<<”nr. nu este prim”
else cout<<”nr. nu este prim” }
}

5. Determinarea divizorilor unui număr


a) Afișarea divizorilor proprii ai unui număr
.....
int divizor(int n, int d)
31
{
if (d<n/2)
{
if (n % d == 0) cout<<d<<” “;
divizor(n, d + 1);
}
}
int main()
{
int n;
cout<<”n=”; cin>>n;
divizor(n, 2);
}
b) Descompunerea unui număr în factori primi
.......
int factor(int n, int f, int p)
{
if (n>1)
if (n % f == 0) factor(n/f, f, p+1);
else { if (p!=0) cout<<f<<”^ ”<<p;
factor(n, f + 1,0);}
else if (p!=0) cout<<f<<”^ ”<<p;
}
int main()
{
int n;
cout<<”n=”; cin>>n;
factor(n, 2, 0);
}

6. Conversia între baze de numerație


.......
int conversie(int n, int b)
{
int c;
c = n % b;
if (n >=b) conversie(n/b, b); // afișarea o va face după apelări de jos în sus
cout<<c;
}
int main()
{
int n;
cout<<”nr=”; cin>>n;
cout<<”baza=”; cin>>b;
conversie(n, b);
}

7. Câțiva algoritmi pentru prelucrarea tablourilor de memorie


a) Citirea și afișarea unui vector
......
int n, v[100];
int citire(int i)
{

32
cout<<”v[“<<i<<”]=”; cin>>v[i];
if (i!=n – 1) citire(i +1);
}
int afisare(int i)
{
cout<<v[i]<<” “;
if (i!=n – 1) afisare(i +1);
}

int main()
{
cout<<”n=”; cin>>n;
citire(0);
afisare(0);
}

b) Verificarea dacă un număr x se găsește în vector


......
int n, v[100];
int citire(int i)
{
cout<<”v[“<<i<<”]=”; cin>>v[i];
if (i!=n – 1) citire(i +1);
}
int gasit(int x, int i)
{
if (i>n – 1) return 0;
else if (x==v[i]) return 1
else gasit(x, i + 1);
}

int main()
{
cout<<”n=”; cin>>n;
citire(0);
if gasit(x, 0) cout<<”nr este in vector”;
else cout<<”nr. nu este in vector”;
}

c) Să se adune elementele unui vector


.....
int n,v[100];
int citire(int i)
{
cout<<"v["<<i<<"]=";cin>>v[i];
if (i!=n-1) citire(i+1);
}
int suma(int i)
{
if (i==n) return 0;
else return v[i]+suma(i+1);
}

33
int main()
{
cout<<"n="; cin>>n;
citire(0);
cout<<suma(0);
}

8. Tehnici de programare
a) Divide et impera
Divide et impera este o tehnică specială prin care se pot rezolva anumite probleme.
Acestă tehnică se bazează pe un principiu extrem de simplu: descompune problema în două sau mai
multe subprobleme (mai ușoare), care se rezolvă, iar soluțiile pentru problema inițială se obține combinând
soluțiile problemelor în care a fost descompusă.
Divide et impera este o tehnică ce admite o implementare recursivă: la un anumit nivel avem două
posibilități:
- am ajuns la o problemă care admite o rezolvare imediată, caz în care se rezolvă și se revine din apel
(condiția de terminare)
- nu am ajuns încă la o problemă cu rezolvare imediată, caz în care descompunem problema în două sau
mai multe subprobleme, pentru fiecare dintre ele reapelăm funcția, combinăm rezultatele și revenim
din apel.

Cel mai potrivit model pentru explicarea metodei îl constituie prelucrarea unui vector de forma (x0,
x1,…,xn-1).
Aplicarea metodei constă în împărțirea șirului dat în două subșiruri (x0,…xm), (xm+1,…,xn-1) și
prelucrarea fiecăruia dintre ele. Astfel, am descompus problema în două subprobleme de același tip. În
continuare, pentru fiecare dintre subșirurile obținute sunt posibile două situații:
- se poate prelucra direct;
- nu poate fi prelucrat direct, caz în care subșirul trebuie descompus în același fel. Lanțul de
descompuneri va continua până când obținem numai subșiruri prelucrabile direct.
O structură posibilă a acesteia ar fi:
- dacă subșirul (xi,…xj) poate fi prelucrat direct, atunci îl prelucrăm;
- în caz contrar: împărțim subșirul (xi,…xj) în alte 2 subșiruri: (xi,…xm) și (xm+1,…xj). Prelucrăm
subșirurile obținute: întrucât subșirul(xi,…xm) trebuie descompus mai departe în aceeași manieră,
rezultă că prelucrarea acestuia se va face printr-un auto-apel recursiv de forma Div_Imp (i,m); analog,
șirul (xm+1,…xj) se va prelucra prin auto-apelul Div_Imp (m+1,j)
Observații:
- De obicei elementul xm care determină partiționarea subșirului (xi,…xj), este cel aflat pe poziția
de mijloc: m = (i+j)/2. Astfel, subșirurile obținute vor avea dimensiuni apropiate și în consecință
subproblemele determinate de către aceste subșiruri vor fi apropiate din punct de vedere al
complexității.
- În procesul de descompunere, subșirurile pot fi alese și altfel:de exemplu, (xi,…xm-1) și (xm+1,…xj),
adică elementul de referință xm nu a fost inclus în nici unul dintre subșiruri;
- În general, un subșir poate fi prelucrat direct dacă nu are niciun element sau conține un singur
element.
-
subalgoritm Div_Imp(i, j, rez)
dacă i=j rezolvă(rez)
altfel m = (i + j)/2
Div_Imp(i, m, rez1)
Div_Imp(m+1, j, rez2)
rez = combină(rez1, rez2)
sfârșit subalgoritm

34
Exemple:
1. Să se afle maximul elementelor dintr-un vector.
….
int v[100], n;
int max(int i, int j)
{
int a, b;
if (i==j) return v[i];
else {
m = (i + j)/2;
a = max(i, m);
b = max(m+1, j);
if (a>b) return a;
else return b;
}
}

int main()
{
cout<<"n=";cin>>n;
for(i=0; i<n; i++) {cout<<"v["<<i+1<<"]=";cin>>v[i];}
cout<<max(0, n-1);
return 0;
}

2. Să se calculeze s = 12 + 23 + n(n + 1)


….
int n;
int suma(int i, int j, int &s)
{
int m, s1, s2;
if (i==j) s=j*(j+1);
else { m=(i+j)/2;
suma(i,m,s1);
suma(m+1,j,s2);
s = s1 + s2;
}
}

int main()
{ int s;
cout<<"n=";cin>>n;
suma(1, n, s);
cout<<s;
return 0;
}

3. Căutarea binară. Fie un vector cu n elemente ordonate crescător și un număr nr. Să se verifice dacă
numărul nr se află în vector sau nu, iar în caz afirmativ să se specifice poziția în vector.
….
int v[100], n, nr;
int caut(int i, int j, int &k)
{

35
int m;
if (i<j)
{ m=(i+j)/2;
if (nr==v[m]) k=m;
else if (nr<v[m]) caut(i,m,k);
else caut(m+1,j,k);
}
}
int main()
{ int i, k=0;
cout<<"n=";cin>>n;
for(i=0; i<n; i++) {cout<<"v["<<i+1<<"]=";cin>>v[i];}
cout<<"nr=";cin>>nr;
caut(0, n-1, k);
if (k==0) cout<<"nr nu a fost gasit";
else cout<<"nr a fost gasit pe pozitia "<<k+1;
return 0;
}
4. Turnurile din Hanoi. Se dau trei tije 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.

( ) {
( ) ( )

….
char a, b, c;
int n;
int hanoi(int n, char a, char b, char c)
{
if (n==1) cout<<a<<b<<” “;
else {hanoi(n-1, a, c, b);
cout<<a<<b<<” “;
hanoi(n-1, c, b, a);
}
}

int main()
{
cout<<"n=";cin>>n;
a=‟a‟; b=‟b‟; c=‟c‟;
hanoi(n, a, b, c);
return 0;
}

5. QuickSort (Sortarea rapidă)


Fie v un tablou cu n componente. Sortarea prin această metodă are la bază următorul principiu:
practic tabloul este ordonat când pentru fiecare element din tablou v[poz], elementele situate la
stânga sunt mai mici v[poz], iar cele din dreapta sunt mai mari sau egale decât v[poz]. Sortarea
tabloului v decurge astfel:
- la un moment dat se prelucrează o secvență din vector cu indecși cuprinși între i și j
- se ia una din aceste componente, fie ea piv=v[i], care se consideră element pivot;
36
- se fac în tablou interschimbări de componente, astfel încât toate cele mai mici decât valoarea
pivot sa treaca în stânga acesteia, iar elementele cu valoare mai mare decât pivot sa treacă în
dreapta; prin această operaţie se va deplasa şi valoarea pivot, astfel că el nu se va mai găsi pe
poziția inițială ci pe o poziție corespunzătoare relației de ordine. Fie aceasta k. În urma acestui
set de operații elementele din stânga sunt mai mici decat pivotul, dar nu neapărat și în ordine,
iar cele din dreapta sunt mai mari, dar nu neapărat și în ordine.
- se continuă setul de operații similare, aplicând recursiv metoda pentru zona de tablou situată în
stânga componentei pivot şi pentru cea din dreapta acesteia;
- oprirea recursiei se face când lungimea zonei de tablou care trebuie sortată devine egală cu 1.

7 4 2 8 5 9 3 10 1 6

6 4 2 8 5 9 3 10 1 7

6 4 2 8 5 9 3 10 1 7

6 4 2 8 5 9 3 10 1 7

6 4 2 7 5 9 3 10 1 8

6 4 2 1 5 9 3 10 7 8

6 4 2 1 5 9 3 10 7 8

6 4 2 1 5 7 3 10 9 8

6 4 2 1 5 7 3 10 9 8

6 4 2 1 5 3 7 10 9 8

În continuare se aplică aceeași secvență pe porțiunile din stânga, respectiv dreapta pivotului:
6 4 2 1 5 3

10 9 8

Complexitatea algoritmului QuickSort este O(n*log(n)).

#include <iostream>
using namespace std;
int v[100];
int n;

int poz(int i, int j)


{int piv, aux, k;
piv=v[i];
while (i<j)
{ if (v[i]>v[j]) {aux=v[i];
v[i]=v[j];
v[j]=aux;
}
if (v[i]==piv) j--;
else i++;
}
k=i;

37
return k;
}

void quick(int i, int j)


{int k;
if (i<j) {k=poz(i, j);
quick(i, k-1);
quick(k+1, j);
}
}

void main()
{ int i;
cout<<"n="; cin>>n;
for(i=0;i<n;i++)
{cout<<"v["<<i<<"]="; cin>>v[i];
}
quick(0,n-1);
for(i=0;i<n;i++) cout<<v[i]<<' ';
return 0;
}

6. MergeSort (Sortarea prin interclasare)


Această metodă de ordonare are la bază interclasarea a doi vectori (fiind dați doi vectori ordonați
se obține un al treilea vector ordonat care va conține elementele din cei doi vectori).
În cazul sortării prin interclasare vectorii care se interclasează sunt două secvențe ordonate din
același vector.
Sortarea prin interclasare utilizeaza metoda Divide et Impera:
- se împarte vectorul în secvențe din ce in ce mai mici, astfel încât fiecare secvență să fie ordonată
la un moment dat și interclasată cu o altă secvență din vector corespunzătoare.
- practic interclasarea va începe când se ajunge la o secvență formată din două elemente. Aceasta
odată ordonată se va interclasa cu o alta corespunzatoare. Cele două secvențe vor alcătui un subșir
ordonat din vector mai mare care la rândul lui se va interclasa cu subșirul corespunzător ș.a.m.d.
Exemplu:

8 7 9 3 6 4 17 16
Se împarte în două secvențe:
8 7 9 3 6 4 17 16
În continuare pentru prima secvență se procedează la fel:
8 7 9 3
După o nouă împărțire se oțtine:
8 7
Se începe interclasarea. Se consideră că avem doi vectori de lungime 1 care se interclasează:
8 7
Rezultă:
7 8
La fel și pentru secvența :
9 3 Rezultă:

3 9
Ceea ce înseamnă că cele două secvențe determină obținerea următorului subșir din vector:
7 8 3 9

38
Prin interclasare se obține:
3 7 8 9
La fel se procedează și cu secvența:
6 4 17 16
Obținându-se
4 6 16 17
Cele două secvențe inițiale din vector au devenit:
3 7 8 9 4 6 16 17
Care se interclasează obținând:
3 4 6 7 8 9 16 17

#include <iostream>
using namespace std;
int a[100], n, m;

int interclas(int i, int m, int j)


{int b[100];
int x=i, k=1, y=m+1, t=i;
while(x<=m && y<=j)
if (a[x]<a[y]) b[k++]=a[x++];
else b[k++]=a[y++];
while (x<=m) b[k++]=a[x++];
while (y<=j) b[k++]=a[y++];
for (k=1;k<=(j-i)+1;k++) a[t++]=b[k];
}

int mergesort(int i,int j)


{if (i<j) {m=(i+j)/2;
mergesort (i, m);
mergesort (m+1, j);
interclas(i, m, j);
}
}

int main()
{ int i;
cout<<"n="; cin>>n;
for(i=1;i<=n; i++) {cout<<"a["<<i<<"]="; cin>>a[i];}
mergesort(1,n);
for(i=1;i<=n;i++) cout<<a[i]<<' ';
return 0;
}

Complexitatea algoritmului de sortare prin interclasare este O(n.log2(n)).

b) Metoda backtracking
Tehnica backtracking se folosește în rezolvarea problemelor care îndeplinesc simultan condițiile:
C1) Soluția poate fi pusă sub forma unui vector s = (x1, x2, ..., xn) cu .
C2) A1, A2, ..., An sunt mulțimi finite cu elemente aflate într-o relație de ordine bine stabilită
C3) Nu se dispune de o altă metodă de rezolvare mai rapidă.
Observații:
1. Nu pentru toate problemele, n este cunoscut de la început
39
2. x1, x2, ..., xn pot fi la rândul lor vectori
3. În multe probleme A1, A2, ..., An coincid
4. Dacă nu cunoaștem această tehnică, suntem tentați, atunci când întâlnim astfel de probleme, să
generăm toate elementele produsului cartezian și fiecare element să fie testat dacă
este soluție (timpul de execuție poate fi considerat infinit).
Prezentarea tehnicii
- Pentru că soluția este un vector, vom utiliza o stivă:

xk-1

x2
st x1

- Nivelul k al stivei trebuie inițializat cu o valoare aflată înaintea tuturor valorilor posibile din mulțimea
Ak. Acest lucru îl vom realiza cu subprogramul init()
- Găsirea următorului element din Ak, element netestat, se face cu ajutorul funcției succesor(). Dacă
există succesor, acesta este pus în stivă și funcția returnează 1, altfel funcția returnează 0.
- Odată ales un element trebuie văzut dacă acesta îndeplinește condițiile de continuare. Acest lucru se
realizează cu funcția valid(), care returnează valoarea 1, dacă condițiile sun îndeplinite sau 0 în caz
contrar.
- Apoi se testează dacă s-a ajuns la soluția finală cu ajutorul subrogramului soluție().
- Soluția se tipărește cu subrogramul tipărire().

Rutina backtracking:
void back()
{
int as, ev;
k = 1; init();
while (k > 0)
{
do {} while (as = succesor() && !valid());
if (as) if (solutie()) tiparire();
else { k ++; init();}
else k – –;
}
}

Exemple:
1. Să se genereze toate permutările mulțimii {1, 2, …, n}, unde n este un număr natural.
A1 = A2 = ... = An = {1, 2, …, n}
Condiția de continuare: numerele să fie distincte în stivă
Exemplu:
n=3
1 2 3 1 2 3 1
1 2 2 2 2 3 3 3 3 1 1
1 1 1 1 1 1 1 1 1 1 2 2 2
sol sol

2 3 1 2 3 1 2 3
1 1 2 3 3 3 3 1 1 1 1 2
2 2 2 2 2 2 2 3 3 3 3 3 3
sol sol sol
40
1 2 3
2 2 2 3
3 3 3 3 3
sol

# include <iostream> {
using namespace std; int i;
int st[10], n, k; for(i = 1; i < n; i++) cout<<st[i];
cout<<endl;
void init() }
{st[k] = 0;}
void back()
int succesor() {
{ int as, ev;
if (st[k] < n) {st[k] ++; return 1;} k = 1; init();
else return 0; while (k > 0)
} {
do {} while (as = succesor() && !valid());
int valid() if (as) if (solutie()) tiparire();
{ else { k ++; init();}
int i; else k – –;
for(i = 1; i < k; i++) }
if (st[i] == st[k]) return 0; }
return 1;
} void main()
{
int solutie() cout<<”n=”; cin>>n;
{return k==n;} back();
}
void tiparire()

2. Să se genereze toate aranjamentele de n luate câte p.


Condițiile de continuare: numerele să fie distincte în stivă

# include <iostream> }
using namespace std;
int st[10], n, p, k; int solutie()
{return k==p;}
void init()
{st[k] = 0;} void tiparire()
{
int succesor() int i;
{ for(i = 1; i < p; i++) cout<<st[i];
if (st[k] < n) {st[k] ++; return 1;} cout<<endl;
else return 0; }
}
void back()
int valid() {
{ int as, ev;
int i; k = 1; init();
for(i = 1; i < k; i++) while (k > 0)
if (st[i] == st[k]) return 0; {
return 1; do {} while (as = succesor() && !valid());

41
if (as) if (solutie()) tiparire(); void main()
else { k ++; init();} {
else k – –; cout<<”n=”; cin>>n;
} cout<<”p=”; cin>>p;
} back();
}

3. Să se genereze toate combinările de n luate câte p.


Condițiile de continuare:
- numerele să fie distincte în stivă
- pentru că nu contează ordinea, se generează în ordine crescătoare

# include <iostream> {
using namespace std; int i;
int st[10], n, p, k; for(i = 1; i < p; i++) cout<<st[i];
cout<<endl;
void init() }
{st[k] = 0;}
void back()
int succesor() {
{ int as, ev;
if (st[k] < n) {st[k] ++; return 1;} k = 1; init();
else return 0; while (k > 0)
} {
do {} while (as = succesor() && !valid());
int valid() if (as) if (solutie()) tiparire();
{ else { k ++; init();}
int i; else k – –;
for(i = 1; i < k; i++) }
if (st[i] >= st[k]) return 0; }
return 1;
} void main()
{
int solutie() cout<<”n=”; cin>>n;
{return k==p;} cout<<”p=”; cin>>p;
back();
void tiparire() }

4. Problema damelor
Să se așeze pe o tablă de șah n×n, n dame, care să nu se atace reciproc. Două dame se atacă dacă se află
pe aceeași linie, pe aceeași coloană sau pe aceeași diagonală.

# include <iostream> }
using namespace std;
int st[10], n, k; int valid()
{
void init() int i;
{st[k] = 0;} for(i = 1; i < k; i++)
if ((st[i] == st[k]) || (abs(st[k] – st[i])==abs(k-
int succesor() i)))return 0;
{ return 1;
if (st[k] < n) {st[k] ++; return 1;} }
else return 0;

42
int solutie() {
{return k==n;} do {} while (as = succesor() && !valid());
if (as) if (solutie()) tiparire();
void tiparire() else { k ++; init();}
{ else k – –;
int i; }
for(i = 1; i < n; i++) cout<<st[i]; }
cout<<endl;
} void main()
{
void back() cout<<”n=”; cin>>n;
{ cout<<”p=”; cin>>p;
int as, ev; back();
k = 1; init(); }
while (k > 0)

9. Lista
Lista este o structură de date logică, liniară, cu date omogene în care fiecare element are un succesor și un
predecesor, exceptând primul element, care nu are decât succesor, și ultimul element, care nu are decât
predecesor.

a1 → a1 → ...→ ak-1 → ak → ak+1 → ...→ an

a1- nu are predecesor ak-1 este predecesorul lui ak an- nu are succesor
ak+1 este succesorul lui ak
Observații:
1) Dacă n = 0, atunci lista este vidă.
2) Elementele listei se numesc noduri
Operații catre se pot executa unei liste:
- inițializarea listei – se creează lista vidă
- crearea listei – se adaugă repetat elemente în listă, pornind de la lista vidă
- adăugarea unui element în listă – la sfârșit, la început sau în interior
- ștergerea unui element din listă – de la sfârșit, de la început sau din interior
- parcurgerea listei – se vizitează elementele listei pentru a obține informații
- căutarea unui element care îndeplinește anumite condiții
- schimbarea unui nod în cadrul listei

Implementarea secvențială a listelor se face cu ajutorul vectorilor


Cazuri particulare de liste:
a) Stiva este o listă pentru care singurele operații permise sunt:
- adăugarea unui element în stivă (PUSH) în vârf
- eliminarea, consultarea sau modificarea ultimului element introdus în stivă (POP)

Pentru că în stivă accesul este permis doar la un singur capăt, numit vârful stivei, siva se mai numește
listă de tip LIFO (Last In First Out)
Exemplu: stiva de farfurii
Implementarea secvențială a stivei:
POP PUSH

an vârful stivei
...
a2
a1
43
const int DimMax=100ș
typedef int stiva[DimMax]; // partea de declarare a stivei
stiva s;

void PUSH(stiva s, int &vf, int x)


{ void POP(stiva s, int &vf)
if (vf ==DimMax) cout<<”stiva este plina”; {
else { vf++; if (vf ==0) cout<<”stiva este vida”;
s[vf]=x; else vf – –;
} }
}

Exemplu:
B vf=2
A vf=1 A A vf=0
se adaugă se scoate B se scoate A
deasupra lui A (A nu poate fi
scos deocamdată)

b) Coada este o listă pentru care toate adăugările sunt făcute la unul din capete, numit sfârșit, iar toate
ștergerile, consultările sau modificările la celălat capăt, numit vârf.
Coada funcționează pe principiul FIFO (First In First Out)

Exemplu: coada reală (de la magazin)


Implementarea secvențială a stivei:

POP ← ← PUSH
a1 a2 ..... an-1 an

vârf (vf) sfârșit (sf)

const int DimMax=100ș


typedef int coada[DimMax]; // partea de declarare a stivei
coada c;

void PUSH(coada c, int &sf, int x) void POP(coada c, int &sf)


{ {
if (sf ==DimMax) cout<<”coada este plina”; if (vf >sf) cout<<”coada este vida”;
else { sf++; else {for (vf=2; vf<=n; vf++) c[vf-1]=c[vf];
c[sf]=x; sf – –;
} }
} }

Exemplu:

1 2 3 4 5 se poate elimina doar 1


vf sf

2 3 4 5 se poate adăuga doar după 5

2 3 4 5 6

44
10. Grafuri

10.1. Grafuri neorientate


A. Noțiuni de bază
Definiție
Se numește graf neorientat o pereche ordonată de mulțimi G = (X, Γ), unde X = {x1, x2, …, xn} este o
mulțime finită și nevidă de elemente numite vârfuri sau noduri, iar Γ = X×X este o mulțime de perechi
neordonate de câte două elemente din X, numite muchii sau arce.
Notație:
(x, y) – muchia ce trece prin vârfurile x și y.
Exemplu:
2 3 X = {1, 2, 3, 4, 5, 6, 7}
1 Γ = {(1, 2), (2, 3), (2, 4), (3, 4), (5, 6)}

6
5
7
Obs.
1. Muchia (x, y) este totuna cu (y, x) în grafurile neorientate.
2. x și y sunt vârfuri adiacente și se numesc extremitățile muchiei (x, y).
3. Vârful x este incident cu muchia (x, y), la fel și vârful y.
Definiție
Se numește gradul unui vârf, numărul muchiilor care trec prin vârful x.
Notație:
d(x) – gradul vârfului x
Exemplu:
d(2) = 3
Obs.
1. Vârful cu gradul 0 se numește vârf izolat.
2. Vârful cu gradul 1 se numește vârf terminal.
3. d(x1) + d(x2) + … + d(xn) = 2 * numărul muchiilor
( )
4. Numărul total de grafuri neorientate care se poate realiza cu n vârfuri este de .
5. Numărul minim de muchii necesar astfel încât să nu existe noduri izolate este de 0 1.

B. Moduri de reprezentare
1. Matricea de adiacență
Definiție:
( )
( ) ̅̅̅̅
{ este matricea de adiacență.
Obs:
1. A este o matrice simetrică față de diagonal principală: ( ) ̅̅̅̅̅
2. Elementele de pe diagonala principală sunt nule: .
Exemplu:

2 3
1
( )
4

45
2. Liste de adiacență
Definiție
Fiecărui vârf se atașează lista succesorilor săi.
Exemplu:
1: 2
2: 1, 3, 4
3: 2, 4
4: 2, 3

C. Graf parțial și subgraf


Definiție
Fie graful G = (X, Γ). Se numește graf parțial al lui G, graful G1 = (X, Γ1), unde .
Obs:
1. Un graf parțial G1 se obține din graful G păstrând toate vârfurile și suprimând niște muchii.
2. Numărul total de grafuri parțiale ale unui graf cu m muchii este 2m.
Exemplu:
2 3 2 3
1 → 1

4 4
graf graf parțial

Definiție
Fie graful G = (X, Γ). Se numește subgraf al lui G, graful G1 = (Y, Γ1), unde și , iar Γ1 va
conține numai muchiile care au ambele extremități în Y.
Obs:
1. Un subgraf G1 se obține din graful G eliminând niște vârfuri și păstrând doar acele muchii care au
ambele extremități în mulțimea vârfurilor rămase.
2. Numărul total de subgrafuri ale unui graf cu n vârfuri este 2n – 1.
Exemplu:
2 3 2 3
1 → 1

graf subgraf

D. Graf complet și graf bipartit


Definiție
Graful G = (X, Γ) se numește graf complet cu n vârfuri dacă ( ) ( )( ) .
Notație:
Kn – graf complet
Exemplu:
2

1 3

4 5

46
Obs:
( )
Un graf complet cu n vârfuri are muchii.
Definiție
Graful G = (X, Γ) se numește graf bipartit dacă ( ) astfel încât:
1)
2) Toate muchiile grafului au o extremitate în A și cealaltă în B.
Exemplu:
4
1
5
A = {1, 2, 3}
2 B = {4, 5, 6, 7}
6

3
7

Definiție
Se numește graf bipartit complet un graf bipartit cu proprietatea că ( ) ( )( ).
Exemplu:
3
1
4
A = {1, 2}
2 B = {3, 4, 5}
5

E. Lanț și ciclu
Definiție
Fie G = (X, Γ). Se numește lanț în graful G, o succesiune de vârfuri L = (z1, z2, …,zk), z1, z2, …,zk X
cu proprietatea că există muchiile (z1, z2), (z2, z3), ..., (zk-1, zk) Γ.
Obs:
1. Lanțul este un traseu care pleacă din vârful z1 și ajunge în vârful zk, trecând prin mai multe vârfuri și
parcurgând mai multe muchii.
2. z1, zk sunt extremitățile lanțului
3. nr. de muchii = lungimea lanțului
Exemplu:

2 5

1 3 4
8
6 7 9

L1 = (1, 2, 3, 5, 4, 8), L2 = (1, 2, 3, 2), L3 = (9, 3, 5, 4, 3, 2), L4 = (6, 7, 3, 5, 4, 8)


Definiție
Se numește lanț elementar lanțul cu vârfurile z1, z2, …,zk distincte două câte două. În caz contrar, lanțul
este ne-elementar.
Exemplu:
L1, L4 sunt lanțuri elementare
L2, L3 sunt lanțuri ne-elementare
47
Definiție
Lanțul L = (z1, z2, …,zk), z1, z2, …,zk X cu proprietatea că z1 = zk și muchiile (z1, z2), (z2, z3), ..., (zk-1,
zk) sunt distincte două câte două se numește ciclu.
Exemplu:
C1 = (3, 4, 5, 3, 7, 6, 1, 2, 3), C2 = (1, 2, 3, 7, 6,1), C3 = (3, 5, 4, 9, 3)
Definiție
Se numește ciclu elementar ciclul cu proprietatea că toate vârfurile , cu excepția primului și ultimului,
sunt distincte două câte două. În caz contrar, ciclul este ne-elementar.
Exemplu:
C2, C3 sunt cicluri elementare
C1 este ciclu ne-elementare

F. Parcurgerea grafurilor
1. Parcurgerea în lățime BF (Breadth First)
Se pornește de la un vârf de start, care se vizitează, apoi se vizitează toți vecinii săi. Pentru fiecare
dintre aceste vârfuri, se vizitează vecinii lui, care nu au fost încă vizitați. Pentru noile vârfuri se
procedează la fel. Procedeul continuă până când s-au vizitat toate vârfurile.
Exemplu:

1
5
BF: 1, 2, 5, 6, 3, 4, 7
7, 5, 6, 1, 2, 3, 4
2 7

3 4
6

2. Parcurgerea în adâncime DF (Depth First)


Se pornește de la un vârf de start, care se vizitează, apoi se vizitează primul dintre vecinii săi
nevizitați încă. Cu acest vârf se continuă în același mod.
Exemplu:

1
5
DF: 1, 2, 3, 4, 5, 7, 6
7, 5, 1, 2, 3, 4, 6
2 7

3 4
6

G. Conexitatea
Definiție:
Un graf G = (X, Γ) este conex dacă oricare ar fi două vârfuri ale sale există un lanț care le leagă.
Exemplu: Contraexemplu:
1 1
5 2 5
2

7 3 4 7
3 4
6 6
48
Definiție:
Fie graful G = (X, Γ). Subgraful G1 = (Y, Γ1) al lui G cu proprietatea că nu există nici un lanț care să lege
un vârf din Y cu un vârf din X\Y se numește componentă conexă.
Exemplu:
1
2 5
Y = {1, 2, 3, 4} componentă conexă
Z = {5, 6, 7} component conexă
3 4 7
6

Obs:
1. Dacă numărul componentelor conexe dintr-un graf este mai mare decât 1, atunci graful nu este conex.
2. Numărul minim de muchii necesare pentru ca un graf să fie conex este de n – 1, unde n este numărul
de vârfuri.
3. Numărul maxim de de muchii dintr-un graf neorientat cu n vârfuri și p component conexe este de
( )( )
.
4. Pentru a obține dintr-un graf neorientat conex, 2 componente conexe, numărul minim de muchii care
trebuie eliminate este egal cu gradul minim din graf.

H. Grafuri hamiltoniene și euleriene


Definiție:
1. Se numește lanț hamiltonian un lanț ce conține toate vârfurile.
2. Se numește ciclu hamiltonian un ciclu elementar ce conține toate vârfurile.
3. Se numește graf hamiltonian un graf care conține un ciclu hamiltonian.
Exemplu:
1
2
L = (2, 3, 5, 4, 1) – lanț hamiltonian
5
C = (1, 2, 3, 5, 4, 1) – ciclu hamiltonian
3 4

Teoremă:
Dacă într-un graf G = (X, Γ) cu vârfuri, gradul fiecărui vârf verifică condiția ( ) , atunci
graful este hamiltonian.
Obs:
( )
Numărul de cicluri hamiltoniene într-un graf complet cu n noduri este .
Definiție:
1. Se numește ciclu eulerian un ciclu elementar ce conține toate muchiile grafului.
2. Se numește graf eulerian un graf care conține un ciclu eulerian.
Exemplu:

2 6

1 3 7
5 4 8
C = (1, 2, 3, 6, 7, 8, 3, 4, 5, 1) – ciclu eulerian

49
Teoremă:
Un graf G = (X, Γ) fără vârfuri izolate este eulerian dacă și numai dacă este conex și gradul vârfurilor
sunt numere pare.

10.2. Grafuri orientate


A. Noțiuni de bază
Definiție
Se numește graf orientat o pereche ordonată de mulțimi G = (X, Γ), unde X = {x1, x2, …, xn} este o
mulțime finită și nevidă de elemente numite noduri, iar Γ = X×X este o mulțime de perechi ordonate de câte
două elemente din X, numite arce.
Exemplu:
X = {1, 2, 3, 4}
1 Γ = {(1, 1), (2, 1), (2, 3), (2, 4), (3, 2), (3, 4), (4, 3)}

3 4

Obs.
1. arcul (x, y) nu este totuna cu (y, x) în grafurile orientate.
2. x și y sunt noduri adiacente. x se numește extremitatea inițială a arcului (x, y), iar y se numește
extremitatea finală a arcului (x, y)
3. nodul x este incident cu arcul (x, y), la fel și nodul y.
4. y este succesorul lui x, iar x este predecesorul lui y.
5. (x, x) se numește buclă.
Definiție
Se numește p-graf un graf orientat în care numărul arcelor identice este mai mic sau egal cu o valoare
dată p.
Exemplu:
Graful din exemplul anterior este un 2-graf.
Definiție
1. Se numește grad extern al nodului x, numărul arcelor care ies din nodul x.
2. Se numește grad intern al nodului x, numărul arcelor care intră în nodul x.
Notații:
d+(x) – gradul extern al nodului x
d–(x) – gradul intern al nodului x
Exemplu:
d+(2) = 3
d–(2) = 1
Obs.
1. Suma gradelor interne ale tuturor nodurilor este egală cu suma gradelor externe ale tuturor nodurilor
și cu numărul de arce.
( )
2. Numărul total de grafuri orientate care se poate realiza cu n noduri este de .
Definiție:
X+(x) = * ( ) + – mulțimea succesorilor lui x
X–(x) = * ( ) + – mulțimea predecesorilor lui x
Exemple:
X+(2) = * +

X (2) = * +
Definiție:
+
(x) = *( ) ( ) + – mulțimea arcelor care ies din nodul x
50

(x) = *( ) ( ) + – mulțimea arcelor care intră în nodul x
Exemple:
+
(2) = *( ) ( ) ( )+

(2) = *( )+
Obs.
1. Noțiunile de graf parțial și subgraf se păstrează de la grafuri neorientate.
( )
2. Numărul de grafuri orientate complete cu n noduri este de .

B. Moduri de reprezentare
1. Matricea de adiacență
Definiție:
( )
( ) ̅̅̅̅
{ este matricea de adiacență.
Obs:
A nu este o matrice simetrică față de diagonal principală, iar diagonala principală nu este întotdeauna
nulă.
Exemplu:

2
( )

3 4

2. Liste de adiacență
Definiție
Fiecărui vârf se atașează lista succesorilor săi.
Exemplu:
1: 1
2: 1, 3, 4
3: 2, 4
4: 3
3. Matricea de incidență
Definiție:
( )
( ) ̅̅̅̅
{ ( ) este matricea de incidență.

Exemplu:

( )

C. Drumuri și circuite
Definiție
Se numește lanț al grafului orientat G = (X, Γ) mulțimea arcelor L = {a1, a2, …, ak} cu proprietatea că
oricare două arce vecine în mulțime au o extremitate comună.
Obs.
Lanțul este, de fapt, un traseu care unește prin arce două noduri numite extremitățile lanțului fără a ține
cont de orientarea arcelor componente.

51
Exemplu:
2
L1 = {(4, 3), (4, 2), (2, 1)}
L2 = {(3, 2), (4, 2), (4, 3), (1, 3)}
1

3 4

Definiție
Se numește drum al grafului orientat G = (X, Γ) un șir de noduri D = (x1, x2, …, xk) cu proprietatea că
oricare două noduri consecutive sunt adiacente, adică există (x1, x2), (x2, x3), ..., (xk-1, xk) .
Obs.
Dacă nodurile x1, x2, …, xk sunt distincte două câte două, atunci drumul se numește elementar. În caz
contrar, drumul se numește ne-elementar.
Exemplu:
2
D1 = (3, 4, 2, 1) – drum elementar
D2 = (3, 4, 3, 2, 1) – drum ne-elementar

3 4

Definiție
Se numește circuit al grafului orientat G = (X, Γ) un drum C = (x1, x2, …, xk) cu proprietatea că x1 = xk
și arcele (x1, x2), (x2, x3), ..., (xk-1, xk) sunt distincte două câte două.
Obs.
Dacă nodurile, cu excepția primului și ultimului, sunt distincte două câte două, atunci circuitul se
numește elementar. În caz contrar, circuitul se numește ne-elementar.
Exemplu:
1 2
C1 = (3, 5, 6, 4, 3) – circuit elementar
C2 = (1, 3, 2, 1) – circuit elementar
C3 = (4, 3, 2, 1, 3, 5, 6, 4) – circuit ne-elementar
3

4 5

D. Matricea drumurilo
Definiție
( ) ̅̅̅̅
{ este matricea drumurilor.

Exemplu:
2
1
3 ( )

4 52
10.3. Arbori
A. Noțiuni de bază
Definiție
Se numește arbore un grafului neorientat conex și fărăr cicluri.
Exemplu:
X are trei copii. Fiecare dintre copii se va căsători și va avea alți copii, devenind la rândul său părinte ș.
a. m. d.
1

2 3 4

5 6 7

8 9

Obs.
1. Nodul in care nu intră nici un arc se numește rădăcina arborelui.
2. Cu excepția rădăcinii, fiecare nod are proprietatea că în el intră un singur arc. Acesta leagă nodul
respective cu un alt nod numit predecesor sau părinte. De exemplu, 4 este părintele lui 7.
3. Dintr-un nod pot ieși unul sau mai multe arce. Fiecare astfel de arc leagă nodul respective de un alt
nod numit successor sau fiu. De exemplu, 8 este fiul lui 6.
4. Nodurile din care nu mai iese nici un arc se numesc noduri terminale sau frunze. De exemplu, 3, 5,
7, 8, 9 sunt frunze.
Teoremă
Un graf G = (X, Γ) cu n noduri este arbore dacă și numai dacă are n-1 arce și nu conține cicluri.
Obs.
Un arbore cu n noduri are n-1 arce.
Definiție
Se numește arbore binar strict un arbore care are proprietatea că fiecare nod, cu excepția nodurilor
terminale, are exact 2 descendenți.
Se numește arbore binar complet un arbore binar strict care are toate nodurile terminale pe același nivel.
Obs.
1. Un arbore binar strict care are n noduri terminale are în total 2n – 1 noduri.
2. Un arbore binar complet care are n noduri terminale are în total 2n – 1 noduri.
3. Înălțimea unui arbore este data de numărul maxim dintre nivelurile nodurilor terminale.

B. Moduri de reprezentare
1. Matricea de adiacență
2. Liste de adiacență
3. Legătura de tip TATA
Arborele se reprezintă sub forma unui vector T cu n elemente: , - {

53
Exemple:
1. T = (0, 1, 1, 1, 2, 2, 4, 6, 6)
2.
5

T = (5, 3, 5, 5, 0, 3, 4, 1, 7)
3 1 4

2 6 8 7

54

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