⚠️ Evaluare · BAC Backtracking

Greșeli Frecvente la BAC

Cele mai comune erori când vine vorba de probleme de Backtracking la examenul de Bacalaureat — cu exemple de cod greșit și corectat

Greșelile #1–8 ordonate după frecvență
1
INIT(k) inițializează cu 1 în loc de 0
INIT

INIT(k) trebuie să seteze x[k] cu valoarea de dinaintea primului element valid al domeniului — astfel, la prima incrementare din BCK() (x[k]++), x[k] devine exact primul element posibil. Dacă domeniul este {1, 2, …, n}, atunci INIT trebuie să pună x[k] = 0, nu x[k] = 1 — altfel prima valoare testată va fi 2, nu 1, și prima permutare/soluție va fi ratată.

✗ Greșit
int INIT(int k) {
    x[k] = 1;  // prima valoare
              // deja consumată
}
✓ Corect
int INIT(int k) {
    x[k] = 0;  // înaintea domeniului
              // x[k]++ → devine 1
}
💡Regulă: INIT(k) pune x[k] = valmin − 1, unde valmin este prima valoare validă din domeniu. Pentru domenii {1..n} → x[k]=0. Pentru {0..n−1} → x[k]=−1.
2
EXISTA(k) folosește condiție greșită (< vs <=)
EXISTA

EXISTA(k) trebuie să returneze 1 atâta vreme cât mai există valori neexplorate. Dacă domeniul este {1, 2, …, n}, condiția corectă este x[k] < n — mai există cel puțin o valoare (n) de testat. Scriind x[k] <= n, algoritmul va permite și x[k]=n+1 (ieșit din domeniu), generând valori invalide.

✗ Greșit — depășește domeniul
int EXISTA(int k) {
    // domeniu {1..n}
    if (x[k] <= n) return 1;
    else return 0;
}
✓ Corect
int EXISTA(int k) {
    // domeniu {1..n}
    if (x[k] < n) return 1;
    else return 0;
}
📌Logică: EXISTA verifică dacă mai există valori de incrementat. Dacă x[k] este deja la ultima valoare validă (n), nu mai există nimic de testat → return 0.
3
VALID(k) compară și cu nivelul curent (i = 1..k în loc de 1..k−1)
VALID

VALID(k) verifică compatibilitatea valorii x[k] cu valorile anterior plasate la pozițiile 1..k−1. Dacă bucla merge până la k inclusiv, va compara x[k] cu el însuși — ceea ce va returna mereu 0 (fals) pentru orice problemă de unicitate, blocând complet algoritmul.

✗ Greșit — se compară cu sine însuși
int VALID(int k) {
    for (int i=1; i<=k; i++)
        if (x[i]==x[k]) return 0;
    return 1;
}
✓ Corect — comparare cu 1..k−1
int VALID(int k) {
    for (int i=1; i<k; i++)
        if (x[i]==x[k]) return 0;
    return 1;
}
⚠️Același tip de eroare apare și în problema N-Regine la verificarea diagonalelor: abs(x[i]-x[k]) == abs(i-k) — bucla trebuie să itereze i de la 1 la k−1.
4
VALID pentru N-Regine uită verificarea diagonalelor
VALID

La N-Regine, VALID trebuie să verifice trei condiții: coloana (x[i] ≠ x[k]), diagonala principală (|x[i] − x[k]| ≠ |i − k|) și antidiagonala (aceeași condiție prin valoarea absolută — ea acoperă ambele diagonale). Mulți elevi verifică doar coloana, generând soluții invalide.

✗ Greșit — lipsesc diagonalele
int VALID(int k) {
    for(int i=1; i<k; i++)
        if(x[i]==x[k])
            return 0; // fără diagonale
    return 1;
}
✓ Corect — coloana + diagonale
int VALID(int k) {
    for(int i=1; i<k; i++)
        if(x[i]==x[k] ||
           abs(x[i]-x[k])==abs(i-k))
            return 0;
    return 1;
}
💡Condiția abs(x[i]-x[k]) == abs(i-k) acoperă atât diagonala principală, cât și antidiagonala printr-un singur test. Nu e nevoie de două condiții separate.
5
SOLUTIE(k) verifică k == n+1 în loc de k == n
SOLUTIE

În schema iterativă BCK, funcția SOLUTIE(k) este apelată după ce x[k] a primit o valoare validă. La momentul apelului, k este nivelul curent (ultima poziție completată). Deci pentru un vector de n elemente, soluția este completă când k == n. Scriind k == n+1, soluția completă nu e niciodată detectată.

✗ Greșit — condiție greșită
int SOLUTIE(int k) {
    if (k == n+1) return 1;
    else return 0;
}
✓ Corect
int SOLUTIE(int k) {
    if (k == n) return 1;
    else return 0;
}
📌Atenție: Confuzia vine din schema recursivă, unde condiția de oprire este adesea if(k > n) sau if(k == n+1). În schema iterativă BCK, k rămâne pe ultimul nivel completat.
6
TIPAR afișează i = 1..k−1 în loc de 1..k
TIPAR

Când SOLUTIE(k) returnează 1, k este nivelul ultimei poziții completate (k == n). Funcția TIPAR(k) trebuie să afișeze elementele de la 1 la k inclusiv. Mulți elevi scriu i < k (sau i <= k-1), omițând ultimul element.

