Documente Academic
Documente Profesional
Documente Cultură
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).
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};
9. Fişiere antet
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:
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;
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]
}
e) Instrucţiunea for:
Pseudocod C++
pentru contorvi, 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);
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.
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.
10
Pentru a avea acces la manipulatori trebuie să folosim fişierul antet iomanip.h.
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
Declararea unui vector cu atribuirea unor valori iniţiale: tip_dată nume[nr_elemente] = {listă_valori}
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.
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
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}
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:
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
<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;
d) Tablouri de înregistrări
Exemplu:
struct elev
{char nume[20], pren[10]; //declarare înregistrare
unsigned nota1, nota2;
float media};
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;
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
23
b. Funcția operand – subprogramul care returnează un rezultat prin chiar numele său, și eventual și alte
rezultate, prin intermediul parametrilor
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;
}
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;
}
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;
}
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!.
( ) {
( )
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)
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:
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.
( ) {
( )
( ) { ( )
( )
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
( ) { ( ( ))
( ) {
( )
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” }
}
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);
}
int main()
{
cout<<”n=”; cin>>n;
citire(0);
if gasit(x, 0) cout<<”nr este in vector”;
else cout<<”nr. nu este in vector”;
}
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;
}
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;
}
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
#include <iostream>
using namespace std;
int v[100];
int n;
37
return k;
}
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;
}
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 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;
}
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()
# 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();
}
# 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- 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
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;
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)
POP ← ← PUSH
a1 a2 ..... an-1 an
Exemplu:
2 3 4 5 6
44
10. Grafuri
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
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
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
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
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.
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.
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