2/4
</> Informatica liceu

Informatica clasa 10

Lecția 2 ⏱ 120 min

Curs Complet de Informatică — Clasa a X-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 44)


📋 Cuprins

  1. Introducere
  2. Capitolul 1: Metode de prelucrare a listelor sortate — căutarea binară și interclasarea
  3. Capitolul 2: Modelul conceptual neliniar — mulțimea
  4. Capitolul 3: Clasa set din Python
  5. Capitolul 4: Modelul conceptual liniar — șirul de caractere
  6. Capitolul 5: Clasa str din Python și string din C++
  7. Capitolul 6: Modelul conceptual asociativ — dicționarul
  8. Capitolul 7: Clasa dict din Python
  9. Capitolul 8: Modele conceptuale mixte
  10. Capitolul 9: Subprograme recursive
  11. Capitolul 10: Metoda Divide et Impera
  12. Capitolul 11: Metoda Greedy
  13. Anexe

Introducere

Despre acest curs

Acest curs acoperă integral programa școlară de informatică pentru clasa a X-a, specializarea matematică-informatică (Ordin MEC 4.350/2025, Anexa 44). Cursul continuă firesc materia din clasa a IX-a, introducând concepte mai avansate:

  • Modele conceptuale noi: mulțimi (neliniare), dicționare (asociative), șiruri de caractere, structuri mixte.
  • Strategii avansate: căutare binară, interclasare, Divide et Impera, Greedy.
  • Subprograme recursive — un nou paradigm de gândire algoritmică.

Recapitulare — ce am învățat în clasa a IX-a

Pentru a urmări cursul cu ușurință, ar trebui să stăpânești deja:

  • Structuri de control (if, while, for).
  • Liste Python (list) și vectori C++ (vector).
  • Subprograme (funcții) iterative.
  • Prelucrări de numere (cifre, divizori, CMMDC).
  • Sortare elementară (selecția, bulele, lista de frecvențe).
  • Fișiere text și interfețe grafice (Tkinter).

Ce e nou în clasa a X-a?

În clasa a IX-a, datele erau fie simple, fie organizate liniar (liste). Acum vom întâlni:

  • Modele neliniare (mulțimea) — fără ordine, fără duplicate.
  • Modele asociative (dicționarul) — acces prin „cheie” în loc de poziție.
  • Modele mixte — combinații (liste de dicționare, dicționare de liste, matrice).

Conceptual, gândirea se schimbă:

În clasa a IX-a: „Unde se află datele? (la poziția i)”
În clasa a X-a: „Care sunt datele legate de o anumită informație-cheie?”

Competențe generale

Cele 6 CG rămân aceleași ca în clasa a IX-a (bazate pe taxonomia Bloom revizuită): Identificare → Explicare → Utilizare → Analiză → Evaluare → Creare.


Capitolul 1: Metode de prelucrare a listelor sortate

1.1. De ce ne trebuie liste sortate?

O listă ordonată (sortată) permite algoritmi mult mai eficienți decât o listă neordonată. Două operații fundamentale câștigă enorm în viteză când datele sunt sortate:

  • Căutarea unui element (căutare binară — O(log n) vs O(n) la parcurgerea liniară).
  • Combinarea a două liste sortate (interclasarea — O(n+m)).

1.2. Căutarea secvențială vs. căutarea binară

Căutarea secvențială (liniară)

Parcurgem lista element cu element până găsim valoarea dorită.

def cautare_secventiala(a, x):
    for i in range(len(a)):
        if a[i] == x:
            return i
    return -1  # nu a fost găsit

Complexitate: O(n) — în cel mai rău caz, parcurgem toată lista.

Căutarea binară

Condiție obligatorie: lista să fie sortată.

Idee: la fiecare pas, ne uităm la mijloc:

  • Dacă elementul de la mijloc este cel căutat → gata.
  • Dacă este mai mic decât cel căutat → căutăm în jumătatea dreaptă.
  • Dacă este mai mare → căutăm în jumătatea stângă.

La fiecare pas, dimensiunea intervalului se înjumătățește.

Vizualizare

Căutăm 23 în [2, 5, 7, 12, 17, 23, 31, 45, 58, 72]:

Pas 1: [2, 5, 7, 12, 17, 23, 31, 45, 58, 72]
        ↑                    ↑               ↑
       st=0                mij=4           dr=9
       a[4]=17 < 23 → mergem dreapta

Pas 2: [23, 31, 45, 58, 72]
        ↑       ↑        ↑
       st=5   mij=7     dr=9
       a[7]=45 > 23 → mergem stânga

Pas 3: [23, 31]
        ↑   ↑
       st=5 mij=5  (dr=6)
       a[5]=23 → GĂSIT! poziția 5

Implementare Python

def cautare_binara(a, x):
    st, dr = 0, len(a) - 1
    while st <= dr:
        mij = (st + dr) // 2
        if a[mij] == x:
            return mij
        elif a[mij] < x:
            st = mij + 1
        else:
            dr = mij - 1
    return -1

lista = [2, 5, 7, 12, 17, 23, 31, 45, 58, 72]
print(cautare_binara(lista, 23))  # 5
print(cautare_binara(lista, 100)) # -1

Implementare C++

#include <iostream>
#include <vector>
using namespace std;

int cautare_binara(vector<int>& a, int x) {
    int st = 0, dr = a.size() - 1;
    while (st <= dr) {
        int mij = st + (dr - st) / 2;  // evităm overflow
        if (a[mij] == x) return mij;
        else if (a[mij] < x) st = mij + 1;
        else dr = mij - 1;
    }
    return -1;
}

int main() {
    vector<int> lista = {2, 5, 7, 12, 17, 23, 31, 45, 58, 72};
    cout << cautare_binara(lista, 23);  // 5
    return 0;
}

Complexitate

Dimensiune (n) Pași secvențială Pași binară
10 max 10 max 4
100 max 100 max 7
1.000 max 1.000 max 10
1.000.000 max 1.000.000 max 20
1.000.000.000 max 1 miliard max 30

Căutarea binară are complexitate O(log₂ n) — fantastic de rapidă.

1.3. Versiunea recursivă a căutării binare

def cautare_binara_rec(a, x, st, dr):
    if st > dr:
        return -1
    mij = (st + dr) // 2
    if a[mij] == x:
        return mij
    elif a[mij] < x:
        return cautare_binara_rec(a, x, mij + 1, dr)
    else:
        return cautare_binara_rec(a, x, st, mij - 1)

lista = [2, 5, 7, 12, 17, 23, 31, 45, 58, 72]
print(cautare_binara_rec(lista, 23, 0, len(lista) - 1))  # 5

1.4. Erori frecvente la căutarea binară

  • Lista nu este sortată — rezultat imprevizibil.
  • Condiția st <= dr (nu st < dr) — altfel pierzi elementul de la poziția finală.
  • Actualizare greșită: st = mij (în loc de mij + 1) → ciclu infinit.

1.5. Interclasarea a două liste sortate

Problemă: Avem două liste deja sortate crescător. Vrem să obținem o singură listă care conține toate elementele, tot sortată.

Soluție ineficientă: concatenare + sortare → O((n+m)·log(n+m)).
Soluție eficientă — interclasarea: O(n+m).

Algoritmul de interclasare

Folosim doi indici, câte unul pentru fiecare listă. La fiecare pas:

  1. Comparăm elementele curente din fiecare listă.
  2. Adăugăm în lista rezultat pe cel mai mic.
  3. Avansăm indicele listei din care am luat elementul.
  4. Când o listă se epuizează, copiem elementele rămase din cealaltă.

Vizualizare

A = [1, 4, 7, 9]
B = [2, 3, 8, 10, 15]

i=0, j=0: A[0]=1 < B[0]=2 → adaugăm 1   Rezultat: [1]
i=1, j=0: A[1]=4 > B[0]=2 → adaugăm 2   Rezultat: [1, 2]
i=1, j=1: A[1]=4 > B[1]=3 → adaugăm 3   Rezultat: [1, 2, 3]
i=1, j=2: A[1]=4 < B[2]=8 → adaugăm 4   Rezultat: [1, 2, 3, 4]
i=2, j=2: A[2]=7 < B[2]=8 → adaugăm 7   Rezultat: [1, 2, 3, 4, 7]
i=3, j=2: A[3]=9 > B[2]=8 → adaugăm 8   Rezultat: [1, 2, 3, 4, 7, 8]
i=3, j=3: A[3]=9 < B[3]=10 → adaugăm 9  Rezultat: [1, 2, 3, 4, 7, 8, 9]
A epuizată → copiem rest din B         Rezultat: [1, 2, 3, 4, 7, 8, 9, 10, 15]

Implementare Python

def interclasare(a, b):
    rezultat = []
    i, j = 0, 0
    while i < len(a) and j < len(b):
        if a[i] <= b[j]:
            rezultat.append(a[i])
            i += 1
        else:
            rezultat.append(b[j])
            j += 1
    # Copiem elementele rămase (doar una din bucle va executa)
    while i < len(a):
        rezultat.append(a[i])
        i += 1
    while j < len(b):
        rezultat.append(b[j])
        j += 1
    return rezultat

A = [1, 4, 7, 9]
B = [2, 3, 8, 10, 15]
print(interclasare(A, B))
# [1, 2, 3, 4, 7, 8, 9, 10, 15]

Implementare C++

vector<int> interclasare(vector<int>& a, vector<int>& b) {
    vector<int> rezultat;
    int i = 0, j = 0;
    while (i < a.size() && j < b.size()) {
        if (a[i] <= b[j]) rezultat.push_back(a[i++]);
        else              rezultat.push_back(b[j++]);
    }
    while (i < a.size()) rezultat.push_back(a[i++]);
    while (j < b.size()) rezultat.push_back(b[j++]);
    return rezultat;
}

1.6. Stabilitatea interclasării

