1. Ce este backtracking?
Mergeti pe un culoar. Ajungeti la un zid. Va intoarceti la ultima intersectie si luati alt culoar. Repetati pana gasiti iesirea sau epuizati toate drumurile. Aceasta intoarcere controlata este esenta backtracking-ului.
Important: nu intrati niciodata de doua ori pe acelasi culoar din acelasi punct — acesta este rolul marcajului (used[]).
2. Spatiul solutiilor si arborele de cautare
( ) <-- radacina: solutie vida
/ [1] [2] <-- nivel 1: prima pozitie
| |
[1,2] [2,1] <-- nivel 2: solutii complete
SOLUTIE SOLUTIE
Pentru n=3: 3 ramuri la radacina, 2 la nivelul 1, 1 la nivelul 2. Total frunze = 3! = 6 solutii.
3. Schema generala in Python
# Schema generala backtracking in Python # k = pozitia curenta (0..n-1); n = lungimea solutiei # sol = lista solutiei partial construite # used = marcaj: used[v]=True daca v a fost deja folosit def backtracking(k, n, sol, used): # (a) Conditia de solutie completa if k == n: print(sol[:]) return # (b) Iterare valori candidate pentru pozitia k for v in range(1, n + 1): # (c) Conditia de continuare: v este valid pe pozitia k? if not used[v]: # Alegere: plaseaza v pe pozitia k sol[k] = v used[v] = True # Recursie: rezolva pozitia k+1 backtracking(k + 1, n, sol, used) # REVENIRE (backtrack): anuleaza alegerea sol[k] = 0 used[v] = False
n = 3 sol = [0] * n used = [False] * (n + 1) print("Permutarile lui {1, 2, 3}:") backtracking(0, n, sol, used)
Permutarile lui {1, 2, 3}:
[1, 2, 3]
[1, 3, 2]
[2, 1, 3]
[2, 3, 1]
[3, 1, 2]
[3, 2, 1]
4. Conditia de continuare vs. conditia de solutie completa
- Conditia de solutie completa (
k == n): toate pozitiile au primit valori; solutia este gata de procesat. - Conditia de continuare / validare partiala (
conditie(k, v)): valoarea v poate fi plasata pe pozitia k fara a viola restrictiile problemei. Daca esueaza, nu exploram acel subarbore (pruning).
Conditia de continuare: k == 0 or sol[k-1] != v (nu repetam valoarea precedenta).
def backtracking(k, n, sol): if k == n: print(sol[:]) return for v in [0, 1]: # conditie continuare: diferit de precedentul if k == 0 or sol[k-1] != v: sol[k] = v backtracking(k + 1, n, sol) sol[k] = -1 n = 3 sol = [-1] * n print("Siruri din {0,1} fara cifre consecutive identice, lungime 3:") backtracking(0, n, sol)
Siruri din {0,1} fara cifre consecutive identice, lungime 3:
[0, 1, 0]
[1, 0, 1]
Fara conditia de continuare: 2^3 = 8 siruri de explorat. Cu conditia: arborele este taiat precoce si sunt gasite direct cele 2 siruri valide.
5. Urmarirea executiei pas cu pas
Traseul backtracking pentru permutarile lui {1,2}:
bt(k=0) sol=[]
bt(k=1) sol=[1]
bt(k=2) sol=[1, 2]
-> SOLUTIE: [1, 2]
bt(k=1) sol=[2]
bt(k=2) sol=[2, 1]
-> SOLUTIE: [2, 1]
Total apeluri recursive: 5
| Apel nr. | k | v incercat | sol partial | Actiune |
|---|---|---|---|---|
| 1 | 0 | 1 | [ ] | alege v=1, apel recursiv |
| 2 | 1 | 2 | [1] | alege v=2, apel recursiv |
| 3 | 2 | - | [1, 2] | SOLUTIE, return, revenire |
| 4 | 1 | - | [1] | v=2 epuizat, return, revenire la k=0 |
| 5 | 0 | 2 | [ ] | alege v=2, apel recursiv |
| 6 | 1 | 1 | [2] | alege v=1, apel recursiv |
| 7 | 2 | - | [2, 1] | SOLUTIE, return, revenire |
6. Schema in C++ EXCLUSIV INTENSIV
La profilul intensiv informatica, implementam aceeasi schema in C++ cu tablouri globale (conventie standard la concursuri). Diferente fata de Python: tipuri declarate explicit, tablouri cu dimensiune maxima constanta, functia main() obligatorie.
#include <iostream> using namespace std; const int NMAX = 10; int n = 3; int sol[NMAX]; bool used[NMAX]; bool conditie(int k, int v) { return !used[v]; // v nu a mai fost folosit } bool esteSolutieCompleta(int k) { return k == n; } void afiseazaSolutia() { for (int i = 0; i < n; i++) cout << sol[i] << (i < n - 1 ? " " : " "); } void backtracking(int k) { if (esteSolutieCompleta(k)) { afiseazaSolutia(); return; } for (int v = 1; v <= n; v++) { if (conditie(k, v)) { sol[k] = v; used[v] = true; backtracking(k + 1); sol[k] = 0; used[v] = false; } } } int main() { cout << "Permutarile lui {1, 2, 3}:" << endl; backtracking(0); return 0; }
Permutarile lui {1, 2, 3}:
1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1
- C++:
bool used[NMAX]cu dimensiune maxima; Python:used = [False] * (n+1)alocat dinamic. - C++:
conditie()siesteSolutieCompleta()ca functii separate — claritate arhitecturala, recomandat la olimpiada. - C++: variabilele
n,sol,usedsunt globale — conventie uzuala la concursuri. - Ambele variante produc acelasi rezultat; structura algoritmului este identica.