✗ Greșit — omite ultimul element
void TIPAR(int k) {
    for(int i=1; i<k; i++)
        cout << x[i] << " ";
    cout << endl;
}
✓ Corect
void TIPAR(int k) {
    for(int i=1; i<=k; i++)
        cout << x[i] << " ";
    cout << endl;
}
💡Puteți memora: TIPAR afișează pozițiile 1..k, VALID verifică pozițiile 1..k−1. Cele două funcții au limite de iterare diferite și exact opuse față de nivelul curent.
7
k-- lipsește în ramura EXISTA = fals a schemei iterative
BCK

Ramura else k-- din BCK este mecanismul de revenire (backtrack). Fără ea, când domeniul unui nivel se epuizează, algoritmul intră în buclă infinită — k rămâne pe același nivel și EXISTA va returna mereu 0, fără a avansa sau reveni.

✗ Greșit — buclă infinită
while (k > 0)
    if (EXISTA(k)) {
        x[k]++;
        if (VALID(k)) {
            if (SOLUTIE(k)) TIPAR(k);
            else { k++; INIT(k); }
        }
    }
    // else lipseste → k nu se decrementează
✓ Corect
while (k > 0)
    if (EXISTA(k)) {
        x[k]++;
        if (VALID(k)) {
            if (SOLUTIE(k)) TIPAR(k);
            else { k++; INIT(k); }
        }
    } else k--;   // backtrack!
⚠️Aceasta este una dintre cele mai frecvente greșeli la BAC. Fără else k--, programul scris de mână va intra în buclă infinită la rulare, chiar dacă la trasare manuală pare corect.
8
Schema recursivă: uitând used[x[k]] = false la revenire
Recursiv

În implementarea recursivă a permutărilor, vectorul used[] marchează ce valori au fost deja folosite. La revenirea din recursie (backtrack), marcajul trebuie resetat — altfel elementul rămâne marcat ca folosit și nu va mai putea fi plasat în alte poziții, generând soluții incomplete sau deloc.

✗ Greșit — used[] nu e resetat
void back(int k) {
    for(int v=1; v<=n; v++) {
        if (used[v]) continue;
        x[k] = v;
        used[v] = true;
        if (k==n) tipar();
        else back(k+1);
        // used[v] = false; — LIPSEȘTE
    }
}
✓ Corect
void back(int k) {
    for(int v=1; v<=n; v++) {
        if (used[v]) continue;
        x[k] = v;
        used[v] = true;
        if (k==n) tipar();
        else back(k+1);
        used[v] = false;  // backtrack
    }
}
📌Principiul simetriei: orice resursă marcată înainte de apelul recursiv trebuie eliberată după el. Marcaj → apel → demarcaj. Această structură sandwich garantează că arborele este explorat corect.
Tabel de referință rapidă — funcțiile BKT
Funcție Ce face Greșeală clasică Verificare rapidă
INIT(k) Setează x[k] la valoarea de dinaintea domeniului x[k] = 1 în loc de x[k] = 0 (omite prima valoare) x[k] = valmin − 1
EXISTA(k) Verifică dacă mai există valori neexplorate <= în loc de < (permite ieșirea din domeniu) x[k] < valmax
VALID(k) Testează compatibilitatea x[k] cu 1..k−1 Bucla merge până la k inclusiv; uită diagonalele la N-Regine for i=1; i<k; i++
SOLUTIE(k) Returnează 1 când soluția este completă k == n+1 în loc de k == n k == n
TIPAR(k) Afișează soluția x[1..k] i < k în loc de i <= k (omite ultimul element) for i=1; i<=k; i++
BCK() Bucla principală cu avansare și revenire Lipsește else k-- → buclă infinită Structura if(EXISTA)…else k--
Sfaturi pentru examen
01
Trasează manual cu n = 3
Înainte de a scrie codul final, trasează algoritmul pe hârtie cu n=3 și verifică că obții exact 6 permutări. Dacă obții 5 sau 7, ai o greșeală în INIT, EXISTA sau VALID.
02
Verifică simetria la recursiv
Orice used[v] = true înainte de apelul recursiv trebuie urmat de used[v] = false după el. Dacă nu e simetric, ai uitat resetarea.
03
Numără soluțiile așteptate
Pentru permutări: n! soluții. Pentru N-Regine N=4: 2 soluții, N=5: 10 soluții, N=6: 4 soluții. Dacă algoritmul tău produce alt număr, există o eroare de logică.
04
Nu confunda cele 2 scheme
Schema iterativă BCK folosește while + k++/k--. Schema recursivă folosește back(k+1). Nu amesteca elementele celor două — BAC-ul cere de obicei forma iterativă.
05
Acordă atenție limitelor de iterare
VALID: i < k (comparare cu precedentele). TIPAR: i <= k (afișare inclusiv curentul). Ușor de confuzat sub presiunea examenului.
06
Verifică INIT pentru fiecare avansare
INIT(k) se apelează de două ori: la start (k=1; INIT(k)) și la fiecare avansare (k++; INIT(k)). Dacă lipsește al doilea apel, nivelul nou nu va fi inițializat corect.