🎯 Obiective
1. Recapitulare: ce știm deja?
În părțile anterioare am învățat să lucrăm cu variabile, condiții și repetări, apoi cu șiruri, liste și matrice. Am învățat funcții, recursivitate, programare dinamică și grafuri.
2. Ce înseamnă optimizare?
Într-o problemă de optimizare există mai multe soluții posibile și vrem una cât mai bună.
- minimizare — alegem o valoare cât mai mică;
- maximizare — alegem o valoare cât mai mare.
Exemplu: avem activități și vrem să alegem cât mai multe fără să se suprapună.
3. Ideea algoritmului Greedy
Greedy înseamnă să construim soluția pas cu pas, alegând la fiecare pas o variantă care pare cea mai bună în acel moment.
O schemă generală este:
solutie = ...
pregatim datele
cat timp mai avem alegeri:
alegem varianta cea mai buna local
daca alegerea este permisa:
o pastram4. Greedy cu sortare
Multe probleme Greedy devin simple după ce sortăm elementele după un criteriu.
a.sort()
for x in a:
# analizăm x
...Sortarea trebuie făcută după criteriul care susține strategia. Nu există o singură ordine corectă pentru toate problemele.
5. Problema activităților
Avem activități cu moment de început și sfârșit. Vrem să alegem cât mai multe activități care nu se suprapun.
Strategia Greedy corectă: alegem activitatea care se termină cel mai devreme, apoi căutăm următoarea activitate care începe după terminarea celei alese.
activitati.sort(key=lambda x: x[1])
ultima = -1
numar = 0
for activitate in activitati:
inceput = activitate[0]
sfarsit = activitate[1]
if inceput >= ultima:
numar = numar + 1
ultima = sfarsit
print(numar)lambda doar pentru criteriul de sortare. Dacă acest concept nu a fost încă studiat în detaliu, el trebuie tratat ca o extensie a capitolului despre funcții, nu memorat mecanic.6. Problema monedelor
Avem valori de monede și vrem să obținem o sumă folosind cât mai puține monede. Strategia „aleg mereu cea mai mare monedă posibilă” funcționează pentru anumite sisteme de monede, dar nu pentru orice sistem.
monede = [10, 5, 1]
suma = 28
numar = 0
for moneda in monede:
while suma >= moneda:
suma = suma - moneda
numar = numar + 1
print(numar)[1, 3, 4] și suma 6, Greedy alege 4 + 1 + 1, adică 3 monede, dar soluția optimă este 3 + 3, adică 2. Pentru astfel de cazuri este potrivită programarea dinamică.7. Greedy și programarea dinamică
| Greedy | Programare dinamică |
|---|---|
| Alege imediat o variantă. | Compară soluții ale subproblemelor. |
| De obicei este simplu și rapid. | Poate necesita mai multă memorie. |
| Are nevoie de o strategie corectă. | Folosește relații între subprobleme. |
| Nu este corect în orice problemă. | Poate rezolva probleme în care Greedy eșuează. |
8. Greedy în grafuri
În partea 7 am introdus grafurile și drumurile. Unele probleme de grafuri au soluții Greedy.
Dijkstra
Pentru muchii cu costuri nenegative, Dijkstra alege repetat vârful nevizitat cu distanța cunoscută cea mai mică și încearcă să îmbunătățească distanțele vecinilor.
distanta[start] = 0
repetam:
alegem v care are distanta minima
il marcam
pentru fiecare vecin u al lui v:
nou = distanta[v] + cost(v, u)
daca nou < distanta[u]:
distanta[u] = nou9. Arbore de acoperire de cost minim
Un graf neorientat conex poate avea un arbore care leagă toate vârfurile fără cicluri. Dacă vrem cost total minim, căutăm un arbore de acoperire minim.
Kruskal — ideea
- Sortăm muchiile crescător după cost.
- Luăm muchia cea mai ieftină care nu formează un ciclu.
- Continuăm până când avem suficiente muchii pentru a lega toate vârfurile.
10. Cum verificăm o strategie Greedy?
- Definim clar ce alegere facem.
- Arătăm că alegerea este permisă.
- Explicăm de ce alegerea nu ne strică posibilitatea de a obține o soluție optimă.
- Repetăm argumentul pentru pașii următori.
Nu este suficient să spunem „pare cea mai bună”. Trebuie să existe un motiv matematic sau algoritmic.
11. Exerciții rezolvate
Exercițiul 1 — Alegerea activităților
Activități: (1,3), (2,5), (4,7), (6,8), (8,9). Alegem după sfârșitul cel mai mic.
activitati = [(1, 3), (2, 5), (4, 7), (6, 8), (8, 9)]
activitati.sort(key=lambda x: x[1])
ultima = -1
numar = 0
for a in activitati:
if a[0] >= ultima:
numar = numar + 1
ultima = a[1]
print(numar)Se aleg (1,3), (4,7), (8,9), deci răspunsul este 3.
Exercițiul 2 — Monede
Monedele sunt 25, 10, 5, 1. Pentru suma 63, alegem de fiecare dată cea mai mare monedă posibilă.
monede = [25, 10, 5, 1]
suma = 63
numar = 0
for moneda in monede:
while suma >= moneda:
suma = suma - moneda
numar = numar + 1
print(numar)Se folosesc 2×25, 1×10, 1×1, 1×1, 1×1: în total 6 monede.
Exercițiul 3 — De ce Greedy poate eșua?
Pentru [1,3,4] și suma 6, Greedy alege 4, apoi 1 și 1. Obținem 3 monede. Dar 3 + 3 folosește doar 2. Concluzia: strategia Greedy nu este universală.
Exercițiul 4 — Alegerea maximului dintre variante
Avem valori [7, 2, 9, 4, 6]. Dacă problema cere doar valoarea maximă, nu avem nevoie de Greedy: folosim parcurgerea listei.
a = [7, 2, 9, 4, 6]
maxim = a[0]
for x in a:
if x > maxim:
maxim = x
print(maxim)Ideea importantă este să nu folosim o tehnică complicată când o metodă simplă este suficientă.
Exercițiul 5 — Ordinea contează
Pentru o problemă în care trebuie să alegem intervale după momentul de sfârșit, sortarea după început poate produce o alegere greșită. Criteriul de sortare trebuie justificat de strategie.
Exercițiul 6 — Alegerea unui drum
Într-un graf cu costuri nenegative, Dijkstra menține pentru fiecare vârf cea mai bună distanță cunoscută și actualizează vecinii după regula:
nou = distanta[v] + costDacă nou este mai mică decât distanța actuală, o înlocuim.
12. Exerciții propuse
13. Greșeli frecvente
- A presupune că orice problemă de minimizare este Greedy.
- A alege „cea mai mare” sau „cea mai mică” valoare fără justificare.
- A sorta după criteriul greșit.
- A confunda o soluție validă cu o soluție optimă.
- A folosi Dijkstra când există muchii cu cost negativ.
- A trece direct la cod fără să testezi strategia pe exemple mici.
14. Checklist final
| Pot... | ✓ |
|---|---|
| explica ce este o problemă de optimizare? | ☐ |
| explica ideea Greedy? | ☐ |
| alege și justifica un criteriu de sortare? | ☐ |
| rezolva problema activităților? | ☐ |
| explica când Greedy eșuează? | ☐ |
| diferenția Greedy de programarea dinamică? | ☐ |
| explica ideea Dijkstra? | ☐ |
| explica ideea lui Kruskal? | ☐ |
| justifica o strategie, nu doar să o memorez? | ☐ |
15. Legătura cu următoarea parte
După Greedy, următorul pas natural este aprofundarea algoritmilor avansați: complexitate, tehnici de căutare și optimizare, structuri de date mai eficiente și implementări mai rapide ale algoritmilor pe grafuri.