Interclasarea este stabilă: păstrează ordinea relativă a elementelor egale. Acest lucru este important când avem liste de obiecte cu mai multe câmpuri (de exemplu, elevi sortați după notă — la egalitate, rămân în ordinea originală).

✏️ Exerciții propuse — Capitolul 1

  1. Modifică funcția cautare_binara să returneze numărul de pași (iterații) efectuați.
  2. Scrie o funcție care, pentru o listă sortată, returnează prima poziție unde apare o valoare (nu oricare).
  3. Implementează căutarea binară pentru a găsi în ce interval se află o valoare (de ex., pentru [10, 20, 30, 40] și x=25, returnează (1, 2)).
  4. Interclasează două liste sortate care pot conține duplicate, păstrând duplicatele.
  5. Interclasează trei liste sortate.

Capitolul 2: Modelul conceptual neliniar — mulțimea

2.1. Ce este o mulțime?

Mulțimea este o colecție de elemente cu două caracteristici esențiale:

  1. Fiecare element apare o singură dată (fără duplicate).
  2. Ordinea nu contează{1, 2, 3} este aceeași mulțime ca {3, 1, 2}.

Exemple din realitate:

  • Mulțimea elevilor înscriși la un curs.
  • Mulțimea orașelor pe care le-ai vizitat.
  • Mulțimea drepturilor de acces atribuite utilizatorilor.

Diferența față de listă:

Aspect Listă Mulțime
Duplicate Permise Nu
Ordine Contează Nu contează
Acces prin index Da Nu
Verificare apartenență O(n) O(1) mediu

2.2. Operații matematice cu mulțimi

Fie A = {1, 2, 3, 4} și B = {3, 4, 5, 6}.

Operație Notație matematică Rezultat Semnificație
Reuniune A ∪ B {1, 2, 3, 4, 5, 6} Toate elementele din A sau B
Intersecție A ∩ B {3, 4} Elementele din A și B
Diferență A − B {1, 2} Elementele din A dar nu din B
Diferență simetrică A △ B {1, 2, 5, 6} Elementele doar dintr-una singură
Apartenență x ∈ A True/False x face parte din A?
Incluziune A ⊆ B True/False Toate elementele din A sunt și în B?
Egalitate A = B True/False Conțin exact aceleași elemente?

2.3. Diagrame Venn

Diagramele Venn sunt reprezentări vizuale ale mulțimilor:

         A           B
      ┌─────┐     ┌─────┐
      │  1  │     │  5  │
      │  2  │  3  │  6  │
      │     │  4  │     │
      └─────┘─────└─────┘
              ↑
         A ∩ B = {3, 4}

2.4. Aplicații practice

Exemplu 1: Avem două magazine online. Fiecare are o mulțime de clienți.

  • Reuniune → toți clienții cărora putem trimite promoții.
  • Intersecție → clienții fideli (au cumpărat din ambele magazine).
  • Diferență → clienții exclusivi ai primului magazin.

Exemplu 2: Validarea unui puzzle Sudoku — într-o linie, coloană sau sub-pătrat, cifrele trebuie să formeze o mulțime {1, 2, 3, 4, 5, 6, 7, 8, 9} (fără repetiții).

✏️ Exerciții propuse — Capitolul 2

  1. Desenează diagrama Venn pentru A = {a, b, c, d} și B = {b, c, e, f}. Scrie A∪B, A∩B, A−B, B−A.
  2. Explică cu cuvintele tale: când două mulțimi sunt egale? Când una este inclusă în alta?
  3. Dă 3 exemple din viața reală în care modelul de mulțime este mai potrivit decât modelul de listă.

Capitolul 3: Clasa set din Python

3.1. Crearea unei mulțimi

# Mulțime vidă — ATENȚIE: {} este dicționar, nu set!
m = set()

# Mulțime cu valori
fructe = {"mere", "pere", "banane"}
numere = {1, 2, 3, 4, 5}

# Din listă (elimină automat duplicatele)
lista = [1, 2, 2, 3, 3, 3, 4]
unice = set(lista)  # {1, 2, 3, 4}

# Set comprehension
patrate = {x*x for x in range(1, 6)}  # {1, 4, 9, 16, 25}

3.2. Reguli importante

  • Elementele unei mulțimi trebuie să fie imutabile (int, float, str, tuple — DA; list, dict — NU).
  • Nu putem accesa elementele prin index: m[0] dă eroare.
# Corect
m = {1, 2, "text", (3, 4)}

# EROARE — lista este mutabilă
# m = {1, 2, [3, 4]}  # TypeError: unhashable type: 'list'

3.3. Operatori pe mulțimi

Operator Python Semnificație matematică Exemplu
| Reuniune (∪) A | B
& Intersecție (∩) A & B
- Diferență (−) A - B
^ Diferență simetrică (△) A ^ B
in Apartenență (∈) 3 in A
<= Incluziune (⊆) A <= B
< Incluziune strictă A < B
== Egalitate A == B
A = {1, 2, 3, 4}
B = {3, 4, 5, 6}

print(A | B)   # {1, 2, 3, 4, 5, 6}
print(A & B)   # {3, 4}
print(A - B)   # {1, 2}
print(A ^ B)   # {1, 2, 5, 6}
print(3 in A)  # True
print({1, 2} <= A)  # True (subset)
print(A == B)  # False

3.4. Metode uzuale

m = {1, 2, 3}

# Adăugare
m.add(4)              # {1, 2, 3, 4}
m.add(2)              # {1, 2, 3, 4}  — fără efect, deja exista

# Eliminare
m.remove(2)           # {1, 3, 4}  — eroare dacă nu există
m.discard(99)         # {1, 3, 4}  — nu dă eroare dacă nu există
x = m.pop()           # elimină și returnează un element oarecare

# Operații cu returnare (nu modifică mulțimea originală)
A = {1, 2, 3}
B = {3, 4, 5}
print(A.union(B))         # {1, 2, 3, 4, 5}
print(A.intersection(B))  # {3}
print(A.difference(B))    # {1, 2}

# Operații care modifică mulțimea
A.update(B)               # A devine A ∪ B
A.intersection_update(B)  # A devine A ∩ B

# Copiere
C = A.copy()

# Verificări
print(A.issubset({1, 2, 3, 4}))    # True
print(A.issuperset({1, 2}))         # True
print(A.isdisjoint({9, 10}))        # True — nu au elemente comune

3.5. Mulțimi în C++

C++ oferă std::set (sortată, bazată pe arbore) și std::unordered_set (neordonată, bazată pe hash).

#include <iostream>
#include <set>
#include <unordered_set>
using namespace std;

int main() {
    set<int> A = {3, 1, 4, 1, 5, 9, 2, 6};  // duplicatele se elimină

    // set este ORDONAT
    for (int x : A) cout << x << " ";
    // Ieșire: 1 2 3 4 5 6 9

    A.insert(7);
    A.erase(4);

    if (A.count(3)) cout << "3 există";
    if (A.find(10) == A.end()) cout << "10 nu există";

    // Reuniune și intersecție — din <algorithm>
    set<int> B = {4, 5, 6, 7};
    set<int> reuniune;
    set_union(A.begin(), A.end(), B.begin(), B.end(),
              inserter(reuniune, reuniune.begin()));
    return 0;
}

3.6. Aplicații practice

Problemă 1 — Eliminare duplicate dintr-o listă

def elimina_duplicate(lista):
    return list(set(lista))

print(elimina_duplicate([1, 2, 2, 3, 3, 3, 4]))  # [1, 2, 3, 4]

⚠️ Atenție: conversia în set PIERDE ordinea. Pentru a o păstra, folosim:

python def elimina_duplicate_ordine(lista): vazute = set() rezultat = [] for x in lista: if x not in vazute: vazute.add(x) rezultat.append(x) return rezultat

Problemă 2 — Cifrele distincte ale unui număr

def cifre_distincte(n):
    return set(str(n))  # convertim în șir, apoi în mulțime

print(cifre_distincte(122334))  # {'1', '2', '3', '4'}

Problemă 3 — Anagrame

Două cuvinte sunt anagrame dacă conțin aceleași litere (cu aceleași frecvențe).

def sunt_anagrame(s1, s2):
    # Pentru verificare cu frecvențe, nu doar mulțimi:
    return sorted(s1) == sorted(s2)
    # set(s1) == set(s2) este INSUFICIENT! 
    # Ex: "aab" și "abb" au același set dar nu sunt anagrame

print(sunt_anagrame("listen", "silent"))  # True
print(sunt_anagrame("aab", "abb"))         # False

Problemă 4 — Verificare cifre comune

def cifre_comune(a, b):
    cifre_a = set(str(a))
    cifre_b = set(str(b))
    return cifre_a & cifre_b

print(cifre_comune(12345, 56789))  # {'5'}

Problemă 5 — Joc Bingo (numere aleatoare distincte)

import random

def bingo(cate, minim, maxim):
    numere = set()
    while len(numere) < cate:
        numere.add(random.randint(minim, maxim))
    return sorted(numere)

print(bingo(6, 1, 49))  # 6 numere distincte din 1..49

✏️ Exerciții propuse — Capitolul 3

  1. Scrie un program care citește două liste de nume și afișează: numele comune, numele doar din prima, numele doar din a doua.
  2. Găsește toate literele vocale distincte dintr-un text.
  3. Verifică dacă toate zilele săptămânii apar într-o listă de evenimente.
  4. Generează un număr maxim folosind cifrele distincte ale unui număr dat.
  5. Având lista prietenilor pe 3 rețele sociale, afișează: prietenii comuni tuturor, prietenii doar într-o rețea.

Capitolul 4: Modelul conceptual liniar — șirul de caractere

4.1. Ce este un șir de caractere?

Un șir de caractere (string) este o colecție ordonată și imutabilă de caractere.

Exemple:

  • Un nume: "Alex"
  • O propoziție: "Bună ziua!"
  • Un cod: "ABC-123"
  • Un text lung: "Acesta este un paragraf cu mai multe cuvinte."

