Sudoku rešavač (bektreking)
Sudoku je odličan primer bektrekinga: prazna polja popunjavamo jedno po jedno, a čim neki broj izazove sukob, vraćamo se i probamo sledeći. Na kraju lekcije je i poboljšanje koje rešavač ubrzava više stotina puta.
Kako da učiš: svaki primer prekucaj i pokreni, a zadatke na kraju prvo reši sam/a; uputstvo i rešenje otvori tek posle toga. Svi programi su u C++-u.
Pravila i predstavljanje table
Tabla je 9 × 9, podeljena na devet kvadrata 3 × 3. U svaku vrstu, kolonu i kvadrat treba upisati brojeve od 1 do 9, svaki tačno jednom. Tablu čuvamo kao matricu int t[9][9], a prazno polje kao 0. Ulaz je 9 redova po 9 cifara.
Kvadrat kome pripada polje (r, k) ima redni broj (r / 3) * 3 + k / 3 (od 0 do 8).
Rešavač
Za brzu proveru vodimo tri tabele zauzetosti: uVrsti[r][b], uKoloni[k][b] i uKvadratu[q][b] – da li je broj b već upotrebljen. Funkcija resi(p) popunjava polja redom (p = 0 … 80) i vraća true čim je tabla popunjena.
#include <iostream>
#include <string>
using namespace std;
int t[9][9];
bool uVrsti[9][10], uKoloni[9][10], uKvadratu[9][10];
int kvadrat(int r, int k) { return (r / 3) * 3 + k / 3; }
void postavi(int r, int k, int b, bool vrednost) {
uVrsti[r][b] = uKoloni[k][b] = uKvadratu[kvadrat(r, k)][b] = vrednost;
}
bool resi(int p) {
if (p == 81) return true; // sva polja su popunjena
int r = p / 9, k = p % 9;
if (t[r][k] != 0) return resi(p + 1); // polje je zadato
for (int b = 1; b <= 9; b++) {
if (uVrsti[r][b] || uKoloni[k][b] || uKvadratu[kvadrat(r, k)][b]) continue;
t[r][k] = b; postavi(r, k, b, true); // izaberi
if (resi(p + 1)) return true; // istraži – rešenje nađeno
t[r][k] = 0; postavi(r, k, b, false); // poništi
}
return false; // nijedan broj ne odgovara
}
int main() {
for (int r = 0; r < 9; r++) {
string red;
cin >> red;
for (int k = 0; k < 9; k++) {
t[r][k] = red[k] - '0';
if (t[r][k] != 0) postavi(r, k, t[r][k], true);
}
}
if (!resi(0)) {
cout << "Nema resenja" << endl;
return 0;
}
for (int r = 0; r < 9; r++) {
for (int k = 0; k < 9; k++) cout << t[r][k];
cout << "\n";
}
return 0;
}
Primer
Ulaz
530070000
600195000
098000060
800060003
400803001
700020006
060000280
000419005
000080079Izlaz
534678912
672195348
198342567
859761423
426853791
713924856
961537284
287419635
345286179Ubrzanje: prvo polje sa najmanje mogućnosti
Rešavač iznad popunjava polja redom. Kod teških sudokua mnogo je brže da se u svakom koraku izabere prazno polje sa najmanje dozvoljenih brojeva: ako polje ima samo jednu mogućnost, nema grananja, a ako ima nula – odmah se vraćamo. Ova heuristika (u literaturi „MRV – minimum remaining values“) smanjuje stablo pretrage za nekoliko redova veličine.
// Umesto "sledeće polje redom", u funkciji resi() biramo najbolje polje:
int najboljeR = -1, najboljeK = -1, najmanje = 10;
for (int r = 0; r < 9; r++)
for (int k = 0; k < 9; k++) {
if (t[r][k] != 0) continue;
int moguce = 0;
for (int b = 1; b <= 9; b++)
if (!uVrsti[r][b] && !uKoloni[k][b] && !uKvadratu[kvadrat(r, k)][b]) moguce++;
if (moguce < najmanje) { najmanje = moguce; najboljeR = r; najboljeK = k; }
}
if (najboljeR == -1) return true; // nema praznih polja – rešeno
if (najmanje == 0) return false; // ćorsokak
// ... dalje isto kao ranije, ali za polje (najboljeR, najboljeK)
- Pre rekurzivnog poziva ažuriraj i tablu i tabele zauzetosti, a posle neuspešnog poziva vrati obe.
- Funkcija koja vraća
booli odmah izlazi kad nađe rešenje mnogo je brža od one koja traži sva rešenja. - Za kvadrat 3 × 3 koristi celobrojno deljenje:
r / 3ik / 3. - Uvek proveri i ulaz bez rešenja – program treba to da prijavi, a ne da se vrti bez kraja.
Proveri znanje
Otvori kviz
Mini kviz: Sudoku
1. Koji redni broj kvadrata ima polje (4, 7) (indeksi od 0)?
2. Šta funkcija resi() treba da uradi kada nijedan broj ne odgovara polju?
