Informatica clasa 11
Curs Complet de Informatică — Clasa a XI-a¶
Specializarea: Matematică-Informatică (Curriculum de Specialitate)
Limbaje: Python (principal) și C++ (secundar)
Bazat pe programa școlară aprobată prin Ordinul MEC 4.350/2025 (Anexa 46)
📋 Cuprins¶
- Introducere
- Capitolul 1: Metoda Backtracking
- Capitolul 2: Modelul conceptual rețea — grafuri (concepte)
- Capitolul 3: Algoritmi pentru prelucrarea grafurilor
- Capitolul 4: Modelul conceptual ierarhic — arbori
- Capitolul 5: Algoritmi pentru prelucrarea arborilor
- Capitolul 6: Modelul conceptual obiectual
- Capitolul 7: Programare orientată pe obiecte în Python
- Capitolul 8: Programare orientată pe obiecte în C++
- Capitolul 9: Paradigme de bază în programare
- Anexe
Introducere¶
Despre acest curs¶
Clasa a XI-a reprezintă saltul cel mai important în formarea unui informatician. Cu 4 ore/săptămână (dublu față de clasele anterioare), această clasă introduce:
- Backtracking — generarea sistematică a tuturor soluțiilor unei probleme.
- Grafuri — modelarea relațiilor complexe dintre entități (rețele, hărți, ierarhii).
- Arbori — structuri ierarhice cu proprietăți specifice.
- Programarea Orientată pe Obiecte (POO) — un nou mod de a gândi programarea.
- Paradigme de programare — perspective asupra diverselor stiluri de cod.
Recapitulare necesară din clasele anterioare¶
Pentru a parcurge cu succes această clasă, trebuie să stăpânești:
- Recursivitatea (clasa X) — esențială pentru backtracking, parcurgeri de grafuri și arbori.
- Divide et Impera, Greedy (clasa X) — fundamentul strategiilor algoritmice.
- Liste de liste, matrice (clasa X) — pentru matrice de adiacență.
- Dicționare, mulțimi (clasa X) — pentru liste de adiacență și noduri vizitate.
- Subprograme, fișiere (clasa IX) — pentru cod modular și lucru cu date reale.
Ce urmează în clasa a XII-a?¶
- Programare dinamică.
- Baze de date (SQL).
- Algoritmi de învățare automată (Machine Learning).
Ordine recomandată¶
Conform programei, ordinea recomandată este:
- Backtracking (începem cu această strategie — o vom folosi la grafuri și arbori).
- Grafuri (concepte + algoritmi).
- Arbori (concepte + algoritmi).
- POO (model obiectual + sintaxă).
- Paradigme de programare.
Capitolul 1: Metoda Backtracking¶
1.1. Ce este Backtracking?¶
Backtracking („întoarcere din drum”) este o strategie de rezolvare care explorează toate variantele posibile ale unei probleme, construind pas cu pas soluții candidate. Când o variantă nu mai poate fi continuată, algoritmul se întoarce („backtrack”) și încearcă altă alegere.
Analogie intuitivă: un labirint — explorezi un coridor; dacă te blochezi, te întorci la ultima intersecție și încerci alt drum.
1.2. Când folosim Backtracking?¶
- Trebuie să găsim toate soluțiile unei probleme (combinații, permutări).
- Căutăm soluția într-un spațiu exponențial de variante.
- Putem elimina devreme („prune”) ramuri care nu duc la soluții valide.
Exemple de probleme:
- Generare permutări, aranjamente, combinări, produs cartezian.
- Problema reginelor (N-regine pe o tablă N×N).
- Problema colorării hărții.
- Problema comis-voiajorului.
- Sudoku, labirinturi.
1.3. Schema generală a algoritmului¶
Back(k):
pentru fiecare valoare v posibilă pe poziția k:
dacă v este validă (nu încalcă restricțiile):
plasează v pe poziția k
dacă am ajuns la soluție completă:
afișează soluția
altfel:
Back(k + 1) # recurge pe nivelul următor
(retragere implicită — alta valoare va fi încercată)
Ingredientele:
- Vector soluție
st[]— reține alegerile curente. - Funcție de validare — verifică dacă alegerea curentă e legală.
- Condiție de soluție completă — când ne oprim.
1.4. Generarea permutărilor¶
Permutări ale mulțimii {1, 2, …, n} = toate aranjările distincte ale celor n numere.
Exemplu pentru n=3:
123 132 213 231 312 321
Sunt n! = 6 permutări.
Implementare Python¶
def permutari(n):
st = [0] * n
folosit = [False] * (n + 1)
def back(k):
if k == n: # soluție completă
print(*st)
return
for v in range(1, n + 1): # încercăm fiecare valoare
if not folosit[v]: # validare: nu e deja folosit
st[k] = v
folosit[v] = True
back(k + 1) # recurgem
folosit[v] = False # backtrack
back(0)
permutari(3)
Implementare C++¶
#include <iostream>
using namespace std;
int n, st[20];
bool folosit[21];
void back(int k) {
if (k == n) {
for (int i = 0; i < n; i++) cout << st[i] << " ";
cout << endl;
return;
}
for (int v = 1; v <= n; v++) {
if (!folosit[v]) {
st[k] = v;
folosit[v] = true;
back(k + 1);
folosit[v] = false;
}
}
}
int main() {
n = 3;
back(0);
return 0;
}
1.5. Generarea aranjamentelor¶
Aranjamentele A(n, k) = permutările de câte k elemente dintr-o mulțime de n.
Pentru n=3, k=2: 12 13 21 23 31 32 — sunt A(3,2) = 6.
def aranjamente(n, k):
st = [0] * k
folosit = [False] * (n + 1)
def back(poz):
if poz == k:
print(*st)
return
for v in range(1, n + 1):
if not folosit[v]:
st[poz] = v
folosit[v] = True
back(poz + 1)
folosit[v] = False
back(0)
aranjamente(3, 2)
1.6. Generarea combinărilor¶
Combinările C(n, k) = submulțimile de k elemente ale unei mulțimi de n, fără a conta ordinea.
Pentru n=4, k=2: {1,2} {1,3} {1,4} {2,3} {2,4} {3,4} — C(4,2) = 6.
Trucul: alegem elementele în ordine crescătoare pentru a evita duplicatele.
def combinari(n, k):
st = [0] * k
def back(poz, start):
if poz == k:
print(*st)
return
for v in range(start, n + 1):
st[poz] = v
back(poz + 1, v + 1) # următorul trebuie să fie > v
back(0, 1)
combinari(4, 2)
C++¶
void comb_back(int poz, int start) {
if (poz == k) {
for (int i = 0; i < k; i++) cout << st[i] << " ";
cout << endl;
return;
}
for (int v = start; v <= n; v++) {
st[poz] = v;
comb_back(poz + 1, v + 1);
}
}
// apelat: comb_back(0, 1);
1.7. Produsul cartezian¶
Produsul cartezian {1..n} × {1..n} × … (k factori) = toate secvențele de lungime k cu elemente din {1..n}, cu repetiție permisă.
Pentru n=2, k=3:
111 112 121 122 211 212 221 222
def produs_cartezian(n, k):
st = [0] * k
def back(poz):
if poz == k:
print(*st)
return
for v in range(1, n + 1):
st[poz] = v
back(poz + 1)
back(0)
produs_cartezian(2, 3)
1.8. Problema celor n regine¶
Problemă: Plasează n regine pe o tablă n × n astfel încât niciuna să nu atace pe alta (nu pe aceeași linie, coloană sau diagonală).
Idee: Pe fiecare linie plasăm exact o regină. st[i] = coloana pe care e regina de pe linia i.
Restricții — două regine (i1, st[i1]) și (i2, st[i2]) se atacă dacă:
st[i1] == st[i2](aceeași coloană)|i1 - i2| == |st[i1] - st[i2]|(aceeași diagonală)
def regine(n):
st = [0] * n
nr_solutii = [0]
def valid(poz):
for i in range(poz):
if st[i] == st[poz] or abs(i - poz) == abs(st[i] - st[poz]):
return False
return True
def back(poz):
if poz == n:
nr_solutii[0] += 1
# afișare tablă
for i in range(n):
print('. ' * st[i] + 'Q ' + '. ' * (n - st[i] - 1))
print()
return
for col in range(n):
st[poz] = col
if valid(poz):
back(poz + 1)
back(0)
print(f"Număr total soluții: {nr_solutii[0]}")
regine(4) # 2 soluții
# regine(8) # 92 soluții
Complexitate: Fără validare — O(nⁿ). Cu validarea timpurie, mult mai rapid.
1.9. Problema partițiilor¶
Problemă: Scrie numărul n ca sumă de numere naturale nenule (ordinea contează mai puțin — compoziții vs. partiții).
Compoziții ale lui 4 (ordine contează):
4
1+3, 3+1, 2+2
1+1+2, 1+2+1, 2+1+1
1+1+1+1
Partiții ale lui 4 (ordine nu contează):
4, 3+1, 2+2, 2+1+1, 1+1+1+1
Compoziții¶
def compozitii(n):
st = []
def back(rest):
if rest == 0:
print(" + ".join(map(str, st)))
return
for v in range(1, rest + 1):
st.append(v)
back(rest - v)
st.pop()
back(n)
compozitii(4)
Partiții (elementele ordonate crescător)¶
def partitii(n):
st = []
def back(rest, minim):
if rest == 0:
print(" + ".join(map(str, st)))
return
for v in range(minim, rest + 1):
st.append(v)
back(rest - v, v) # următorul ≥ v
st.pop()
back(n, 1)
partitii(4)
1.10. Problema plății unei sume cu monede¶
Problemă: Ai monede de valori date. În câte moduri poți plăti o sumă S?
def plata_suma(S, monede):
st = []
nr = [0]
def back(rest, idx):
if rest == 0:
nr[0] += 1
print(st)
return
for i in range(idx, len(monede)):
if monede[i] <= rest:
st.append(monede[i])
back(rest - monede[i], i) # i pentru a permite repetări
st.pop()
back(S, 0)
print(f"Total: {nr[0]} moduri")
plata_suma(5, [1, 2, 5])
# [1, 1, 1, 1, 1]
# [1, 1, 1, 2]
# [1, 2, 2]
# [5]
# Total: 4 moduri
1.11. Problema colorării hărții¶
Problemă: Colorăm o hartă cu m culori astfel încât țările vecine să aibă culori diferite.
def coloreaza(n, m, vecini):
# n = număr țări, m = număr culori
# vecini[i] = listă de țări vecine cu i
culori = [0] * n
nr = [0]
def valid(tara, cul):
for v in vecini[tara]:
if v < tara and culori[v] == cul: # doar cele plasate deja
return False
return True
def back(tara):
if tara == n:
nr[0] += 1
return
for c in range(1, m + 1):
culori[tara] = c
if valid(tara, c):
back(tara + 1)
back(0)
return nr[0]
# 4 țări: 0-1, 0-2, 1-2, 1-3, 2-3
vecini = [[1, 2], [0, 2, 3], [0, 1, 3], [1, 2]]
print(coloreaza(4, 3, vecini)) # numărul de colorări cu 3 culori
1.12. Optimizare prin pruning (tăiere)¶
Pruning = eliminarea ramurilor care sigur nu duc la soluție, pentru a accelera algoritmul.
Exemplu — regine: dacă o poziție nu e validă, nu continuăm să plasăm regine pe următoarele linii. Fără pruning, am genera toate nⁿ combinații. Cu pruning, N-reginele pentru n=20 sunt calculabile rapid.
✏️ Exerciții propuse — Capitolul 1¶
- Generează toate submulțimile mulțimii
{1, 2, ..., n}. - Generează toate parolele de lungime
kcu litere dintr-un set dat, astfel încât să nu aibă două caractere adiacente identice. - Anagrame: toate permutările unui cuvânt (atenție la litere duplicate!).
- Problema cavalerului: plasează un cal de șah pe o tablă
n × nși găsește un traseu care acoperă toate căsuțele. - Sudoku: rezolvă un puzzle Sudoku 9×9 prin backtracking.
- Generează toate numerele de 5 cifre cu proprietatea că suma cifrelor este pară și cifra sutelor e mai mare ca a zecilor.
Capitolul 2: Modelul conceptual rețea — grafuri¶
2.1. Ce este un graf?¶
Un graf este o structură formată din:
- Noduri (vârfuri) — entitățile pe care le modelăm.
- Muchii / arce — legăturile dintre noduri.
Exemple din realitate:
- Hartă rutieră — orașe (noduri), șosele (muchii).
- Rețea socială — persoane (noduri), prietenii (muchii).
- Internet — calculatoare (noduri), conexiuni (arce).
- Dependențe software — biblioteci (noduri), dependințe (arce).
2.2. Grafuri neorientate vs. orientate¶
Graf neorientat: muchiile nu au direcție. Dacă a e legat de b, atunci și b e legat de a. (Ex: prietenii Facebook.)
Graf orientat: arcele au direcție. a → b nu implică b → a. (Ex: urmăritori Instagram; străzi cu sens unic.)
Notații:
- Graf neorientat: G = (V, E), E = mulțimea muchiilor, fiecare muchie
{u, v}. - Graf orientat: G = (V, E), E = mulțimea arcelor, fiecare arc
(u, v).
2.3. Terminologie esențială¶
Fie G un graf.
- n = numărul de noduri (|V|).
- m = numărul de muchii/arce (|E|).
- Adiacență: două noduri
u,vsunt adiacente dacă există muchia{u,v}sau arcul(u,v). - Incidență: un nod și o muchie sunt incidente dacă nodul este extremitatea muchiei.
Grad (graf neorientat)¶
Gradul unui nod v, notat deg(v), este numărul de muchii incidente cu v.
Teorema sumelor gradelor: Σ deg(v) = 2·m (fiecare muchie contribuie cu 2 la suma gradelor).
Consecință: numărul de noduri de grad impar este par.
Grad interior și exterior (graf orientat)¶
- Grad interior (in-degree)
deg⁻(v)= numărul de arce care intră înv. - Grad exterior (out-degree)
deg⁺(v)= numărul de arce care pleacă dinv.
Teoremă: Σ deg⁻(v) = Σ deg⁺(v) = m.
2.4. Lanțuri, drumuri, cicluri¶
În graf neorientat:
- Lanț = succesiune de muchii consecutive (fiecare are un nod comun cu următoarea).
- Lanț elementar = fără nod repetat.
- Lanț simplu = fără muchie repetată.
- Lanț compus = nu este simplu (are cel puțin o muchie repetată).
- Ciclu = lanț în care nodul de start coincide cu cel de final.
- Ciclu elementar = ciclu fără nod repetat (excepție: primul = ultimul).
În graf orientat: echivalent, dar cu „drum” în loc de lanț și „circuit” în loc de ciclu.
2.5. Tipuri speciale de grafuri¶
| Tip | Proprietate |
|---|---|
| Complet (K_n) | Orice două noduri sunt adiacente. Are n(n-1)/2 muchii. |
| Conex (neorientat) | Există lanț între orice două noduri. |
| Tare conex (orientat) | Există drum între orice două noduri, în ambele sensuri. |
| Ponderat | Muchiile au asociate costuri / ponderi. |
| Hamiltonian | Conține un lanț/ciclu ce trece prin toate nodurile exact o dată. |
| Eulerian | Conține un lanț/ciclu ce trece prin toate muchiile exact o dată. |
Teorema lui Euler: Un graf conex este eulerian ⇔ toate nodurile au grad par. Are un lanț eulerian ⇔ exact 0 sau 2 noduri de grad impar.
2.6. Subgrafuri și grafuri parțiale¶
- Subgraf — se obține din
Gprin eliminarea unor noduri (și implicit a muchiilor incidente). - Graf parțial — se obține din
Gprin eliminarea unor muchii, păstrând toate nodurile.
2.7. Reprezentarea grafurilor¶
a) Matrice de adiacență¶
O matrice a[n][n] unde:
a[i][j] = 1dacă există muchie/arc întreișij, altfel0.- Pentru grafuri ponderate,
a[i][j] = cost, iar pentru absență se folosește0,-1sauINF.
Proprietăți:
- Neorientat → simetrică față de diagonală.
- Pe diagonală de regulă 0 (nu avem bucle).
Exemplu: graf neorientat cu 4 noduri, muchii {0-1, 0-2, 1-3}:
0 1 2 3
┌─────────
0 │ 0 1 1 0
1 │ 1 0 0 1
2 │ 1 0 0 0
3 │ 0 1 0 0
Avantaje: verificare adiacență O(1); cod simplu.
Dezavantaje: O(n²) memorie chiar pentru grafuri rare.
Python¶
n = 4
a = [[0] * n for _ in range(n)]
muchii = [(0,1), (0,2), (1,3)]
for u, v in muchii:
a[u][v] = 1
a[v][u] = 1 # pentru neorientat
C++¶
int n = 4;
int a[100][100] = {0};
int muchii[][2] = {{0,1}, {0,2}, {1,3}};
for (auto& m : muchii) {
a[m[0]][m[1]] = 1;
a[m[1]][m[0]] = 1;
}
b) Liste de adiacență¶
Pentru fiecare nod, păstrăm lista vecinilor.
Exemplu (același graf):
0: [1, 2]
1: [0, 3]
2: [0]
3: [1]
Avantaje: O(n + m) memorie — excelent pentru grafuri rare.
Dezavantaje: verificare adiacență O(deg(v)).
Python¶
adj = [[] for _ in range(n)]
muchii = [(0,1), (0,2), (1,3)]
for u, v in muchii:
adj[u].append(v)
adj[v].append(u) # neorientat
# Pentru orientat — doar un singur append
C++¶
vector<vector<int>> adj(n);
for (auto& m : muchii) {
adj[m[0]].push_back(m[1]);
adj[m[1]].push_back(m[0]);
}
c) Liste de succesori / predecesori (graf orientat)¶
- Lista de succesori (OUT): pentru fiecare nod, nodurile la care se poate ajunge direct.
- Lista de predecesori (IN): pentru fiecare nod, nodurile de la care se ajunge direct la el.
d) Lista de muchii / arce¶
Toate muchiile într-o singură listă de perechi (u, v) sau triplete (u, v, cost).
muchii = [(0, 1), (0, 2), (1, 3)]
# sau ponderate:
muchii_p = [(0, 1, 5), (0, 2, 3), (1, 3, 7)]
Util pentru algoritmi precum Kruskal sau sortarea muchiilor după cost.
2.8. Compararea reprezentărilor¶
| Reprezentare | Memorie | Verif. adiacență | Parcurgere vecini | Util când |
|---|---|---|---|---|
| Matrice adiacență | O(n²) | O(1) | O(n) | Graf dens, n mic |
| Liste adiacență | O(n+m) | O(deg) | O(deg) | Graf rar, n mare |
| Listă muchii | O(m) | O(m) | O(m) | Algoritmi ca Kruskal |
2.9. Probleme clasice — concepte¶
Problema 1 — grade¶
def grade(a, n):
for i in range(n):
grad = sum(a[i])
print(f"deg({i}) = {grad}")
Problema 2 — verificare dacă un șir este lanț¶
def este_lant(a, sir):
# a = matrice adiacență, sir = lista de noduri
for i in range(len(sir) - 1):
if a[sir[i]][sir[i+1]] == 0:
return False
return True
# exemplu
a = [[0,1,1,0],[1,0,1,0],[1,1,0,1],[0,0,1,0]]
print(este_lant(a, [0, 2, 3])) # True
print(este_lant(a, [0, 3])) # False
Problema 3 — număr de muchii¶
def numar_muchii(a, n, orientat=False):
total = sum(sum(row) for row in a)
return total if orientat else total // 2
Problema 4 — noduri izolate (grad 0)¶
def noduri_izolate(a, n):
return [i for i in range(n) if sum(a[i]) == 0]
✏️ Exerciții propuse — Capitolul 2¶
- Transformă o matrice de adiacență în liste de adiacență.
- Determină dacă un graf neorientat are un număr par de noduri de grad impar.
- Verifică dacă un graf e complet.
- Afișează toți vecinii de gradul 2 (vecini ai vecinilor, care nu sunt vecini direcți).
- Pentru un graf orientat dat prin matrice, afișează gradele interioare și exterioare.
- Transformă un graf neorientat într-unul orientat (fiecare muchie
{u,v}→ arcele(u,v)și(v,u)).
Capitolul 3: Algoritmi pentru prelucrarea grafurilor¶
3.1. Parcurgerea în lățime (BFS — Breadth First Search)¶
Idee: Pornim dintr-un nod sursă și explorăm nodurile în ordinea distanței (în număr de muchii) față de sursă.
Mecanism: folosim o coadă (FIFO).
Algoritm:
BFS(start):
marchează start ca vizitat
adaugă start în coadă
cât timp coada nu e vidă:
u = scoate din față
afișează u
pentru fiecare vecin v al lui u:
dacă v nu e vizitat:
marchează v ca vizitat
adaugă v în coadă
Python¶
from collections import deque
def bfs(adj, start, n):
vizitat = [False] * n
ordine = []
coada = deque([start])
vizitat[start] = True
while coada:
u = coada.popleft()
ordine.append(u)
for v in adj[u]:
if not vizitat[v]:
vizitat[v] = True
coada.append(v)
return ordine
# Exemplu
adj = [[1, 2], [0, 3, 4], [0], [1], [1]]
print(bfs(adj, 0, 5)) # [0, 1, 2, 3, 4]
C++¶
#include <queue>
#include <vector>
using namespace std;
vector<int> bfs(vector<vector<int>>& adj, int start, int n) {
vector<bool> viz(n, false);
vector<int> ordine;
queue<int> q;
q.push(start);
viz[start] = true;
while (!q.empty()) {
int u = q.front(); q.pop();
ordine.push_back(u);
for (int v : adj[u]) {
if (!viz[v]) {
viz[v] = true;
q.push(v);
}
}
}
return ordine;
}
Complexitate: O(n + m) cu liste; O(n²) cu matrice.
3.2. BFS — distanțe minime¶
BFS calculează distanța minimă (în număr de muchii) de la sursă la orice nod.
def bfs_distante(adj, start, n):
dist = [-1] * n
dist[start] = 0
coada = deque([start])
while coada:
u = coada.popleft()
for v in adj[u]:
if dist[v] == -1:
dist[v] = dist[u] + 1
coada.append(v)
return dist
Aplicație: distanța minimă între două persoane într-o rețea socială („șase grade de separare”).
3.3. Parcurgerea în adâncime (DFS — Depth First Search)¶
Idee: explorăm cât mai adânc posibil, revenind doar când nu mai avem unde merge.
Mecanism: folosim o stivă (LIFO), implicit prin recursivitate.
Python recursiv¶
def dfs(adj, start, n):
vizitat = [False] * n
ordine = []
def explorare(u):
vizitat[u] = True
ordine.append(u)
for v in adj[u]:
if not vizitat[v]:
explorare(v)
explorare(start)
return ordine
adj = [[1, 2], [0, 3, 4], [0], [1], [1]]
print(dfs(adj, 0, 5)) # [0, 1, 3, 4, 2]
Python iterativ (cu stivă explicită)¶
def dfs_iter(adj, start, n):
vizitat = [False] * n
ordine = []
stiva = [start]
while stiva:
u = stiva.pop()
if vizitat[u]:
continue
vizitat[u] = True
ordine.append(u)
for v in reversed(adj[u]): # reversed pentru ordine similară cu recursia
if not vizitat[v]:
stiva.append(v)
return ordine
C++ recursiv¶
void dfs(int u, vector<vector<int>>& adj, vector<bool>& viz) {
viz[u] = true;
cout << u << " ";
for (int v : adj[u])
if (!viz[v])
dfs(v, adj, viz);
}
3.4. Aplicații BFS/DFS¶
a) Verificare conexitate¶
Un graf neorientat este conex ⇔ o singură parcurgere (BFS sau DFS) vizitează toate nodurile.
def este_conex(adj, n):
if n == 0:
return True
vizitat = [False] * n
vizitat[0] = True
coada = deque([0])
nr = 1
while coada:
u = coada.popleft()
for v in adj[u]:
if not vizitat[v]:
vizitat[v] = True
nr += 1
coada.append(v)
return nr == n
b) Componente conexe¶
def componente_conexe(adj, n):
comp = [-1] * n
k = 0
for s in range(n):
if comp[s] == -1:
k += 1
coada = deque([s])
comp[s] = k
while coada:
u = coada.popleft()
for v in adj[u]:
if comp[v] == -1:
comp[v] = k
coada.append(v)
return comp, k
# comp[i] = numărul componentei din care face parte nodul i
3.5. Matricea drumurilor — algoritmul Roy-Warshall (Floyd)¶
Problema: pentru orice pereche (i, j), vrem să știm dacă există drum de la i la j.
Idee (Warshall): la pasul k, setăm d[i][j] = 1 dacă d[i][j] = 1 sau (d[i][k] = 1 și d[k][j] = 1).
def matrice_drumuri(a, n):
d = [row[:] for row in a] # copie matricea adiacență
for k in range(n):
for i in range(n):
for j in range(n):
if d[i][k] and d[k][j]:
d[i][j] = 1
return d
Complexitate: O(n³).
Aplicație: verificare tare conexitate.
def tare_conex(a, n):
d = matrice_drumuri(a, n)
for i in range(n):
for j in range(n):
if i != j and d[i][j] == 0:
return False
return True
3.6. Algoritmul lui Dijkstra — drumuri de cost minim¶
Problema: într-un graf ponderat (cu costuri nenegative), să găsim cel mai scurt drum de la un nod sursă la toate celelalte.
Idee: la fiecare pas, alegem nodul nevizitat cu distanța minimă și îi actualizăm vecinii.
Algoritm (cu priority queue):
dist[start] = 0, rest = infinit
PQ ← {(0, start)}
cât timp PQ nu e vidă:
(d, u) = scoate minimul din PQ
dacă d > dist[u]: continuă (versiune veche)
pentru fiecare vecin v cu cost c:
dacă dist[u] + c < dist[v]:
dist[v] = dist[u] + c
adaugă (dist[v], v) în PQ
Python¶
import heapq
INF = float('inf')
def dijkstra(adj, start, n):
# adj[u] = lista de perechi (v, cost)
dist = [INF] * n
dist[start] = 0
pq = [(0, start)]
while pq:
d, u = heapq.heappop(pq)
if d > dist[u]:
continue
for v, cost in adj[u]:
nd = d + cost
if nd < dist[v]:
dist[v] = nd
heapq.heappush(pq, (nd, v))
return dist
# Exemplu
adj = [
[(1, 4), (2, 1)], # 0 → 1 (cost 4), 0 → 2 (cost 1)
[(3, 1)], # 1 → 3 (cost 1)
[(1, 2), (3, 5)], # 2 → 1 (cost 2), 2 → 3 (cost 5)
[]
]
print(dijkstra(adj, 0, 4)) # [0, 3, 1, 4]
C++¶
#include <vector>
#include <queue>
using namespace std;
const int INF = 1e9;
vector<int> dijkstra(vector<vector<pair<int,int>>>& adj, int start, int n) {
vector<int> dist(n, INF);
dist[start] = 0;
priority_queue<pair<int,int>, vector<pair<int,int>>, greater<>> pq;
pq.push({0, start});
while (!pq.empty()) {
auto [d, u] = pq.top(); pq.pop();
if (d > dist[u]) continue;
for (auto [v, cost] : adj[u]) {
if (dist[u] + cost < dist[v]) {
dist[v] = dist[u] + cost;
pq.push({dist[v], v});
}
}
}
return dist;
}
Complexitate: O((n+m) log n) cu priority queue.
3.7. Algoritmul lui Prim — arbore parțial de cost minim (APM)¶
Problema: într-un graf ponderat conex, găsim un subgraf arborescent care conectează toate nodurile, cu suma costurilor minimă.
Aplicație: o rețea de cablare (fibra optică) care conectează toate orașele cu cost minim.
Idee (similar cu Dijkstra): începem dintr-un nod, adăugăm la APM muchia de cost minim care duce la un nod încă nevizitat.
import heapq
def prim(adj, n):
# adj[u] = lista de (v, cost)
in_apm = [False] * n
cost_min = [float('inf')] * n
parinte = [-1] * n
cost_min[0] = 0
pq = [(0, 0)]
total = 0
muchii_apm = []
while pq:
c, u = heapq.heappop(pq)
if in_apm[u]:
continue
in_apm[u] = True
total += c
if parinte[u] != -1:
muchii_apm.append((parinte[u], u, c))
for v, cost in adj[u]:
if not in_apm[v] and cost < cost_min[v]:
cost_min[v] = cost
parinte[v] = u
heapq.heappush(pq, (cost, v))
return total, muchii_apm
Complexitate: O((n+m) log n).
3.8. Ciclu eulerian (Hierholzer)¶
Problema: găsește un ciclu care trece prin fiecare muchie o singură dată.
Condiție (graf neorientat conex): toate nodurile au grad par.
def ciclu_eulerian(adj, n):
# adj[u] = lista de muchii (v, id_muchie)
folosit = [False] * sum(len(l) for l in adj) // 2
stiva = [0]
drum = []
while stiva:
u = stiva[-1]
gasit = False
while adj[u]:
v, id_m = adj[u][-1]
if folosit[id_m]:
adj[u].pop()
else:
folosit[id_m] = True
stiva.append(v)
gasit = True
break
if not gasit:
drum.append(stiva.pop())
return drum[::-1]
✏️ Exerciții propuse — Capitolul 3¶
- Scrie un algoritm care determină dacă există drum de la nodul
ula nodulvfolosind DFS. - Implementează BFS pentru a găsi un drum minim între două noduri.
- Determină ciclurile unui graf (dacă au lungime ≤ k) — cu DFS.
- Numărul de componente conexe ale unui graf dat prin matrice.
- Dijkstra cu afișarea drumului concret (nu doar distanța).
- Algoritmul Roy-Floyd pentru drumuri de cost minim între toate perechile.
- Problema comis-voiajorului (TSP) cu backtracking + eventual Dijkstra între punctele vizitate.
Capitolul 4: Modelul conceptual ierarhic — arbori¶
4.1. Ce este un arbore?¶
Un arbore este un caz special de graf:
- Neorientat, conex, fără cicluri.
Definiții echivalente — un graf cu n noduri este arbore ⇔:
- Este conex și are
n-1muchii. - Este aciclic și are
n-1muchii. - Este conex și eliminarea oricărei muchii îl face neconex.
- Este aciclic și adăugarea oricărei muchii creează un ciclu.
- Între orice două noduri există un lanț unic.
4.2. Arbore cu rădăcină¶
Dacă alegem un nod special — rădăcina — toate celelalte noduri se orientează în raport cu ea.
Terminologie:
- Rădăcină — nodul de pornire.
- Fiu (descendent direct) — nod aflat la un nivel mai jos, legat direct.
- Tată (ascendent direct) — nodul imediat deasupra.
- Descendenți — toți nodurile din subarborele unui nod.
- Ascendenți — toți nodurile de pe drumul spre rădăcină.
- Frați — noduri cu același tată.
- Frunză — nod fără fii.
- Nod intern — nod care nu e frunză.
- Nivel — distanța (în muchii) de la rădăcină.
- Înălțime — nivelul maxim.
Exemplu:
A ← rădăcină (nivel 0)
/ | \
B C D ← nivel 1 (fiii lui A)
/ \ |
E F G ← nivel 2 (frunze: E, F, G)
- E, F, G sunt frunze.
- B, C, D sunt frați.
- A este ascendentul lui toți ceilalți.
- Înălțimea = 2.
4.3. Reprezentarea arborilor cu rădăcină¶
a) Referințe ascendente (vectorul „tata”)¶
tata[i] = indexul tatălui lui i. Rădăcina are tata[r] = 0 sau -1.
Exemplu:
arbore: 1
/ \
2 3
/|
4 5
tata = [_, 0, 1, 1, 2, 2] # index 0 neutilizat
tata[1] = 0 (rădăcină)
tata[2] = 1, tata[3] = 1
tata[4] = 2, tata[5] = 2
Avantaj: foarte compactă, O(n) memorie.
Dezavantaj: nu poți găsi fiii direct — trebuie să parcurgi tot vectorul.
b) Referințe descendente (listă de fii)¶
fii[i] = lista fiilor lui i.
fii = {
1: [2, 3],
2: [4, 5],
3: [],
4: [],
5: []
}
Avantaj: parcurgerea descendenților e O(nr_fii).
4.4. Arbori ordonați¶
Într-un arbore ordonat, ordinea fiilor contează. Pentru fiecare nod, fiii sunt numerotați 1, 2, 3, …
Arbore neordonat: {A → B, C} este același ca {A → C, B}.
Arbore ordonat: (A → B, C) ≠ (A → C, B).
4.5. Arbori binari¶
Un arbore binar este un arbore ordonat în care fiecare nod are cel mult 2 fii — fiul stâng și fiul drept.
Reprezentare cu două vectori:
stg[i] = fiul stâng al lui i (0 sau -1 dacă lipsește)
drp[i] = fiul drept al lui i
Reprezentare OOP (mai naturală):
class Nod:
def __init__(self, val):
self.val = val
self.stg = None
self.drp = None
4.6. Proprietăți ale arborilor binari¶
Pentru un arbore binar cu n noduri și înălțime h:
- Numărul maxim de noduri:
2^(h+1) - 1. - Înălțimea minimă:
⌊log₂ n⌋. - Un arbore binar perfect are toate nivelurile complete.
4.7. Arbori binari de căutare (BST)¶
Un arbore binar de căutare este un arbore binar cu proprietatea:
Pentru orice nod
x:
- Toate valorile din subarborele stâng al lui
xsunt < x.val.- Toate valorile din subarborele drept al lui
xsunt > x.val.
Exemplu:
8
/ \
3 10
/ \ \
1 6 14
/ \ /
4 7 13
Caracteristici:
- Căutare rapidă — O(log n) în medie, O(n) în cel mai rău caz.
- Inorder traversal → elementele în ordine crescătoare.
✏️ Exerciții propuse — Capitolul 4¶
- Pentru un arbore dat prin vectorul
tata, găsește rădăcina. - Calculează înălțimea unui arbore dat prin
tata. - Afișează toate frunzele unui arbore.
- Pentru două noduri dintr-un arbore cu rădăcină, găsește cel mai apropiat strămoș comun (LCA).
- Converteste un arbore de la reprezentarea cu
tatala cea cufii. - Verifică dacă un arbore binar este arbore binar de căutare.
Capitolul 5: Algoritmi pentru prelucrarea arborilor¶
5.1. Parcurgerea unui arbore binar¶
Există 3 metode fundamentale de parcurgere recursivă, care diferă prin ordinea în care se vizitează nodul curent (R), subarborele stâng (S), subarborele drept (D).
| Parcurgere | Ordine | Mnemonic |
|---|---|---|
| Preordine | R → S → D | „Rădăcină întâi” |
| Inordine | S → R → D | „Rădăcină la mijloc” |
| Postordine | S → D → R | „Rădăcină la urmă” |
5.2. Implementarea parcurgerilor¶
class Nod:
def __init__(self, val):
self.val = val
self.stg = None
self.drp = None
def preordine(n):
if n is None: return
print(n.val, end=' ')
preordine(n.stg)
preordine(n.drp)
def inordine(n):
if n is None: return
inordine(n.stg)
print(n.val, end=' ')
inordine(n.drp)
def postordine(n):
if n is None: return
postordine(n.stg)
postordine(n.drp)
print(n.val, end=' ')
C++¶
struct Nod {
int val;
Nod *stg, *drp;
};
void preordine(Nod* n) {
if (!n) return;
cout << n->val << " ";
preordine(n->stg);
preordine(n->drp);
}
// similar pentru inordine și postordine
5.3. Exemplu — cele 3 parcurgeri¶
1
/ \
2 3
/ \ \
4 5 6
- Preordine (R-S-D): 1 2 4 5 3 6
- Inordine (S-R-D): 4 2 5 1 3 6
- Postordine (S-D-R): 4 5 2 6 3 1
Aplicații:
- Preordine — afișare cu indentare, copierea arborelui, diseminare mesaje.
- Inordine — în BST dă elementele sortate; evaluare expresii aritmetice.
- Postordine — ștergere arbore (de jos în sus); evaluare expresii în notația poloneză inversă.
5.4. Operații pe arbori binari de căutare (BST)¶
Căutare¶
def cauta(nod, val):
if nod is None:
return False
if nod.val == val:
return True
if val < nod.val:
return cauta(nod.stg, val)
return cauta(nod.drp, val)
Complexitate: O(h), unde h e înălțimea. Pentru BST echilibrat: O(log n).
Inserare¶
def insereaza(nod, val):
if nod is None:
return Nod(val)
if val < nod.val:
nod.stg = insereaza(nod.stg, val)
elif val > nod.val:
nod.drp = insereaza(nod.drp, val)
return nod
Crearea unui BST din secvența [5, 3, 7, 1, 4, 8]:
5
/ \
3 7
/ \ \
1 4 8
Ștergere (mai complexă)¶
def minim(nod):
while nod.stg:
nod = nod.stg
return nod
def sterge(nod, val):
if nod is None:
return None
if val < nod.val:
nod.stg = sterge(nod.stg, val)
elif val > nod.val:
nod.drp = sterge(nod.drp, val)
else:
# Am găsit nodul
if nod.stg is None:
return nod.drp
if nod.drp is None:
return nod.stg
# Două subarbori — înlocuim cu succesorul inorder
succ = minim(nod.drp)
nod.val = succ.val
nod.drp = sterge(nod.drp, succ.val)
return nod
5.5. Reconstruirea unui arbore din parcurgeri¶
Problemă: având parcurgerea în preordine și inordine, reconstruiește arborele.
Observație cheie:
- Primul element din preordine = rădăcina.
- În inordine, tot ce-i înainte de rădăcină = subarborele stâng; tot ce-i după = cel drept.
def reconstruieste(preord, inord):
if not preord:
return None
val_rad = preord[0]
rad = Nod(val_rad)
idx = inord.index(val_rad)
rad.stg = reconstruieste(preord[1:idx+1], inord[:idx])
rad.drp = reconstruieste(preord[idx+1:], inord[idx+1:])
return rad
preord = [1, 2, 4, 5, 3, 6]
inord = [4, 2, 5, 1, 3, 6]
arb = reconstruieste(preord, inord)
postordine(arb) # 4 5 2 6 3 1
5.6. Aplicații practice — arbori¶
Problemă 1 — numărul de frunze¶
def nr_frunze(nod):
if nod is None:
return 0
if nod.stg is None and nod.drp is None:
return 1
return nr_frunze(nod.stg) + nr_frunze(nod.drp)
Problemă 2 — înălțimea¶
def inaltime(nod):
if nod is None:
return -1
return 1 + max(inaltime(nod.stg), inaltime(nod.drp))
Problemă 3 — numărul de noduri pe un nivel¶
def noduri_pe_nivel(nod, k):
if nod is None:
return 0
if k == 0:
return 1
return noduri_pe_nivel(nod.stg, k-1) + noduri_pe_nivel(nod.drp, k-1)
Problemă 4 — verificare BST¶
def este_bst(nod, min_val=float('-inf'), max_val=float('inf')):
if nod is None:
return True
if nod.val <= min_val or nod.val >= max_val:
return False
return (este_bst(nod.stg, min_val, nod.val) and
este_bst(nod.drp, nod.val, max_val))
5.7. Parcurgerea pe niveluri (BFS pe arbore)¶
from collections import deque
def parcurgere_niveluri(rad):
if rad is None: return
coada = deque([rad])
while coada:
nod = coada.popleft()
print(nod.val, end=' ')
if nod.stg: coada.append(nod.stg)
if nod.drp: coada.append(nod.drp)
✏️ Exerciții propuse — Capitolul 5¶
- Numără nodurile interne (cu cel puțin un fiu).
- Suma valorilor dintr-un arbore binar.
- Verifică dacă doi arbori sunt identici.
- Oglindește un arbore binar (subarborele stâng devine drept și invers).
- Găsește cel mai apropiat strămoș comun pentru două valori dintr-un BST.
- Afișează arborele pe niveluri (fiecare nivel pe o linie).
- Verifică dacă un arbore este perfect echilibrat (diferența între înălțimile subarborilor ≤ 1).
Capitolul 6: Modelul conceptual obiectual¶
6.1. De la proceduri la obiecte¶
Până acum am gândit programele ca secvențe de proceduri (funcții) care operează pe date. Acesta este stilul procedural:
# Stil procedural
def creeaza_elev(nume, varsta):
return {"nume": nume, "varsta": varsta, "note": []}
def adauga_nota(e, n):
e["note"].append(n)
def media(e):
return sum(e["note"]) / len(e["note"])
Stilul obiectual unește datele cu operațiile asupra lor într-o entitate coerentă — un obiect:
# Stil obiectual
class Elev:
def __init__(self, nume, varsta):
self.nume = nume
self.varsta = varsta
self.note = []
def adauga_nota(self, n):
self.note.append(n)
def media(self):
return sum(self.note) / len(self.note)
alex = Elev("Alex", 17)
alex.adauga_nota(9)
alex.adauga_nota(10)
print(alex.media())
6.2. Concepte fundamentale POO¶
1. Clasă — un „șablon” sau „tipar” care definește ce date și ce operații va avea un obiect.
2. Obiect (instanță) — o entitate concretă creată după tiparul unei clase.
3. Atribute — datele asociate unui obiect (starea lui).
4. Metode — operațiile pe care le poate face un obiect.
5. Încapsulare — ascunderea detaliilor interne ale unui obiect; accesul se face doar prin metode publice.
6. Moștenire — o clasă nouă poate extinde o clasă existentă, preluând datele și metodele ei.
7. Polimorfism — aceeași metodă se poate comporta diferit în clase diferite.
6.3. Analogie: clasa ca plan arhitectural¶
- Clasa Casă = planul după care se construiesc casele.
- Atribute: număr_camere, suprafață, culoare.
- Metode: deschide_ușa(), aprinde_lumina().
- Obiect = o casă concretă construită după plan.
- casa_mea: 3 camere, 80 m², galbenă.
- casa_prietenului: 4 camere, 100 m², albastră.
6.4. Încapsularea¶
Principiul: datele sensibile ale unui obiect trebuie să fie protejate de modificarea directă din exterior.
Niveluri de acces:
- Public — accesibil oriunde.
- Privat — accesibil doar în interiorul clasei.
- Protected — accesibil în clasă și în clasele derivate.
Exemplu: un ContBancar nu trebuie să permită modificarea directă a soldului:
class ContBancar:
def __init__(self, titular, sold_initial=0):
self.titular = titular
self._sold = sold_initial # convenție: _ = protected, __ = private
def depune(self, suma):
if suma > 0:
self._sold += suma
def retrage(self, suma):
if 0 < suma <= self._sold:
self._sold -= suma
return True
return False
def sold(self):
return self._sold
6.5. Moștenirea¶
Problema: ai mai multe clase cu funcționalitate comună.
Exemplu: Autoturism, Camion, Autocar — toate sunt Vehicule.
Vehicul
/ | \
Autoturism Camion Autocar
- Clasa de bază (părinte): Vehicul.
- Clasa derivată (fiu): Autoturism (și celelalte).
Beneficii:
- Reutilizare cod.
- Ierarhie logică.
- Extensibilitate.
6.6. Polimorfism¶
Aceeași metodă (cu același nume) se comportă diferit în clase diferite.
Exemplu: metoda sunet() — Câine.sunet() = “Ham!”, Pisică.sunet() = “Miau!”.
✏️ Exerciții propuse — Capitolul 6¶
- Dă 5 exemple din viața reală în care modelul obiectual este natural.
- Pentru o bibliotecă, identifică clasele relevante (Carte, Autor, Cititor, Împrumut) și atributele lor.
- Desenează o ierarhie de clase pentru „Persoană” (elev, profesor, părinte) și descrie atributele/metodele comune și specifice.
Capitolul 7: Programare orientată pe obiecte în Python¶
7.1. Sintaxa de bază¶
class NumeClasa:
def __init__(self, parametri):
# constructor — inițializează atributele
self.atribut1 = parametri
def metoda(self, argumente):
# self = referința la obiectul curent (ca "this" din alte limbaje)
# logică...
pass
7.2. Clasă completă — exemplu¶
class Dreptunghi:
def __init__(self, lungime, latime):
self.lungime = lungime
self.latime = latime
def aria(self):
return self.lungime * self.latime
def perimetrul(self):
return 2 * (self.lungime + self.latime)
def afiseaza(self):
print(f"Dreptunghi {self.lungime} × {self.latime}")
# Utilizare
d1 = Dreptunghi(5, 3)
d1.afiseaza() # "Dreptunghi 5 × 3"
print(d1.aria()) # 15
print(d1.perimetrul()) # 16
7.3. Constructor și destructor¶
- Constructor (
__init__) — se apelează automat la crearea obiectului. - Destructor (
__del__) — se apelează automat când obiectul este distrus (rar folosit direct în Python, deoarece există un garbage collector).
class Fisier:
def __init__(self, nume):
self.nume = nume
self.f = open(nume, 'w')
print(f"S-a deschis {nume}")
def __del__(self):
self.f.close()
print(f"S-a închis {self.nume}")
# f = Fisier("test.txt")
# del f # apelează __del__
7.4. Atribute — de clasă vs. de instanță¶
class Elev:
scoala = "CN Tudor Vianu" # atribut de clasă — comun tuturor elevilor
def __init__(self, nume):
self.nume = nume # atribut de instanță — specific fiecăruia
a = Elev("Ana")
b = Elev("Bogdan")
print(a.scoala, b.scoala) # ambele: "CN Tudor Vianu"
Elev.scoala = "CN Mihai Viteazul"
print(a.scoala, b.scoala) # ambele schimbate!
7.5. Niveluri de acces în Python¶
Python nu are private/public stricte, ci convenții:
| Convenție | Sens |
|---|---|
atribut |
Public |
_atribut |
„Protected” (convenție — nu umblă cineva din afară) |
__atribut |
Privat (Python redenumește, greu de accesat) |
class Cont:
def __init__(self, sold):
self.nume = "Public" # public
self._sold = sold # protected
self.__parola = "secret" # privat
c = Cont(1000)
print(c.nume) # OK
print(c._sold) # OK dar "nu ar trebui"
# print(c.__parola) # EROARE — tehnic accesibil prin c._Cont__parola, dar nu se face
7.6. Metode speciale („dunder methods”)¶
Python permite redefinirea comportamentului operatorilor:
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
return f"({self.x}, {self.y})"
def __add__(self, altul):
return Vector(self.x + altul.x, self.y + altul.y)
def __eq__(self, altul):
return self.x == altul.x and self.y == altul.y
def __len__(self):
return int((self.x**2 + self.y**2)**0.5)
v1 = Vector(1, 2)
v2 = Vector(3, 4)
print(v1) # (1, 2) — folosește __str__
v3 = v1 + v2 # folosește __add__
print(v3) # (4, 6)
print(v1 == Vector(1, 2)) # True — __eq__
Alte metode speciale utile: __repr__, __lt__, __mul__, __getitem__, __setitem__, __iter__.
7.7. Moștenirea¶
class Vehicul:
def __init__(self, marca, viteza_max):
self.marca = marca
self.viteza_max = viteza_max
def info(self):
return f"{self.marca}, viteză max: {self.viteza_max}"
class Autoturism(Vehicul):
def __init__(self, marca, viteza_max, nr_locuri):
super().__init__(marca, viteza_max) # apel constructor părinte
self.nr_locuri = nr_locuri
def info(self):
# Extindem metoda părintelui
return super().info() + f", locuri: {self.nr_locuri}"
class Camion(Vehicul):
def __init__(self, marca, viteza_max, tonaj):
super().__init__(marca, viteza_max)
self.tonaj = tonaj
def info(self):
return super().info() + f", tonaj: {self.tonaj}t"
# Utilizare
a = Autoturism("Dacia", 180, 5)
c = Camion("Volvo", 130, 20)
print(a.info()) # "Dacia, viteză max: 180, locuri: 5"
print(c.info()) # "Volvo, viteză max: 130, tonaj: 20t"
# Polimorfism — aceeași interfață
for v in [a, c]:
print(v.info()) # fiecare folosește propria versiune
7.8. Verificare tip¶
print(isinstance(a, Autoturism)) # True
print(isinstance(a, Vehicul)) # True — moștenire!
print(isinstance(a, Camion)) # False
print(issubclass(Autoturism, Vehicul)) # True
7.9. Exemplu complet — clasa Biblioteca¶
class Carte:
def __init__(self, titlu, autor, isbn):
self.titlu = titlu
self.autor = autor
self.isbn = isbn
self.imprumutata = False
def __str__(self):
stare = "împrumutată" if self.imprumutata else "disponibilă"
return f"[{self.isbn}] {self.titlu} - {self.autor} ({stare})"
class Cititor:
def __init__(self, nume, numar_fisa):
self.nume = nume
self.numar_fisa = numar_fisa
self.carti_imprumutate = []
class Biblioteca:
def __init__(self, nume):
self.nume = nume
self.carti = {}
self.cititori = {}
def adauga_carte(self, carte):
self.carti[carte.isbn] = carte
def inregistreaza_cititor(self, cititor):
self.cititori[cititor.numar_fisa] = cititor
def imprumuta(self, isbn, fisa):
if isbn not in self.carti:
return "Carte inexistentă"
if fisa not in self.cititori:
return "Cititor neînregistrat"
carte = self.carti[isbn]
cititor = self.cititori[fisa]
if carte.imprumutata:
return "Carte deja împrumutată"
carte.imprumutata = True
cititor.carti_imprumutate.append(carte)
return "OK"
def returneaza(self, isbn, fisa):
carte = self.carti.get(isbn)
cititor = self.cititori.get(fisa)
if carte and cititor and carte in cititor.carti_imprumutate:
carte.imprumutata = False
cititor.carti_imprumutate.remove(carte)
return "OK"
return "Eroare"
def afiseaza_disponibile(self):
for c in self.carti.values():
if not c.imprumutata:
print(c)
# Utilizare
b = Biblioteca("Biblioteca Școlii")
b.adauga_carte(Carte("Ion", "Liviu Rebreanu", "001"))
b.adauga_carte(Carte("Moromeții", "Marin Preda", "002"))
b.inregistreaza_cititor(Cititor("Alex", 1))
b.imprumuta("001", 1)
b.afiseaza_disponibile() # afișează doar "Moromeții"
✏️ Exerciții propuse — Capitolul 7¶
- Clasă
Fractiecu operații+,-,*,/și simplificare. - Clasă
Complexpentru numere complexe cu operații de bază. - Ierarhie
Animal→Câine,Pisică,Pasărecu metodesunet()șise_misca(). - Sistem de gestiune școlară:
Persoană→Elev,Profesor. AdaugăClasacare are listă de elevi. - Clasă
Stivaimplementată folosind POO (push, pop, top, is_empty). - Clasă
ArboreBinarDeCautarecu metodeinsereaza,cauta,sterge,inordine.
Capitolul 8: Programare orientată pe obiecte în C++¶
8.1. Sintaxa claselor în C++¶
class NumeClasa {
private:
// date private
int atribut1;
public:
// constructor
NumeClasa(int val) : atribut1(val) {}
// destructor
~NumeClasa() {}
// metode
void metoda() {
// cod
}
// getter
int getAtribut() const { return atribut1; }
};
8.2. Exemplu complet — Dreptunghi¶
#include <iostream>
using namespace std;
class Dreptunghi {
private:
double lungime, latime;
public:
// Constructor
Dreptunghi(double L, double l) : lungime(L), latime(l) {}
// Constructor implicit
Dreptunghi() : lungime(1), latime(1) {}
// Destructor
~Dreptunghi() {
cout << "Obiect distrus" << endl;
}
double aria() const { return lungime * latime; }
double perimetrul() const { return 2 * (lungime + latime); }
void afiseaza() const {
cout << "Dreptunghi " << lungime << " x " << latime << endl;
}
// Setter
void setLungime(double L) {
if (L > 0) lungime = L;
}
};
int main() {
Dreptunghi d(5, 3);
d.afiseaza();
cout << "Arie: " << d.aria() << endl;
return 0;
}
8.3. Niveluri de acces în C++¶
| Modificator | Semnificație |
|---|---|
public |
Accesibil oriunde |
private |
Doar în interiorul clasei (default pentru class) |
protected |
În clasă și clase derivate |
class A {
public:
int x; // public
private:
int y; // privat (default)
protected:
int z; // protected
};
8.4. Moștenirea¶
class Vehicul {
protected:
string marca;
int vitezaMax;
public:
Vehicul(string m, int v) : marca(m), vitezaMax(v) {}
virtual void info() const {
cout << marca << ", vmax: " << vitezaMax;
}
virtual ~Vehicul() {} // destructor virtual — important!
};
class Autoturism : public Vehicul {
private:
int nrLocuri;
public:
Autoturism(string m, int v, int n) : Vehicul(m, v), nrLocuri(n) {}
void info() const override {
Vehicul::info();
cout << ", locuri: " << nrLocuri << endl;
}
};
int main() {
Autoturism a("Dacia", 180, 5);
a.info(); // "Dacia, vmax: 180, locuri: 5"
// Polimorfism prin pointer
Vehicul* v = new Autoturism("BMW", 250, 4);
v->info(); // apelează versiunea din Autoturism (virtual)
delete v;
return 0;
}
Tipuri de moștenire:
public— relație „este un” (cel mai frecvent).private,protected— mai rare, folosite pentru detalii de implementare.
8.5. Funcții virtuale și polimorfism¶
virtualîn clasa de bază → metoda poate fi redefinită în clasele derivate.overrideîn clasa derivată → indică clar că redefinim o metodă virtuală.- Funcție virtuală pură (
virtual ... = 0) → clasa devine abstractă (nu se pot crea instanțe directe).
class Forma {
public:
virtual double aria() const = 0; // pur virtuală
virtual ~Forma() {}
};
class Cerc : public Forma {
double raza;
public:
Cerc(double r) : raza(r) {}
double aria() const override {
return 3.14159 * raza * raza;
}
};
class Patrat : public Forma {
double latura;
public:
Patrat(double l) : latura(l) {}
double aria() const override {
return latura * latura;
}
};
int main() {
Forma* forme[] = {new Cerc(3), new Patrat(4)};
for (auto f : forme) {
cout << f->aria() << endl;
delete f;
}
}
8.6. Operatorii — supraîncărcare¶
class Vector {
int x, y;
public:
Vector(int a, int b) : x(a), y(b) {}
Vector operator+(const Vector& v) const {
return Vector(x + v.x, y + v.y);
}
bool operator==(const Vector& v) const {
return x == v.x && y == v.y;
}
friend ostream& operator<<(ostream& os, const Vector& v) {
os << "(" << v.x << ", " << v.y << ")";
return os;
}
};
8.7. Python vs. C++ — comparație POO¶
| Aspect | Python | C++ |
|---|---|---|
| Declarare privat | __atribut (convenție) |
private: |
| Constructor | __init__ |
Nume clasă |
| Destructor | __del__ (rar) |
~Nume() |
| Moștenire | class B(A): |
class B : public A |
| Apel părinte | super().metoda() |
A::metoda() |
| Polimorfism | Implicit | virtual explicit |
| Clasă abstractă | abc.ABC, @abstractmethod |
virtual ... = 0 |
| Memory management | Automat (GC) | Manual (new/delete) |
✏️ Exerciții propuse — Capitolul 8¶
- Scrie în C++ o clasă
Fractiecu operatorii+,-,*,/,==,<<,>>. - Creează ierarhia
Angajat→Inginer,Profesor, cu metoda virtualăsalariu(). - Implementează clasa
Stackfolosind template-uri C++ pentru a putea stoca orice tip. - Clasă
Matricecu operatorii+,*(matriceal), afișare.
Capitolul 9: Paradigme de bază în programare¶
9.1. Ce este o paradigmă?¶
O paradigmă de programare este un mod fundamental de a gândi programele — perspectivă asupra ce e un program, cum se scrie, cum se execută.
Paradigmele sunt abstracte; limbajele le implementează (unele limbaje suportă mai multe paradigme — ex: Python).
9.2. Paradigma imperativă¶
Principiu: programul este o secvență de instrucțiuni care modifică starea (variabilele).
„Cum” se face ceva, pas cu pas.
Limbaje: C, Pascal, Fortran, Assembly.
// C imperativ
int suma = 0;
for (int i = 1; i <= 10; i++) {
suma += i;
}
printf("%d\n", suma);
9.3. Paradigma procedurală¶
Extinde imperativa cu subprograme (proceduri, funcții). Programul este descompus în module reutilizabile.
Limbaje: C, Pascal, Python (parțial).
def calculeaza_suma(n):
s = 0
for i in range(1, n + 1):
s += i
return s
print(calculeaza_suma(10))
9.4. Paradigma orientată pe obiecte (POO)¶
Principiu: lumea este modelată ca obiecte care au stare (atribute) și comportament (metode). Programul = colecție de obiecte care interacționează.
„Cine” face ceva (obiectul), nu doar „cum”.
Limbaje: Java, C#, C++, Python, Smalltalk.
class Calculator:
def aduna(self, a, b):
return a + b
c = Calculator()
print(c.aduna(3, 5))
9.5. Paradigma funcțională¶
Principiu: programul = aplicații de funcții matematice. Nu există stare modificabilă; funcțiile sunt „pure” (nu au efecte secundare).
„Ce” se calculează, nu „cum”.
Limbaje: Haskell, Lisp, Scala, F#, Elixir. Python și JavaScript au elemente funcționale.
# Funcțional în Python
numere = [1, 2, 3, 4, 5]
# map + filter + reduce
from functools import reduce
patrate_pare = list(map(lambda x: x**2, filter(lambda x: x % 2 == 0, numere)))
print(patrate_pare) # [4, 16]
suma = reduce(lambda a, b: a + b, numere)
print(suma) # 15
9.6. Paradigma declarativă¶
Principiu: specifici ce vrei, nu cum se obține. Sistemul decide metoda.
Exemple: SQL (bază de date), HTML (structură pagini), Prolog (logică).
-- SQL declarativ — spun CE vreau, nu CUM să caute
SELECT nume, medie
FROM elevi
WHERE medie > 9
ORDER BY medie DESC;
9.7. Paradigma logică¶
Un caz special al declarativei — programul = set de fapte și reguli. Sistemul deduce soluția.
Limbaj: Prolog.
% Fapte
parinte(maria, ana).
parinte(maria, ion).
parinte(ana, george).
% Regulă
bunic(X, Z) :- parinte(X, Y), parinte(Y, Z).
% Interogare: cine e bunicul lui george?
% ?- bunic(X, george).
% X = maria.
9.8. Paradigme și limbaje moderne¶
| Limbaj | Paradigme principale |
|---|---|
| C | Imperativă, procedurală |
| C++ | Procedurală, POO, generică |
| Java | POO, generică |
| C# | POO, funcțională |
| Python | Procedurală, POO, funcțională |
| JavaScript | Imperativă, POO, funcțională |
| Haskell | Funcțională pură |
| Prolog | Logică |
| SQL | Declarativă |
9.9. Comparație cod — aceeași problemă, paradigme diferite¶
Problemă: suma pătratelor numerelor pare de la 1 la n.
Procedural (Python):
def suma_patrate_pare(n):
s = 0
for i in range(1, n + 1):
if i % 2 == 0:
s += i * i
return s
POO (Python):
class CalculatorSpecial:
def __init__(self, n):
self.n = n
def suma_patrate_pare(self):
return sum(i*i for i in range(1, self.n+1) if i % 2 == 0)
Funcțional (Python):
from functools import reduce
def suma_patrate_pare(n):
return reduce(
lambda a, b: a + b,
map(lambda x: x*x, filter(lambda x: x % 2 == 0, range(1, n+1))),
0
)
Declarativ (SQL):
SELECT SUM(x * x) FROM numere
WHERE x BETWEEN 1 AND 100 AND x % 2 = 0;
9.10. Avantaje și dezavantaje ale paradigmelor¶
| Paradigmă | Avantaje | Dezavantaje |
|---|---|---|
| Procedurală | Simplă, apropiată de mașină | Greu de organizat proiecte mari |
| POO | Bună modelare a realității, reutilizare | Overhead, complexitate |
| Funcțională | Paralelizabilă, fără efecte secundare | Curbă de învățare, performanță |
| Declarativă | Cod concis, expresiv | Limitată la anumite domenii |
9.11. Paradigme multiple — Python și C++¶
Un avantaj al Python și C++ este că permit combinarea paradigmelor. Poți începe procedural, apoi introduce clase când proiectul crește, și folosi elemente funcționale unde ajută lizibilitatea.
✏️ Exerciții propuse — Capitolul 9¶
- Scrie aceeași problemă („determină numerele prime până la n”) în 3 stiluri: procedural, POO, funcțional.
- Compară lizibilitatea și eficiența.
- Explică de ce un limbaj precum SQL este considerat declarativ.
- Identifică paradigmele principale ale limbajelor: Java, Rust, Haskell, JavaScript.
Anexe¶
Anexa A — Diagrama conceptuală a clasei a XI-a¶
STRATEGII MODELE CONCEPTUALE LIMBAJ
───────── ────────────────── ──────
Backtracking Rețea (graf) POO:
├─ permutări ├─ neorientat ├─ clasă
├─ aranjamente ├─ orientat ├─ obiect
├─ combinări ├─ ponderat ├─ încapsulare
├─ N-regine └─ conex/tare conex ├─ moștenire
└─ colorare └─ polimorfism
Alg. pe grafuri Ierarhic (arbore)
├─ BFS ├─ arbore cu rădăcină Paradigme:
├─ DFS ├─ arbore ordonat ├─ procedurală
├─ Roy-Floyd ├─ arbore binar ├─ obiectuală
├─ Dijkstra └─ BST ├─ funcțională
├─ Prim └─ declarativă
└─ ciclu Eulerian Obiectual
└─ clase și obiecte
Anexa B — Cheat sheet algoritmi grafuri¶
from collections import deque
import heapq
# BFS — parcurgere și distanțe
def bfs(adj, s, n):
dist = [-1] * n
dist[s] = 0
q = deque([s])
while q:
u = q.popleft()
for v in adj[u]:
if dist[v] == -1:
dist[v] = dist[u] + 1
q.append(v)
return dist
# DFS — recursiv
def dfs(adj, u, viz):
viz[u] = True
for v in adj[u]:
if not viz[v]:
dfs(adj, v, viz)
# Dijkstra — drum minim ponderat
def dijkstra(adj, s, n): # adj[u] = [(v, cost), ...]
dist = [float('inf')] * n
dist[s] = 0
pq = [(0, s)]
while pq:
d, u = heapq.heappop(pq)
if d > dist[u]: continue
for v, w in adj[u]:
if d + w < dist[v]:
dist[v] = d + w
heapq.heappush(pq, (dist[v], v))
return dist
# Prim — APM
def prim(adj, n):
in_apm = [False] * n
total = 0
pq = [(0, 0)]
while pq:
c, u = heapq.heappop(pq)
if in_apm[u]: continue
in_apm[u] = True
total += c
for v, w in adj[u]:
if not in_apm[v]:
heapq.heappush(pq, (w, v))
return total
# Roy-Floyd — drumuri toate perechile
def floyd(a, n):
d = [row[:] for row in a]
for k in range(n):
for i in range(n):
for j in range(n):
if d[i][k] and d[k][j]:
d[i][j] = min(d[i][j] or 10**9, d[i][k] + d[k][j])
return d
Anexa C — Cheat sheet arbori¶
# Definiția unui nod
class Nod:
def __init__(self, v):
self.v = v
self.s = self.d = None
# Parcurgeri
def pre(n):
if n: print(n.v, end=' '); pre(n.s); pre(n.d)
def inord(n):
if n: inord(n.s); print(n.v, end=' '); inord(n.d)
def post(n):
if n: post(n.s); post(n.d); print(n.v, end=' ')
# BST — inserare
def ins(n, v):
if not n: return Nod(v)
if v < n.v: n.s = ins(n.s, v)
else: n.d = ins(n.d, v)
return n
# BST — căutare
def cauta(n, v):
if not n: return False
if n.v == v: return True
return cauta(n.s, v) if v < n.v else cauta(n.d, v)
Anexa D — Șabloane Backtracking¶
# Șablon generic
def back(poz):
if poz == n:
afișează_solutia()
return
for v in valori_posibile:
if valid(poz, v):
st[poz] = v
back(poz + 1)
# Permutări
def perm(poz):
if poz == n:
print(st); return
for v in range(1, n+1):
if not folosit[v]:
st[poz] = v; folosit[v] = True
perm(poz+1)
folosit[v] = False
# Combinări
def comb(poz, start):
if poz == k:
print(st); return
for v in range(start, n+1):
st[poz] = v
comb(poz+1, v+1)
# Produs cartezian
def prod(poz):
if poz == k:
print(st); return
for v in range(1, n+1):
st[poz] = v
prod(poz+1)
Anexa E — Probleme de olimpiadă recomandate¶
Backtracking:
- Submulțimi cu suma S.
- N-regine (n=12).
- Cavalerul pe tablă de șah (problema Turului calului).
- Sudoku solver.
- Labirint — toate ieșirile.
Grafuri:
6. Componente conexe într-o rețea.
7. Rutare optimă cu Dijkstra (hartă GPS).
8. APM pentru o rețea de cabluri.
9. Flux maxim (pentru clasa XII).
10. Detectarea ciclurilor.
Arbori:
11. Numărul de noduri pe fiecare nivel.
12. Diametrul unui arbore (cel mai lung drum).
13. Cel mai apropiat strămoș comun (LCA).
14. Serializarea și deserializarea unui arbore.
15. Construcție arbore din preordine + inordine.
POO:
16. Sistem bibliotecă complet (clase Carte, Cititor, Biblioteca, Împrumut).
17. Joc de cărți (clase Carte, Pachet, Jucator, Joc).
18. Simulator bancar (Cont, ContEconomii derivat, Tranzactie).
Anexa F — Resurse online esențiale¶
Vizualizări algoritmi:
- VisuAlgo — Graph — BFS, DFS, MST, SSSP animate.
- VisuAlgo — Tree — BST operations.
- Algorithm Visualizer — colecție mare.
Probleme gradate:
- pbinfo.ro — categorie „Grafuri”, „Arbori”, „Backtracking”.
- infoarena.ro — probleme olimpiadă România.
- Codeforces — probleme internaționale.
Cursuri online:
- CS50 Harvard — introducere generală.
- MIT 6.006 — algoritmi.
Anexa G — Glosar (termeni noi din clasa a XI-a)¶
| Termen | Definiție |
|---|---|
| Graf | Structură (V, E) cu noduri și muchii/arce. |
| Muchie | Legătură neorientată între două noduri. |
| Arc | Legătură orientată între două noduri. |
| Adiacență | Două noduri conectate direct printr-o muchie/arc. |
| Grad | Numărul de muchii incidente cu un nod. |
| Lanț | Succesiune de muchii consecutive (graf neorientat). |
| Drum | Echivalent cu lanțul, în graf orientat. |
| Ciclu | Lanț închis (primul = ultimul nod). |
| Conex | Graf în care există lanț între orice două noduri. |
| Tare conex | Graf orientat cu drum între orice două noduri. |
| Ponderat | Graf cu costuri pe muchii/arce. |
| Hamiltonian | Conține ciclu/lanț ce vizitează fiecare nod o dată. |
| Eulerian | Conține ciclu/lanț ce folosește fiecare muchie o dată. |
| BFS | Parcurgere în lățime (coadă). |
| DFS | Parcurgere în adâncime (stivă/recursie). |
| APM | Arbore parțial de cost minim. |
| Backtracking | Strategie de explorare a tuturor variantelor cu retur. |
| Arbore | Graf conex fără cicluri. |
| Rădăcină | Nodul special al unui arbore cu rădăcină. |
| Frunză | Nod fără descendenți. |
| BST | Arbore binar de căutare. |
| Preordine/Inordine/Postordine | Cele 3 parcurgeri recursive ale arborilor binari. |
| Clasă | Șablon pentru crearea obiectelor. |
| Obiect | Instanță concretă a unei clase. |
| Încapsulare | Protejarea datelor interne prin metode. |
| Moștenire | Relație între o clasă de bază și derivate. |
| Polimorfism | Comportament diferit al aceleiași metode în clase diferite. |
| Constructor | Metoda specială apelată la crearea obiectului. |
| Paradigmă | Mod fundamental de a concepe programele. |
Anexa H — Pregătire pentru clasa a XII-a¶
Anul viitor urmează:
- Programare dinamică (extindere a recursivității cu memoizare).
- Baze de date SQL — model relațional.
- Normalizarea bazelor de date.
- Învățare automată (Machine Learning) — scikit-learn, TensorFlow.
Pregătire utilă acum:
- Rezolvă cât mai multe probleme cu backtracking și grafuri.
- Exersează POO construind proiecte complete (nu doar exerciții).
- Învață SQL de bază în vacanță (oricum utile).
🎓 Încheiere¶
Clasa a XI-a este anul decisiv pentru un viitor informatician. Ai parcurs:
✅ Backtracking — generare sistematică și problema N-reginelor.
✅ Grafuri — concepte, reprezentări și toți algoritmii mari (BFS, DFS, Dijkstra, Prim, Roy-Floyd).
✅ Arbori — structuri ierarhice, BST, parcurgeri.
✅ POO în Python și C++ — clase, moștenire, polimorfism.
✅ Paradigme de programare — perspectivă largă asupra domeniului.
Principii pentru succes¶
1. Practică intensă:
Informatica e ca sportul — teoria fără practică e sterilă. Un informatician bun rezolvă minim 200 de probleme în cursul clasei a XI-a.
2. Înțelege, nu memoriza:
BFS și DFS au aceeași structură; diferă doar la coadă vs. stivă. Dijkstra = BFS + priority queue. Prim = Dijkstra cu alt criteriu. Vezi tiparele!
3. Desenează:
Înainte să scrii cod pentru un graf/arbore, desenează-l pe hârtie. Simulează algoritmul manual.
4. Testează la limită:
Cazuri extreme: graf vid, cu un singur nod, complet, izolat. Arbore cu un singur nod. Permutări pentru n=0.
5. Colaborare:
Explică-ți algoritmii unui coleg — dacă nu-i poți explica, nu i-ai înțeles. Lucrul în echipă la proiecte mari te pregătește pentru facultate.
O recomandare personală¶
„Cel mai bun cod este cel pe care nu-l scrii.” — proverb al programatorilor.
Înainte să scrii ceva complicat, întreabă-te: există deja un algoritm/structură standard care rezolvă asta? Gândirea asta te va transforma dintr-un elev care rezolvă probleme într-un arhitect de soluții.
Document generat în conformitate cu Programa școlară pentru disciplina Informatică, clasa a XI-a, curriculum de specialitate, Ordin MEC 4.350/2025, Anexa 46.