4.2. Caracteristici fundamentale

  1. Ordonat — fiecare caracter are o poziție precisă.
  2. Indexat — primul caracter are indexul 0.
  3. Imutabil (în Python) — după creare, NU poate fi modificat direct.
  4. Iterabil — poți parcurge fiecare caracter.

4.3. Codificarea caracterelor — ASCII și Unicode

Fiecare caracter este reprezentat în memorie printr-un cod numeric.

ASCII (standard vechi, 128 caractere):

  • 'A' → 65
  • 'a' → 97
  • '0' → 48
  • ' ' (spațiu) → 32

Unicode/UTF-8 (standard modern, ~150.000 caractere): include toate limbile.

print(ord('A'))   # 65 — codul numeric
print(ord('ă'))   # 259 — caracter românesc
print(chr(65))    # 'A' — caracterul de la codul 65
print(chr(8364))  # '€' — simbolul euro

Relații utile:

  • ord('a') = 97, ord('z') = 122
  • ord('A') = 65, ord('Z') = 90
  • ord('0') = 48, ord('9') = 57
  • ord(litera_mica) - ord('a') = poziția in alfabet (0 pentru ‘a’, 25 pentru ‘z’)

4.4. Indexare și slicing

s = "Programare"
#    0123456789
#   -10-9...-1

# Acces la caracter
print(s[0])    # 'P'
print(s[4])    # 'r'
print(s[-1])   # 'e'  (ultimul)
print(s[-2])   # 'r'  (penultimul)

# Slicing [start:stop:step]
print(s[0:4])   # "Prog"  (poziții 0, 1, 2, 3)
print(s[4:])    # "ramare"
print(s[:4])    # "Prog"
print(s[::2])   # "Pormr"  (din 2 în 2)
print(s[::-1])  # "eramargorP"  (inversat)

4.5. Imutabilitatea

În Python, nu poți modifica un șir existent:

s = "Alex"
# s[0] = 'M'  # EROARE!

# Trebuie să creezi un șir NOU
s_nou = "M" + s[1:]  # "Mlex"

4.6. Parcurgerea unui șir

s = "Python"

# Prin caracter
for c in s:
    print(c)

# Prin index
for i in range(len(s)):
    print(f"s[{i}] = {s[i]}")

# Prin index și caracter
for i, c in enumerate(s):
    print(f"{i}: {c}")

✏️ Exerciții propuse — Capitolul 4

  1. Citește un cuvânt și afișează fiecare literă pe câte un rând.
  2. Afișează codul ASCII al fiecărui caracter dintr-un șir.
  3. Verifică dacă primul și ultimul caracter al unui cuvânt sunt identici.
  4. Afișează un șir inversat, fără a folosi [::-1] sau metode speciale.

Capitolul 5: Clasa str din Python și string din C++

5.1. Crearea unui string în Python

Python permite mai multe moduri de a scrie șiruri:

s1 = 'Salut'              # apostrofuri simple
s2 = "Alex"                # ghilimele duble
s3 = '''Text pe
mai multe linii'''         # apostrofuri triple
s4 = """La fel cu 
ghilimele triple"""
s5 = "El a spus: \"Salut\""  # escape cu backslash
s6 = 'El a spus: "Salut"'    # mai elegant

5.2. Operatori pe șiruri

s = "Salut"
t = "lume"

# Concatenare
print(s + " " + t)    # "Salut lume"

# Multiplicare
print("ab" * 3)        # "ababab"
print("-" * 20)        # "--------------------"

# Apartenență
print("lut" in s)      # True
print("xyz" in s)      # False
print("ab" not in s)   # True

# Comparare (lexicografică — ordine alfabetică)
print("abc" < "abd")   # True
print("abc" < "abcd")  # True (mai scurt = mai mic la prefix)
print("Abc" < "abc")   # True (majuscula < minuscula în ASCII)

# Dimensiune
print(len(s))          # 5

5.3. Metode pentru schimbarea formatului

s = "Bună Ziua, Alex!"

print(s.upper())       # "BUNĂ ZIUA, ALEX!"
print(s.lower())       # "bună ziua, alex!"
print(s.title())       # "Bună Ziua, Alex!" — fiecare cuvânt cu majusculă
print(s.capitalize())  # "Bună ziua, alex!" — doar prima literă
print(s.swapcase())    # "bUNĂ zIUA, aLEX!" — inversează

5.4. Metode pentru verificarea conținutului

print("abc123".isalnum())   # True — litere și cifre
print("abc".isalpha())       # True — doar litere
print("123".isdigit())       # True — doar cifre
print("   ".isspace())       # True — doar spații
print("ABC".isupper())       # True
print("abc".islower())       # True
print("Title Text".istitle()) # True

5.5. Metode pentru căutare

s = "Bună ziua, bună seara"

# Indexul primei apariții
print(s.find("bună"))      # 11
print(s.find("xyz"))       # -1 (nu există)
print(s.index("bună"))     # 11 (ERoare dacă nu există)

# Indexul ultimei apariții
print(s.rfind("bună"))     # 11 (doar una este aici)

# Numărul de apariții
print(s.count("bună"))     # 1 (case-sensitive!)
print(s.lower().count("bună"))  # 2

# Verifică început/sfârșit
print(s.startswith("Bună"))   # True
print(s.endswith("seara"))    # True

5.6. Metode pentru modificare (returnează șir NOU)

s = "  Salut, lume!  "

# Eliminare spații
print(s.strip())        # "Salut, lume!"
print(s.lstrip())       # "Salut, lume!  "
print(s.rstrip())       # "  Salut, lume!"

# Înlocuire
print("abcabc".replace("a", "X"))     # "XbcXbc"
print("abcabc".replace("a", "X", 1))  # "Xbcabc" — doar prima apariție

# Divizare
print("a,b,c,d".split(","))            # ["a", "b", "c", "d"]
print("unul doi trei".split())         # ["unul", "doi", "trei"] — spații
print("abc\ndef\nghi".splitlines())    # ["abc", "def", "ghi"]

# Alipire (inversul lui split)
print("-".join(["2024", "01", "15"]))  # "2024-01-15"
print(" ".join(["Alex", "e", "aici"])) # "Alex e aici"

5.7. Formatare — f-strings

nume = "Alex"
varsta = 15
media = 9.723456

# f-strings (Python 3.6+)
print(f"Salut, {nume}! Ai {varsta} ani.")
print(f"Media: {media:.2f}")         # 2 zecimale
print(f"Media: {media:8.2f}")         # 8 caractere total, 2 zecimale
print(f"Procent: {0.85:.0%}")         # "85%"

# Format alternativ
print("Salut, {}! Ai {} ani.".format(nume, varsta))
print("Media: %.2f" % media)

5.8. Clasa string din C++

#include <iostream>
#include <string>
#include <algorithm>
using namespace std;

int main() {
    string s = "Salut";
    string t = "lume";

    // Concatenare
    string u = s + " " + t;         // "Salut lume"
    s += "!";                        // "Salut!"

    // Acces la caracter
    cout << s[0] << endl;            // 'S'
    cout << s.length() << endl;      // 6 (len)
    cout << s.size() << endl;        // 6 (tot len)

    // Substring
    cout << s.substr(0, 3) << endl;  // "Sal" (poz 0, lungime 3)

    // Căutare
    cout << s.find("lut") << endl;   // 2
    cout << s.find("xy") << endl;    // string::npos (-1 in practica)

    // Înlocuire
    string v = "abcabc";
    size_t pos = v.find("a");
    if (pos != string::npos) v.replace(pos, 1, "X");  // "Xbcabc"

    // Conversii (C++11+)
    int nr = stoi("42");
    double r = stod("3.14");
    string snr = to_string(123);

    // Transformare (manuală cu <algorithm>)
    string w = "Hello";
    transform(w.begin(), w.end(), w.begin(), ::tolower);  // "hello"

    // Comparare lexicografică
    cout << (string("abc") < string("abd")) << endl;  // 1 (true)

    return 0;
}

5.9. Probleme rezolvate

Problemă 1 — Validare e-mail simplu

def email_valid(s):
    return s.count("@") == 1 and "." in s.split("@")[1] and " " not in s

print(email_valid("alex@exemplu.ro"))    # True
print(email_valid("alex@@exemplu.ro"))   # False
print(email_valid("alex@exemplu"))       # False
print(email_valid("alex exemplu.ro"))    # False

Problemă 2 — Numărare vocale

def numar_vocale(s):
    vocale = set("aeiouăîâAEIOUĂÎÂ")
    return sum(1 for c in s if c in vocale)

print(numar_vocale("Bună ziua, Alex!"))  # 7

Problemă 3 — Criptare Caesar (cifru cu deplasare)

def caesar_cripteaza(text, k):
    rezultat = ""
    for c in text:
        if c.isupper():
            rezultat += chr((ord(c) - ord('A') + k) % 26 + ord('A'))
        elif c.islower():
            rezultat += chr((ord(c) - ord('a') + k) % 26 + ord('a'))
        else:
            rezultat += c  # caracterele non-literă rămân neschimbate
    return rezultat

def caesar_decripteaza(text, k):
    return caesar_cripteaza(text, -k)

mesaj = "Salut, Alex!"
cifrat = caesar_cripteaza(mesaj, 3)
print(cifrat)                            # "Vdoxw, Doha!"
print(caesar_decripteaza(cifrat, 3))     # "Salut, Alex!"

Problemă 4 — Palindrom

def este_palindrom(s):
    s_curat = ''.join(c.lower() for c in s if c.isalnum())
    return s_curat == s_curat[::-1]

print(este_palindrom("capac"))               # True
print(este_palindrom("Ele fac cafele"))      # True
print(este_palindrom("python"))              # False

Problemă 5 — Frecvența cuvintelor

def frecventa_cuvinte(text):
    cuvinte = text.lower().split()
    frecv = {}
    for c in cuvinte:
        c_curat = c.strip(".,!?;:")
        frecv[c_curat] = frecv.get(c_curat, 0) + 1
    return frecv

text = "Ana are mere. Mere sunt roșii. Ana iubește mere."
print(frecventa_cuvinte(text))
# {'ana': 2, 'are': 1, 'mere': 3, 'sunt': 1, 'roșii': 1, 'iubește': 1}

✏️ Exerciții propuse — Capitolul 5

  1. Scrie o funcție care primește un nume complet ("Ion Popescu Marin") și returnează-l reorganizat ca "Popescu Marin Ion".
  2. Validează un număr de telefon: 10 cifre, poate conține spații/cratime.
  3. Numără câte cuvinte dintr-un text încep cu literă mare.
  4. Scrie o funcție care transformă un text în „limbaj de păsări” (fiecare vocală este dublată cu „p”: „a” → „apa”).
  5. Găsește cel mai lung cuvânt dintr-o propoziție.
  6. Verifică complexitatea unei parole (conține litere mari, mici, cifre, simboluri, minim 8 caractere).

Capitolul 6: Modelul conceptual asociativ — dicționarul

6.1. Ce este un dicționar?

Un dicționar este o colecție de perechi cheie → valoare. Accesul se face prin cheie, nu prin poziție.

Analogii din realitate:

  • Dicționarul lingvistic: cuvânt (cheie) → definiție (valoare).
  • Agenda telefonică: nume (cheie) → număr (valoare).
  • Evidența populației: CNP (cheie) → date personale (valoare).
  • Meniu restaurant: denumire preparat (cheie) → preț (valoare).

6.2. Caracteristici

  1. Cheile sunt unice — nu poți avea două chei identice.
  2. Cheile sunt imutabile — în general, str, int, tuple (nu list, dict).
  3. Valorile pot fi orice — chiar alte dicționare sau liste.
  4. Accesul este foarte rapid — O(1) în medie (bazat pe tabele hash).

6.3. Când folosim dicționare?

Folosim dicționare când:

  • Avem o relație clară „cheie → valoare”.
  • Vrem acces rapid după un identificator (nu după poziție).
  • Cheile au nume logice (nu doar 0, 1, 2…).

Folosim liste când:

  • Ordinea este importantă.
  • Nu avem identificatori unici.
  • Vrem să parcurgem toate elementele.

6.4. Comparație vizuală

Lista:

indice:    0       1         2      3
valori: "Ana"  "Bogdan"  "Ion"  "Maria"

Dicționarul:

chei:     "Ana"  "Bogdan"  "Ion"  "Maria"
valori:     9      10        7      8

În listă, lista[2] înseamnă „al 3-lea element”. În dicționar, note["Ana"] înseamnă „nota lui Ana”.

✏️ Exerciții propuse — Capitolul 6

  1. Dă 5 exemple din viața reală în care modelul de dicționar este potrivit.
  2. Pentru fiecare exemplu, identifică ce sunt cheile și ce sunt valorile.
  3. Explică de ce nu putem avea două chei egale într-un dicționar.

Capitolul 7: Clasa dict din Python

7.1. Crearea unui dicționar

# Dicționar vid
d1 = {}
d2 = dict()

# Cu valori
note = {"Ana": 9, "Bogdan": 10, "Ion": 7}
persoana = {
    "nume": "Alex",
    "varsta": 15,
    "oras": "București"
}

# Din liste de perechi
perechi = [("a", 1), ("b", 2), ("c", 3)]
d = dict(perechi)  # {"a": 1, "b": 2, "c": 3}

# Cu zip
chei = ["a", "b", "c"]
valori = [1, 2, 3]
d = dict(zip(chei, valori))  # {"a": 1, "b": 2, "c": 3}

# Dict comprehension
patrate = {x: x*x for x in range(1, 6)}
# {1: 1, 2: 4, 3: 9, 4: 16, 5: 25}

7.2. Acces și modificare

note = {"Ana": 9, "Bogdan": 10, "Ion": 7}

# Acces direct
print(note["Ana"])       # 9
# print(note["Maria"])   # EROARE — KeyError

# Acces sigur cu get()
print(note.get("Ana"))           # 9
print(note.get("Maria"))         # None — fără eroare
print(note.get("Maria", 0))      # 0 — valoare implicită

# Adăugare / modificare
note["Maria"] = 8       # adaugă cheie nouă
note["Ana"] = 10        # modifică valoarea existentă

# Verificare existență
print("Ana" in note)        # True
print("Xyz" in note)        # False
print("Ana" not in note)    # False

7.3. Metode ale clasei dict

note = {"Ana": 9, "Bogdan": 10, "Ion": 7}

# Obținere vizualizări (view objects)
print(list(note.keys()))    # ["Ana", "Bogdan", "Ion"]
print(list(note.values()))  # [9, 10, 7]
print(list(note.items()))   # [("Ana", 9), ("Bogdan", 10), ("Ion", 7)]

# Eliminare
x = note.pop("Ion")         # x = 7, cheia "Ion" este eliminată
# note.pop("Xyz")           # EROARE
y = note.pop("Xyz", None)   # y = None, fără eroare

ultimul = note.popitem()    # elimină și returnează (cheie, valoare) ultima adăugată

note.clear()                # golește dicționarul

# Actualizare cu alt dicționar
note = {"Ana": 9}
alte_note = {"Bogdan": 10, "Ana": 8}
note.update(alte_note)     # {"Ana": 8, "Bogdan": 10}  — actualizat!

# setdefault — adaugă cheia doar dacă nu există
note.setdefault("Cristina", 7)  # adaugă "Cristina": 7
note.setdefault("Ana", 5)       # NU modifică, "Ana" există deja

# fromkeys — creează dict cu chei date și valoare comună
d = dict.fromkeys(["a", "b", "c"], 0)   # {"a": 0, "b": 0, "c": 0}
d = dict.fromkeys(["a", "b", "c"])      # {"a": None, "b": None, "c": None}

# Copiere
note_copie = note.copy()

7.4. Iterarea unui dicționar

note = {"Ana": 9, "Bogdan": 10, "Ion": 7}

# Implicit — iterează pe CHEI
for k in note:
    print(k, "->", note[k])

# Explicit pe chei
for k in note.keys():
    print(k)

# Pe valori
for v in note.values():
    print(v)

# Pe perechi (recomandat)
for k, v in note.items():
    print(f"{k}: {v}")

7.5. Operații utile

note = {"Ana": 9, "Bogdan": 10, "Ion": 7, "Maria": 8}

# Suma valorilor
total = sum(note.values())      # 34

# Media
medie = sum(note.values()) / len(note)  # 8.5

# Elevul cu nota maximă
elev_max = max(note, key=note.get)    # "Bogdan"
# Alternativ:
elev_max = max(note.items(), key=lambda x: x[1])[0]

# Elevi cu notă >= 9
premiați = {k: v for k, v in note.items() if v >= 9}
# {"Ana": 9, "Bogdan": 10}

# Inversare chei-valori (atenție la duplicate!)
inversat = {v: k for k, v in note.items()}

7.6. Dicționare imbricate

elevi = {
    "Alex": {
        "varsta": 15,
        "clasa": "9A",
        "note": [9, 8, 10]
    },
    "Maria": {
        "varsta": 16,
        "clasa": "10B",
        "note": [10, 9, 9]
    }
}

# Acces
print(elevi["Alex"]["clasa"])        # "9A"
print(elevi["Alex"]["note"][0])      # 9

# Modificare
elevi["Alex"]["note"].append(7)

# Adăugare elev nou
elevi["Cristina"] = {
    "varsta": 15,
    "clasa": "9A",
    "note": []
}

# Afișare
for nume, date in elevi.items():
    print(f"{nume}: clasa {date['clasa']}, media {sum(date['note'])/len(date['note']):.2f}")

7.7. Unificarea dicționarelor (Python 3.9+)

stoc1 = {"pâine": 5, "lapte": 3}
stoc2 = {"ouă": 12, "lapte": 2}

# Operator | (unire)
total = stoc1 | stoc2      # {"pâine": 5, "lapte": 2, "ouă": 12}
# La cheile comune, rămâne valoarea din al doilea

# Operator |= (actualizează pe loc)
stoc1 |= stoc2

# Unpacking (funcționează și în versiuni mai vechi)
total = {**stoc1, **stoc2}

7.8. Probleme rezolvate

Problemă 1 — Frecvența caracterelor

def frecvente(s):
    f = {}
    for c in s:
        f[c] = f.get(c, 0) + 1
    return f

print(frecvente("informatica"))
# {'i': 2, 'n': 1, 'f': 1, 'o': 1, 'r': 1, 'm': 1, 'a': 2, 't': 1, 'c': 1}

Problemă 2 — Traducător simplu

dict_ro_en = {
    "mere": "apple",
    "pere": "pear",
    "struguri": "grapes",
    "căpșune": "strawberries"
}

def tradu(cuvant):
    return dict_ro_en.get(cuvant.lower(), "cuvânt necunoscut")

print(tradu("Mere"))    # "apple"
print(tradu("banane"))  # "cuvânt necunoscut"

Problemă 3 — Agendă telefonică

def agenda_interactiva():
    agenda = {}
    while True:
        print("\n1. Adaugă  2. Caută  3. Șterge  4. Afișează tot  0. Ieșire")
        op = input("Opțiune: ")
        if op == "1":
            nume = input("Nume: ")
            tel = input("Telefon: ")
            agenda[nume] = tel
        elif op == "2":
            nume = input("Nume: ")
            print(agenda.get(nume, "Nu există"))
        elif op == "3":
            nume = input("Nume: ")
            if agenda.pop(nume, None):
                print("Șters.")
            else:
                print("Nu există.")
        elif op == "4":
            for n, t in sorted(agenda.items()):
                print(f"{n}: {t}")
        elif op == "0":
            break

# agenda_interactiva()

Problemă 4 — Gestiune magazin

stoc = {
    "pâine":     {"pret": 3.50, "cantitate": 50},
    "lapte":     {"pret": 6.00, "cantitate": 30},
    "ouă":       {"pret": 1.20, "cantitate": 200},
}

def valoare_totala(stoc):
    return sum(d["pret"] * d["cantitate"] for d in stoc.values())

def actualizeaza_pret(stoc, produs, pret_nou):
    if produs in stoc:
        stoc[produs]["pret"] = pret_nou
    else:
        print(f"Produsul {produs} nu există.")

def produse_scumpe(stoc, prag):
    return [p for p, d in stoc.items() if d["pret"] > prag]

print(f"Valoare totală: {valoare_totala(stoc):.2f} lei")
print(f"Produse > 5 lei: {produse_scumpe(stoc, 5)}")

✏️ Exerciții propuse — Capitolul 7

  1. Citește un text și afișează cel mai frecvent cuvânt.
  2. Creează un catalog elev → listă de note. Adaugă funcții pentru adăugare notă, calcul medie, afișare top 3.
  3. Glosar informatic (termen → definiție) cu funcții de căutare, adăugare, export în fișier.
  4. Pentru două dicționare de stocuri, afișează: produsele din ambele, produsele doar într-unul, produsele cu preț diferit.
  5. Simulează un sistem de vot: dicționar candidat → număr de voturi. Afișează câștigătorul.

Capitolul 8: Modele conceptuale mixte

8.1. Ce sunt structurile mixte?

Datele din realitate sunt adesea complexe — nu se potrivesc într-o singură listă sau dicționar. Structurile mixte combină două sau mai multe modele conceptuale:

  • Listă de liste → matrice, table, grile.
  • Listă de dicționare → colecții de obiecte cu atribute.
  • Dicționar de liste → grupuri asociate cu un identificator.
  • Dicționar de dicționare → ierarhie pe mai multe niveluri.

8.2. Lista de liste (matrice)

O listă de liste este o colecție de liste, toate de aceeași lungime — practic, un tablou bidimensional.

# Matrice 3x4 — 3 rânduri, 4 coloane
matrice = [
    [1, 2, 3, 4],
    [5, 6, 7, 8],
    [9, 10, 11, 12]
]

# Acces: matrice[rând][coloană]
print(matrice[0][0])   # 1
print(matrice[2][3])   # 12
print(matrice[1])      # [5, 6, 7, 8] — un rând întreg

# Dimensiuni
n = len(matrice)         # 3 rânduri
m = len(matrice[0])      # 4 coloane

# Parcurgere
for i in range(n):
    for j in range(m):
        print(matrice[i][j], end=" ")
    print()

8.3. Crearea unei matrice

n, m = 3, 4

# CORECT
M = [[0] * m for _ in range(n)]

# GREȘIT! — toate rândurile se referă la ACEEAȘI listă!
# M = [[0] * m] * n

# Verificare
M[0][1] = 99
print(M)  # cu metoda corectă, doar primul rând are 99

8.4. Matrice în C++

#include <iostream>
#include <vector>
using namespace std;

int main() {
    // Tablou static
    int m[3][4] = {
        {1, 2, 3, 4},
        {5, 6, 7, 8},
        {9, 10, 11, 12}
    };

    // Vector dinamic
    vector<vector<int>> M(3, vector<int>(4, 0));
    M[1][2] = 99;

    // Parcurgere
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 4; j++) {
            cout << m[i][j] << " ";
        }
        cout << endl;
    }
    return 0;
}

8.5. Aplicații ale matricelor — vecinătăți

Matrice de adiacență (vecinătate): M[i][j] = 1 dacă există o legătură între i și j, altfel 0.

Exemplu: Relații de prietenie între 4 persoane.

# 4 persoane: Ana(0), Bogdan(1), Cristina(2), Daniel(3)
# Ana e prietenă cu Bogdan și Cristina
# Bogdan e prieten cu Ana și Daniel
# Cristina e prietenă cu Ana
# Daniel e prieten cu Bogdan
prietenii = [
    [0, 1, 1, 0],   # Ana
    [1, 0, 0, 1],   # Bogdan
    [1, 0, 0, 0],   # Cristina
    [0, 1, 0, 0]    # Daniel
]

# Câți prieteni are fiecare persoană?
nume = ["Ana", "Bogdan", "Cristina", "Daniel"]
for i, n in enumerate(nume):
    print(f"{n}: {sum(prietenii[i])} prieteni")

8.6. Parcurgerea vecinilor

Pentru elementul M[i][j], cei 4 vecini ortogonali sunt:

  • Sus: M[i-1][j]
  • Jos: M[i+1][j]
  • Stânga: M[i][j-1]
  • Dreapta: M[i][j+1]

Cei 8 vecini (plus diagonalele):

def vecini_elementului(M, i, j):
    n, m = len(M), len(M[0])
    directii = [(-1, -1), (-1, 0), (-1, 1),
                ( 0, -1),          ( 0, 1),
                ( 1, -1), ( 1, 0), ( 1, 1)]
    vecini = []
    for di, dj in directii:
        ni, nj = i + di, j + dj
        if 0 <= ni < n and 0 <= nj < m:
            vecini.append(M[ni][nj])
    return vecini

8.7. Bordarea (padding)

Bordarea = adăugarea unui „chenar” exterior de valori speciale (de obicei 0 sau -1) în jurul matricei, pentru a evita verificările 0 <= i < n.

# Matricea originală 3x3
M = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9]
]

# Bordată cu 0 — devine 5x5
bordata = [
    [0, 0, 0, 0, 0],
    [0, 1, 2, 3, 0],
    [0, 4, 5, 6, 0],
    [0, 7, 8, 9, 0],
    [0, 0, 0, 0, 0]
]

# Acum putem accesa M[i-1][j-1] fără să verificăm

8.8. Listă de dicționare

Ideală pentru colecții de obiecte cu mai multe atribute.

elevi = [
    {"nume": "Ana", "varsta": 15, "media": 9.5},
    {"nume": "Bogdan", "varsta": 16, "media": 8.7},
    {"nume": "Cristina", "varsta": 15, "media": 9.8},
    {"nume": "Daniel", "varsta": 17, "media": 7.2}
]

# Filtrare
buni = [e for e in elevi if e["media"] >= 9]

# Sortare după un câmp
elevi_sortati = sorted(elevi, key=lambda e: e["media"], reverse=True)

# Media generală
medie_generala = sum(e["media"] for e in elevi) / len(elevi)

# Găsire după criteriu
ana = next((e for e in elevi if e["nume"] == "Ana"), None)

8.9. Dicționar de liste

Util pentru grupare după un criteriu.

# Elevii grupați pe clase
clase = {
    "9A": ["Ana", "Bogdan", "Cristina"],
    "10B": ["Daniel", "Elena"],
    "11C": ["Florin", "Gabriela", "Horia"]
}

# Adăugare elev
clase["9A"].append("Ion")

# Număr total elevi
total = sum(len(l) for l in clase.values())

# Crearea automată a grupurilor
elevi_plati = [("Ana", "9A"), ("Bogdan", "9A"), ("Daniel", "10B")]
grupate = {}
for nume, clasa in elevi_plati:
    grupate.setdefault(clasa, []).append(nume)

8.10. Dicționar de dicționare

Pentru ierarhii complexe.

# Evidență magazin: produs → detalii
magazin = {
    "001": {"denumire": "Pâine", "pret": 3.50, "stoc": 50},
    "002": {"denumire": "Lapte", "pret": 6.00, "stoc": 30},
    "003": {"denumire": "Ouă", "pret": 0.80, "stoc": 200}
}

# Acces
print(magazin["001"]["denumire"])

# Modificare
magazin["001"]["pret"] = 4.00

# Adăugare produs nou
magazin["004"] = {"denumire": "Unt", "pret": 12.00, "stoc": 15}

# Valoare totală stoc
valoare = sum(p["pret"] * p["stoc"] for p in magazin.values())

8.11. Problemă rezolvată — tablă de șah cu regine

Tablă 8x8. 1 = regină albă, -1 = regină neagră, 0 = liber. Verificăm câte regine negre atacă o regină albă de la poziția dată.

def atacuri_regina(tabla, i, j):
    """Returnează nr. reginei inamice atacate de cea de la (i,j)."""
    culoare = tabla[i][j]
    if culoare == 0:
        return 0

    directii = [(-1, 0), (1, 0), (0, -1), (0, 1),   # ortogonale
                (-1, -1), (-1, 1), (1, -1), (1, 1)]  # diagonale

    atacuri = 0
    n = len(tabla)
    for di, dj in directii:
        ni, nj = i + di, j + dj
        while 0 <= ni < n and 0 <= nj < n:
            if tabla[ni][nj] != 0:
                if tabla[ni][nj] != culoare:  # culoare opusă
                    atacuri += 1
                break  # oprire la prima piesă întâlnită
            ni += di
            nj += dj
    return atacuri

✏️ Exerciții propuse — Capitolul 8

  1. Calculează suma elementelor unei matrice.
  2. Transpune o matrice (rândurile devin coloane).
  3. Verifică dacă o matrice este simetrică (M[i][j] == M[j][i]).
  4. Catalog: listă de elevi cu listă de note. Calculează media fiecărui elev.
  5. Găsește elementul maxim dintr-o matrice și poziția lui.
  6. Reprezintă un joc X și 0 — verifică cine a câștigat.
  7. Labirint (0 = zid, 1 = culoar). Verifică dacă există drum de la (0,0) la (n-1,m-1) — fără recursivitate deocamdată.

Capitolul 9: Subprograme recursive

9.1. Ce este recursivitatea?

Recursivitatea este tehnica prin care o funcție se apelează pe sine însăși pentru a rezolva o problemă mai mică, similară.

Analogii intuitive:

  • Oglinda în oglindă — o oglindă reflectă cealaltă, creând o infinitate de imagini.
  • Păpușile Matrioșka — o păpușă conține o alta mai mică, care conține una și mai mică…
  • Fractali — un model se repetă la scări din ce în ce mai mici.

9.2. Condiții pentru o funcție recursivă corectă

O funcție recursivă trebuie să aibă:

  1. Caz de bază (caz de oprire) — condiția în care NU se mai apelează recursiv.
  2. Pas recursiv — apel al funcției cu argumente care aduc problema mai aproape de cazul de bază.
  3. Progres către cazul de bază — fiecare apel trebuie să „micșoreze” problema.

Fără caz de oprire → recursivitate infinită → eroare RecursionError.

9.3. Primul exemplu — factorial

Definiție matematică recursivă:

0! = 1                  (cazul de bază)
n! = n × (n-1)!         (pasul recursiv)

Implementare Python

def factorial(n):
    if n == 0 or n == 1:       # caz de bază
        return 1
    return n * factorial(n - 1) # pas recursiv

print(factorial(5))  # 120

Implementare C++

int factorial(int n) {
    if (n == 0 || n == 1) return 1;
    return n * factorial(n - 1);
}

Cum funcționează? Stiva de apeluri

factorial(5)
  └─ 5 * factorial(4)
       └─ 4 * factorial(3)
            └─ 3 * factorial(2)
                 └─ 2 * factorial(1)
                      └─ return 1     ← caz de bază
                 ← return 2*1 = 2
            ← return 3*2 = 6
       ← return 4*6 = 24
  ← return 5*24 = 120

Observație cheie: fiecare apel rămâne „suspendat” pe stivă până când apelul pe care l-a făcut returnează.

9.4. Recursivitate directă vs. indirectă

  • Recursivitate directă: funcția se apelează DIRECT pe sine.
  • Recursivitate indirectă: funcția A apelează B, care apelează A.
# Directă
def A(n):
    if n == 0: return
    A(n - 1)

# Indirectă (A → B → A → B ...)
def par(n):
    if n == 0: return True
    return impar(n - 1)

def impar(n):
    if n == 0: return False
    return par(n - 1)

print(par(10))   # True
print(impar(7))  # True

9.5. Exemple clasice

a) Suma cifrelor

def suma_cifre(n):
    if n == 0:
        return 0
    return n % 10 + suma_cifre(n // 10)

print(suma_cifre(1234))  # 10

b) Suma elementelor unei liste

def suma_lista(lista, i=0):
    if i == len(lista):
        return 0
    return lista[i] + suma_lista(lista, i + 1)

# Sau, mai elegant:
def suma_lista_v2(lista):
    if not lista:
        return 0
    return lista[0] + suma_lista_v2(lista[1:])

print(suma_lista([1, 2, 3, 4, 5]))    # 15
print(suma_lista_v2([1, 2, 3, 4, 5])) # 15

c) CMMDC — algoritmul lui Euclid

def cmmdc(a, b):
    if b == 0:
        return a
    return cmmdc(b, a % b)

print(cmmdc(48, 18))  # 6

d) Ridicarea la putere (eficientă)

def putere(a, n):
    if n == 0:
        return 1
    if n % 2 == 0:                    # putere pară
        jumatate = putere(a, n // 2)
        return jumatate * jumatate
    return a * putere(a, n - 1)       # putere impară

print(putere(2, 10))  # 1024

Complexitate: O(log n) — mult mai rapid decât O(n) direct!

e) Fibonacci recursiv (INEFICIENT)

def fib(n):
    if n < 2:
        return n
    return fib(n - 1) + fib(n - 2)

print(fib(10))  # 55
# fib(30) durează 1-2 secunde
# fib(40) durează zeci de secunde!

De ce este lent? Pentru că reface aceleași calcule de mai multe ori:

fib(5)
├── fib(4)                         ← calculat 1 dată
│   ├── fib(3)                     ← calculat 2 ori
│   │   ├── fib(2)                 ← calculat 3 ori
│   │   │   ├── fib(1) → 1
│   │   │   └── fib(0) → 0
│   │   └── fib(1) → 1
│   └── fib(2)                     ← recalculat!
│       ├── fib(1) → 1
│       └── fib(0) → 0
└── fib(3)                         ← recalculat!
    ...

Soluție — memoizare:

def fib_memo(n, cache={}):
    if n in cache:
        return cache[n]
    if n < 2:
        return n
    cache[n] = fib_memo(n-1, cache) + fib_memo(n-2, cache)
    return cache[n]

print(fib_memo(100))  # 354224848179261915075 — instantaneu!

9.6. Recursiv vs. Iterativ

Orice problemă rezolvabilă recursiv se poate rezolva și iterativ. Alegerea depinde de:

Criteriu Recursiv Iterativ
Cod Mai scurt, elegant Mai lung
Memorie Mai multă (stiva de apeluri) Mai puțină
Viteză Puțin mai lent Puțin mai rapid
Lizibilitate Uneori mai bună Uneori mai bună

Exemplu — suma de la 1 la n:

# Recursiv
def suma_rec(n):
    if n == 0:
        return 0
    return n + suma_rec(n - 1)

# Iterativ
def suma_iter(n):
    s = 0
    for i in range(1, n + 1):
        s += i
    return s

# Formula directă O(1)
def suma_formula(n):
    return n * (n + 1) // 2

9.7. Combinări C(n, k)

Formula recursivă: C(n, k) = C(n-1, k-1) + C(n-1, k)

def comb(n, k):
    if k == 0 or k == n:
        return 1
    return comb(n-1, k-1) + comb(n-1, k)

print(comb(5, 2))  # 10

9.8. Șabloane recursive

Tiparul 1: linear recursion (descompunere simplă)

f(n) = ceva + f(n-1)

Exemple: factorial, suma, suma cifrelor.

Tiparul 2: binary recursion (dublă descompunere)

f(n) = f(n-1) + f(n-2)

Exemple: Fibonacci, combinări.

Tiparul 3: divide & conquer

f(n) = combine(f(n/2), f(n/2))

Exemple: căutare binară, sortare prin interclasare.

9.9. Avantaje și dezavantaje

Avantaje:

  • Cod mai clar pentru probleme de natură recursivă (ex: arbori, fractali).
  • Urmează direct definiția matematică.

Dezavantaje:

  • Stiva de apeluri consumă memorie.
  • Python are limită implicită (~1000 apeluri). Se poate mări: sys.setrecursionlimit(10000).
  • Poate fi lent fără memoizare.

✏️ Exerciții propuse — Capitolul 9

  1. Scrie recursiv o funcție care afișează cifrele unui număr de la stânga la dreapta.
  2. Verifică recursiv dacă un șir este palindrom.
  3. Calculează recursiv al n-lea termen din șirul lui Lucas: L(0)=2, L(1)=1, L(n)=L(n-1)+L(n-2).
  4. Calculează numărul de cifre ale unui număr.
  5. Afișează recursiv un triunghi de stele: n=4 →

```
*
**



```
1. Găsește recursiv maximul dintr-o listă.
1. Cifra maximă a unui număr (recursiv).


Capitolul 10: Metoda Divide et Impera

10.1. Ce este Divide et Impera?

„Divide et impera” (latină: „împarte și stăpânește”) este o strategie de rezolvare a problemelor prin:

  1. Divizare — împărțim problema în subprobleme mai mici de același tip.
  2. Rezolvare (conquer) — rezolvăm fiecare subproblemă (de obicei recursiv).
  3. Combinare — combinăm soluțiile subproblemelor într-o soluție finală.

Schema generală:

DIVIDE_IMPERA(problema):
    dacă problema este simplă:
        rezolvă direct
    altfel:
        împarte problema în subprobleme
        rezolvă fiecare subproblemă prin DIVIDE_IMPERA
        combină soluțiile

10.2. Când folosim D&I?

  • Când problema poate fi împărțită în subprobleme similare, independente.
  • Când combinarea soluțiilor este posibilă și eficientă.

Aplicații clasice:

  • Căutare binară.
  • Sortare prin interclasare (Merge Sort).
  • Calculul puterii (O(log n)).
  • Turnurile din Hanoi.
  • Fractali.
  • Maximum/minimum într-o listă.

10.3. Căutarea binară ca D&I

Divizare: împărțim lista în două jumătăți.
Rezolvare: căutăm recursiv doar în jumătatea potrivită.
Combinare: nu e nevoie — soluția vine direct din subproblema rezolvată.

(Implementarea este în Capitolul 1.)

10.4. Maximul dintr-o listă

def max_di(lista, st, dr):
    if st == dr:                       # caz de bază: un singur element
        return lista[st]
    mij = (st + dr) // 2
    max_stg = max_di(lista, st, mij)      # rezolvă stânga
    max_drp = max_di(lista, mij + 1, dr)  # rezolvă dreapta
    return max(max_stg, max_drp)          # combină

lista = [3, 7, 2, 9, 1, 8, 5]
print(max_di(lista, 0, len(lista) - 1))  # 9

Observație: pentru maximum, D&I nu este mai rapid decât parcurgerea liniară (ambele sunt O(n)), dar ilustrează bine tiparul.

10.5. Suma unei liste cu D&I

def suma_di(lista, st, dr):
    if st == dr:
        return lista[st]
    mij = (st + dr) // 2
    return suma_di(lista, st, mij) + suma_di(lista, mij + 1, dr)

print(suma_di([1, 2, 3, 4, 5], 0, 4))  # 15

10.6. Sortarea prin interclasare (Merge Sort)

Cel mai important algoritm D&I. Complexitate O(n log n) — mult mai bun decât O(n²) al sortărilor elementare.

Idee:

  1. Împarte lista în două jumătăți.
  2. Sortează recursiv fiecare jumătate.
  3. Interclasează jumătățile sortate.
def interclasare(a, b):
    rezultat = []
    i = j = 0
    while i < len(a) and j < len(b):
        if a[i] <= b[j]:
            rezultat.append(a[i]); i += 1
        else:
            rezultat.append(b[j]); j += 1
    rezultat += a[i:]
    rezultat += b[j:]
    return rezultat

def merge_sort(lista):
    if len(lista) <= 1:
        return lista
    mij = len(lista) // 2
    stanga = merge_sort(lista[:mij])
    dreapta = merge_sort(lista[mij:])
    return interclasare(stanga, dreapta)

print(merge_sort([5, 3, 8, 1, 9, 2, 7]))
# [1, 2, 3, 5, 7, 8, 9]

Vizualizare

                    [5, 3, 8, 1, 9, 2, 7]
                      /                \
                 [5, 3, 8]             [1, 9, 2, 7]
                  /     \                /       \
                [5]   [3, 8]          [1, 9]   [2, 7]
                       /  \            / \      /  \
                     [3]  [8]        [1] [9]  [2]  [7]
                       \  /           \ /      \ /
                       [3,8]          [1,9]    [2,7]
                    /       \            \     /
                [3, 5, 8]              [1, 2, 7, 9]
                        \              /
                       [1, 2, 3, 5, 7, 8, 9]

Merge Sort în C++

#include <vector>
using namespace std;

vector<int> interclasare(vector<int>& a, vector<int>& b) {
    vector<int> rez;
    int i = 0, j = 0;
    while (i < a.size() && j < b.size()) {
        if (a[i] <= b[j]) rez.push_back(a[i++]);
        else              rez.push_back(b[j++]);
    }
    while (i < a.size()) rez.push_back(a[i++]);
    while (j < b.size()) rez.push_back(b[j++]);
    return rez;
}

vector<int> merge_sort(vector<int> v) {
    if (v.size() <= 1) return v;
    int mij = v.size() / 2;
    vector<int> stg(v.begin(), v.begin() + mij);
    vector<int> drp(v.begin() + mij, v.end());
    stg = merge_sort(stg);
    drp = merge_sort(drp);
    return interclasare(stg, drp);
}

10.7. Turnurile din Hanoi

Regulă: avem 3 tije (A, B, C) și n discuri de mărimi diferite, toate pe tija A, ordonate (cel mai mare jos). Trebuie să mutăm toate discurile pe tija C, cu regulile:

  • Mutăm câte un disc o dată.
  • Un disc mai mare nu poate fi pus peste unul mai mic.

Soluție D&I:

  1. Mută n-1 discuri de pe A pe B (folosind C ca tijă auxiliară).
  2. Mută discul cel mare de pe A pe C.
  3. Mută n-1 discuri de pe B pe C (folosind A ca tijă auxiliară).
def hanoi(n, sursa, auxiliar, destinatie):
    if n == 1:
        print(f"Mută discul 1 de pe {sursa} pe {destinatie}")
        return
    hanoi(n - 1, sursa, destinatie, auxiliar)
    print(f"Mută discul {n} de pe {sursa} pe {destinatie}")
    hanoi(n - 1, auxiliar, sursa, destinatie)

hanoi(3, 'A', 'B', 'C')
# Mută discul 1 de pe A pe C
# Mută discul 2 de pe A pe B
# Mută discul 1 de pe C pe B
# Mută discul 3 de pe A pe C
# Mută discul 1 de pe B pe A
# Mută discul 2 de pe B pe C
# Mută discul 1 de pe A pe C

Număr de mutări: 2^n - 1. Pentru n=64 (legenda), sunt 18.446.744.073.709.551.615 mutări!

10.8. Ridicarea la putere — O(log n)

Am văzut în Capitolul 9. Este un exemplu tipic de D&I:

def putere(a, n):
    if n == 0: return 1
    if n % 2 == 0:
        x = putere(a, n // 2)
        return x * x          # combinare: x * x
    return a * putere(a, n - 1)

Exemplu: 2^10 = (2^5)^2 = ((2^2)^2 · 2)^2 = ... — O(log n) operații.

10.9. Covorul lui Sierpinski

Un fractal generat recursiv.

import turtle

def sierpinski(t, l, nivel):
    if nivel == 0:
        for _ in range(3):
            t.forward(l)
            t.left(120)
        return
    sierpinski(t, l / 2, nivel - 1)
    t.forward(l / 2)
    sierpinski(t, l / 2, nivel - 1)
    t.backward(l / 2)
    t.left(60)
    t.forward(l / 2)
    t.right(60)
    sierpinski(t, l / 2, nivel - 1)
    t.left(60)
    t.backward(l / 2)
    t.right(60)

# t = turtle.Turtle()
# sierpinski(t, 200, 4)
# turtle.done()

10.10. Complexitate D&I — Master Theorem (informal)

Pentru recurențe de forma:

T(n) = a · T(n/b) + O(n^d)
Condiție Complexitate
a > b^d O(n^(log_b a))
a = b^d O(n^d · log n)
a < b^d O(n^d)

Exemple:

  • Căutarea binară: T(n) = T(n/2) + O(1) → O(log n).
  • Merge Sort: T(n) = 2T(n/2) + O(n) → O(n log n).
  • Maximum D&I: T(n) = 2T(n/2) + O(1) → O(n).

✏️ Exerciții propuse — Capitolul 10

  1. Implementează D&I pentru a găsi minimul și maximul simultan dintr-o listă.
  2. Calculează 2^n folosind recursia eficientă (O(log n)).
  3. Implementează căutarea binară recursiv.
  4. Scrie Merge Sort pentru o listă de șiruri de caractere.
  5. Problema inversiunilor: câte perechi (i, j) cu i < j au a[i] > a[j]? (D&I, O(n log n))
  6. Modifică Hanoi să numere mutările în loc să le afișeze.

Capitolul 11: Metoda Greedy

11.1. Ce este Greedy?

Greedy („lacom”) este o strategie de rezolvare a problemelor de optimizare, care la fiecare pas face alegerea care pare cea mai bună pe moment (optimul local), sperând că suma acestor alegeri duce la un optim global.

Principii:

  • Decizia se ia pas cu pas.
  • Nu se revine asupra alegerilor anterioare.
  • La fiecare pas, se alege „cea mai bună” opțiune disponibilă.

Important: Greedy NU garantează întotdeauna soluția optimă! Trebuie demonstrat că funcționează pentru problema dată.

11.2. Când folosim Greedy?

  • Probleme de optimizare (maxim/minim).
  • Când există o strategie de alegere locală care duce la optim global.
  • Când sortarea poate ajuta la stabilirea ordinii de procesare.

Avantaje: simplu, rapid (de obicei O(n log n) din cauza sortării).
Dezavantaj: nu funcționează pentru toate problemele.

11.3. Exemplu clasic — Selectarea activităților

Problemă: Ai o listă de activități, fiecare cu un interval [start, sfârșit). Găsește numărul maxim de activități care se pot executa fără suprapuneri.

Exemplu:

Activități: A(1-4), B(3-5), C(0-6), D(5-7), E(3-9), F(5-9), G(6-10), H(8-11)

Strategia Greedy (corectă): sortează activitățile după momentul de sfârșit, apoi ia fiecare activitate care începe după ce s-a terminat ultima aleasă.

def selectie_activitati(activitati):
    # activitati = listă de tuple (start, sfarsit)
    # Sortare după sfârșit
    activitati_sortate = sorted(activitati, key=lambda x: x[1])

    selectate = [activitati_sortate[0]]
    ultim_sfarsit = activitati_sortate[0][1]

    for start, sfarsit in activitati_sortate[1:]:
        if start >= ultim_sfarsit:
            selectate.append((start, sfarsit))
            ultim_sfarsit = sfarsit
    return selectate

activitati = [(1,4), (3,5), (0,6), (5,7), (3,9), (5,9), (6,10), (8,11)]
print(selectie_activitati(activitati))
# [(1, 4), (5, 7), (8, 11)]  — 3 activități

De ce după sfârșit și nu după început? Pentru că, terminând cât mai devreme, lăsăm cât mai mult timp pentru următoarele.

11.4. Problema restului (canonică)

Problemă: Ai monede de 1, 5, 10, 25, 50, 100 bani. Să plătești suma S cu minim de monede.

Strategia Greedy: la fiecare pas, ia moneda cea mai mare ≤ restul de plată.

def rest_minim(S, monede):
    # Sortare descrescătoare
    monede = sorted(monede, reverse=True)
    rezultat = []
    for m in monede:
        while S >= m:
            rezultat.append(m)
            S -= m
    return rezultat

print(rest_minim(63, [1, 5, 10, 25, 50]))  # [50, 10, 1, 1, 1]

11.5. ⚠️ Când Greedy NU funcționează

Contraexemplu: monede [1, 3, 4], sumă 6.

Greedy face: 6 = 4 + 1 + 13 monede.
Optim: 6 = 3 + 32 monede.

# Greedy dă răspuns suboptimal pentru sisteme non-canonice
print(rest_minim(6, [4, 3, 1]))  # [4, 1, 1] — 3 monede, nu optim!

Concluzie: Greedy funcționează pentru monedele standard (1, 5, 10, 25, 50, 100), dar nu universal. Pentru cazul general, se folosește programarea dinamică.

11.6. Problema rucsacului — varianta continuă

Problemă: Ai un rucsac cu capacitate W. Ai n obiecte, fiecare cu masa m[i] și valoarea v[i]. Poți lua fracțiuni din obiecte. Maximizează valoarea totală.

Strategia Greedy: sortează obiectele după raportul valoare/masă descrescător. Ia obiectele în ordine până se umple rucsacul.

def rucsac_continuu(obiecte, W):
    # obiecte = listă de tuple (masa, valoare)
    # Sortare descrescător după raportul valoare/masă
    obiecte_sortate = sorted(obiecte, key=lambda o: o[1]/o[0], reverse=True)

    valoare_totala = 0
    selectate = []
    for masa, valoare in obiecte_sortate:
        if W >= masa:
            selectate.append((masa, valoare, 1.0))  # ia tot
            valoare_totala += valoare
            W -= masa
        elif W > 0:
            fractiune = W / masa
            selectate.append((masa, valoare, fractiune))
            valoare_totala += valoare * fractiune
            W = 0
            break
    return valoare_totala, selectate

obiecte = [(10, 60), (20, 100), (30, 120)]  # (masa, valoare)
W = 50
print(rucsac_continuu(obiecte, W))
# (240.0, [(10, 60, 1.0), (20, 100, 1.0), (30, 120, 2/3)])

⚠️ Pentru rucsacul discret (nu se pot lua fracțiuni), Greedy NU funcționează — trebuie programare dinamică.

11.7. Suma maximă — produse de perechi

Problemă: Ai două mulțimi A și B cu numere. Construiește n perechi (a, b) cu a∈A, b∈B (distincte în A, distincte în B), astfel încât suma Σ a·b să fie maximă.

Strategia Greedy: sortează A crescător și B crescător. Împerechează cel mai mare din A cu cel mai mare din B, al doilea cu al doilea etc.

def suma_max_perechi(A, B, n):
    A = sorted(A, reverse=True)[:n]
    B = sorted(B, reverse=True)[:n]
    return sum(a * b for a, b in zip(A, B))

print(suma_max_perechi([1, 3, 5], [2, 4, 6], 3))  # 5·6 + 3·4 + 1·2 = 44

11.8. Problema fixării scândurilor

Problemă: Ai n scânduri de lungimi date. Vrei să le fixezi pe un perete cu minim număr de cuie, știind că un cui poate fixa o scândură într-un punct și că scândurile se pot suprapune.

Strategia Greedy: cuiul să fie la dreapta scândurii curente — poate fixa și orice scândură următoare care începe înainte de această poziție.

11.9. Structura generală a unui algoritm Greedy

def greedy(elemente):
    # 1. Sortare după criteriu
    elemente_sortate = sorted(elemente, key=criteriu)

    solutie = []
    for e in elemente_sortate:
        if este_fezabil(solutie, e):
            solutie.append(e)
    return solutie

11.10. Divide et Impera vs. Greedy

Aspect Divide et Impera Greedy
Strategie Împarte, rezolvă, combină Alege local optimul
Probleme tipice Sortare, căutare Optimizare
Soluție optimă? Da, întotdeauna Nu întotdeauna
Complexitate Adesea O(n log n) Adesea O(n log n)
Revenire la decizii Nu e nevoie Nu se întoarce

✏️ Exerciții propuse — Capitolul 11

  1. Ai n oferte (preț, reducere %). Alege cele cu cea mai mare reducere absolută.
  2. Într-o stație, trenurile sosesc și pleacă la ore date. Câte peroane sunt necesare minim?
  3. Problema spectacolelor: într-o sală, programează maximum de spectacole fără suprapuneri.
  4. Dă rest cu monede europene (1, 2, 5, 10, 20, 50 cenți, 1, 2 €).
  5. Ai n sarcini cu durate. Vrei să le execuți în ordinea care minimizează timpul mediu de așteptare.
  6. Găsește minimul de cuie pentru fixarea unor scânduri cu intervale date.

Anexe

Anexa A — Cheat sheet Python: set, str, dict

# ============ SET ============
s = set()              # mulțime vidă
s = {1, 2, 3}
s.add(4)
s.remove(2)            # eroare dacă lipsește
s.discard(99)          # nu dă eroare
A | B    # reuniune
A & B    # intersecție
A - B    # diferență
A ^ B    # diferență simetrică
x in A

# ============ STR ============
s = "Alex"
len(s)                  # 4
s[0], s[-1]             # 'A', 'x'
s[1:3]                  # 'le'
s.upper(), s.lower()
s.strip(), s.split()
"-".join(["a","b","c"])
s.replace("a", "b")
s.count("a")
s.find("x")             # -1 dacă nu există
s.isalpha(), s.isdigit()

# ============ DICT ============
d = {"a": 1, "b": 2}
d["c"] = 3
d.get("z", 0)           # 0 (default)
d.pop("a")
del d["b"]
"a" in d
for k, v in d.items(): ...
for k in d.keys(): ...
for v in d.values(): ...
d1 | d2                 # Python 3.9+

Anexa B — Cheat sheet C++: set, string, map

// ============ set ============
#include <set>
set<int> s = {3, 1, 4, 1, 5};   // {1, 3, 4, 5} — SORTAT
s.insert(2);
s.erase(3);
if (s.count(4)) ...
for (int x : s) ...

// ============ string ============
#include <string>
string s = "Alex";
s.length(), s.size();
s[0], s.back();
s.substr(1, 2);                 // "le"
s.find("ex");                   // 2, sau string::npos
s += " B";
stoi("42"), to_string(42);

// ============ map (dict) ============
#include <map>
map<string, int> m;             // ORDONAT după cheie
m["Ana"] = 9;
if (m.count("Ana")) ...
m.erase("Ana");
for (auto& [k, v] : m) ...      // C++17

Anexa C — Complexități comune

Algoritm Complexitate
Căutare secvențială O(n)
Căutare binară O(log n)
Sortare bulelor / selecție O(n²)
Sortare prin interclasare O(n log n)
Sortare listă de frecvențe O(n + k)
Interclasare O(n + m)
Factorial recursiv O(n)
Fibonacci naiv O(2ⁿ)
Fibonacci cu memoizare O(n)
Ridicare la putere rapidă O(log n)
Turnurile din Hanoi O(2ⁿ) mutări
Operații set, dict O(1) mediu
Operații list (append, index) O(1)
Inserție/ștergere la mijloc în listă O(n)

Anexa D — Probleme recomandate pentru practică

Nivel începător:

  1. Numără vocalele distincte dintr-un text.
  2. Frecvența literelor.
  3. Anagrame.
  4. Palindroame (inclusiv cu caractere speciale).

Nivel intermediar:
5. Gestiune bibliotecă cu dicționare.
6. Procesator de statistici pe elevi (listă de dicționare).
7. Validator de formulare.
8. Triaj cozi (Greedy).

Nivel avansat:
9. Merge sort iterativ (nerecursiv).
10. Numărul de inversiuni (D&I).
11. Cel mai lung subșir comun (pregătire pt. programare dinamică).
12. Problema N-Reginelor (backtracking — pregătire pt. clasa a XI-a).

Anexa E — Resurse online recomandate

Vizualizări interactive

Exerciții

Documentație

Anexa F — Glosar de termeni noi (clasa a X-a)

Termen Definiție
Mulțime (set) Colecție neordonată de elemente unice.
Hash Funcție care transformă o cheie într-un index rapid.
Dicționar (dict/map) Colecție de perechi cheie-valoare.
Cheie Identificator unic într-un dicționar.
Imutabil Care nu poate fi modificat după creare (ex: str, tuple).
Slicing Extragerea unei secvențe prin [start:stop:step].
ASCII / Unicode Standard de codificare a caracterelor.
Matrice Tablou bidimensional (listă de liste).
Bordare (padding) Adăugarea de rând/coloană suplimentare pentru ușurința parcurgerii.
Recursivitate Tehnica prin care o funcție se apelează pe sine.
Caz de bază Condiția de oprire a recursivității.
Memoizare Tehnica de a cache-ui rezultate pentru a evita recalculul.
Stiva de apeluri (call stack) Structura care păstrează apelurile recursive active.
Divide et Impera Strategie: împarte, rezolvă, combină.
Greedy Strategie: alege local optimul la fiecare pas.
Căutare binară Algoritm O(log n) pe liste sortate.
Interclasare (merge) Combinare a două liste sortate într-una sortată.
Merge Sort Algoritm de sortare O(n log n) bazat pe D&I.
Complexitate logaritmică O(log n) — foarte eficientă.
Fractal Figură geometrică autosimilară la scări diferite.

Anexa G — Pregătire pentru clasa a XI-a

Pentru anul viitor (specializarea mate-info) urmează:

  • Backtracking (generare permutări, combinări, N-regine).
  • Programare dinamică (problema rucsacului discret, subsecvența crescătoare maximă).
  • Grafuri (reprezentări, BFS, DFS, drumuri minime).
  • Arbori (binar, de căutare).
  • Programare orientată pe obiecte aprofundată.

Pregătire utilă acum:

  • Rezolvă probleme cu Greedy și D&I (gândire algoritmică).
  • Lucrează cu liste de dicționare și matrice (baza pentru reprezentarea grafurilor).
  • Stăpânește bine recursivitatea (esențială pentru backtracking și DFS).

🎓 Încheiere

Ai parcurs integral materia de clasa a X-a! Reține în special:

Modele conceptuale noi: mulțime (neliniară), dicționar (asociativ), structuri mixte.
Clasele Python: set, str, dict — cu toate operatorii și metodele.
Algoritmi pe liste sortate: căutare binară (O(log n)) și interclasare (O(n+m)).
Subprograme recursive — o nouă paradigmă de gândire.
Două strategii majore: Divide et Impera și Greedy.

Cheia succesului în informatică:

  1. Practică zilnică — un set de 2-3 probleme pe zi schimbă totul.
  2. Înțelege, nu memoriza — identifică tiparele, nu pașii exacti.
  3. Desenează — schițe, diagrame, arbori de recursie — toate ajută.
  4. Explică — dacă poți explica un algoritm unui coleg, l-ai înțeles.
  5. Debugging — erorile sunt profesorii tăi. Nu le evita, analizează-le.

„Recursivitatea este înțeleasă de cei care înțeleg recursivitatea.”


Document generat în conformitate cu Programa școlară pentru disciplina Informatică, clasa a X-a, curriculum de specialitate, Ordin MEC 4.350/2025, Anexa 44.

Pe această pagină