Knight's Tour – obilazak konja (backtracking rešenje)
Zadatak:
Napiši program koji pronalazi obilazak konja (Knight's Tour) na šahovskoj tabli dimenzije n × n.
Konj treba da obiđe sva polja table tako da svako polje bude posećeno tačno jednom, prateći pravila kretanja konja u šahu.
Obilazak može biti:
- otvoren – poslednje polje ne napada prvo;
- zatvoren – poslednje polje napada prvo, čineći ciklus.
Ulaz: Sa standardnog ulaza unosi se broj n (5 ≤ n ≤ 10).
Izlaz: Ako rešenje postoji, ispisuje se matrica gde je svako polje numerisano rednim brojem obilaska (od 1 do n×n).
Primer:
Ulaz: 5 Izlaz (primer jednog obilaska): 1 6 15 10 21 14 9 20 5 16 19 2 7 22 11 8 13 24 17 4 25 18 3 12 23
Uputstvo i ideja rešenja
Knight's Tour je jedan od najpoznatijih primera bektreking algoritma. Ideja je sledeća:
- Krećemo od početnog polja i zapisujemo broj poteza (1, 2, 3, …).
- Konj ima maksimalno 8 mogućih skokova – proveravamo ih redom.
- Ako je sledeće polje slobodno i unutar table, prelazimo na njega.
- Ako više nema mogućih skokova → vraćamo se unazad (backtracking).
- Kada smo popunili n×n polja, dobili smo validan obilazak.
Zbog složenosti problema, traženje obilaska se oslanja na pretragu po dubini i pametno odbacivanje pozicija koje vode u ćorsokak.
Rešenje u C++ jeziku
#include <iostream>
#include <vector>
using namespace std;
int n;
vector<vector<int>> tabla;
// Svi mogući skokovi konja
int dx[8] = { 2, 1, -1, -2, -2, -1, 1, 2 };
int dy[8] = { 1, 2, 2, 1, -1, -2, -2, -1 };
// Proverava da li je potez validan
bool validno(int x, int y) {
return (x >= 0 && x < n && y >= 0 && y < n && tabla[x][y] == 0);
}
// Rekurzivna funkcija za obilazak konja
bool obilazak(int x, int y, int korak) {
if (korak == n * n)
return true; // Svi potezi uspešno napravljeni
for (int i = 0; i < 8; i++) {
int nx = x + dx[i];
int ny = y + dy[i];
if (validno(nx, ny)) {
tabla[nx][ny] = korak + 1;
if (obilazak(nx, ny, korak + 1))
return true;
tabla[nx][ny] = 0; // Backtracking
}
}
return false;
}
int main() {
cout << "Unesite dimenziju table n (5 <= n <= 10): ";
cin >> n;
if (n < 5 || n > 10) {
cout << "Dozvoljene vrednosti su od 5 do 10." << endl;
return 1;
}
tabla.assign(n, vector<int>(n, 0));
// Početna pozicija konja (0, 0)
tabla[0][0] = 1;
if (obilazak(0, 0, 1)) {
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
cout << tabla[i][j] << "\t";
}
cout << endl;
}
} else {
cout << "Nema rešenja za zadatu tablu." << endl;
}
return 0;
}
Objašnjenje koda
Program koristi matricu tabla gde se čuva redni broj obilaska.
validno()proverava da li je potez dozvoljen: unutar table i neposećen.obilazak()rekurzivno pokušava 8 poteza konja.- Ako se popuni n×n poteza, rešenje je pronađeno.
- Ako se dođe u ćorsokak → vraćamo se (backtracking).
Komentar o efikasnosti i poboljšanjima
Osnovni backtracking za Knight's Tour ima veoma veliku složenost (eksponencijalnu). Za male table radi dobro, ali za veće postaje spor.
Moguća poboljšanja:
- Warnsdorffovo pravilo – uvek birati potez sa najmanje nastavaka. Drastično ubrzava algoritam.
- Korišćenje heuristika za redukovanje grananja.
- Korišćenje bitmapi za optimizaciju provere zauzetosti.
- Memorisanje problematičnih konfiguracija.
U praksi, Warnsdorffova heuristika može da pronađe rešenje na tabli 8×8 za nekoliko milisekundi.
Varijanta 1 — Detaljno komentarisano C++ rešenje (gruba sila + backtracking)
U ovoj sekciji nalazi se u potpunosti komentarisana verzija osnovnog backtracking algoritma za problem obilaska konja. Cilj je da se korisniku jasno prikaže šta se dešava u svakom koraku algoritma.
Ovo je odličan dodatni materijal za učenike koji žele da razumeju:
- šta znači “proveriti validan potez”,
- kako funkcioniše rekurzija u backtracking algoritmu,
- na koji način se vrši “vraćanje unazad” (undo poteza),
- kako izgleda standardna struktura jednog rekurzivnog pretraživača.
Potpuno komentarisani C++ kod (Varijanta 1)
// -----------------------------------------------------------
// OBILAZAK KONJA – Varijanta 1 (gruba sila + backtracking)
// Kompletno objašnjena verzija koda, sa komentarima reda-po-red.
// -----------------------------------------------------------
#include <iostream>
#include <vector>
using namespace std;
// Sva 8 moguća pomeranja konja
int dx[8] = { 2, 1, -1, -2, -2, -1, 1, 2 };
int dy[8] = { 1, 2, 2, 1, -1, -2, -2, -1 };
// Proveravamo da li je moguće preći na polje (x, y)
bool valid(int x, int y, int n, vector>& board) {
// Mora biti unutar table
if (x < 0 || x >= n) return false;
if (y < 0 || y >= n) return false;
// Polje mora biti neuslovljeno (0 znači neposećeno)
if (board[x][y] != 0) return false;
return true;
}
// Glavna rekurzivna funkcija
bool tour(int x, int y, int step, int n, vector>& board) {
// Ako smo popunili sva n*n polja → kraj!
if (step == n*n)
return true;
// Prolazimo kroz svih 8 mogućih poteza konja
for (int i = 0; i < 8; i++) {
// Računamo novo polje
int nx = x + dx[i];
int ny = y + dy[i];
// Provera da li je potez dozvoljen
if (valid(nx, ny, n, board)) {
// -------------------------------
// 1) Postavljamo konja na novo polje
// -------------------------------
board[nx][ny] = step + 1;
// -------------------------------
// 2) Rekurzivno nastavljamo obilazak
// -------------------------------
if (tour(nx, ny, step + 1, n, board))
return true; // Ako uspe → gotovo
// -------------------------------
// 3) BACKTRACKING (vraćamo se unazad)
// Ako rekurzija NIJE našla rešenje,
// poništavamo potez i pokušavamo sledeći.
// -------------------------------
board[nx][ny] = 0;
}
}
// Ako nijedan potez iz ove pozicije ne vodi do rešenja
return false;
}
int main() {
int n;
cin >> n;
// Tabla inicijalno puna nula (neposećena polja)
vector> board(n, vector(n, 0));
// Startna pozicija: (0,0)
board[0][0] = 1;
// Pokrećemo backtracking algoritam
if (tour(0, 0, 1, n, board)) {
cout << "Pronadjeno resenje:\n";
for (auto& row : board) {
for (int cell : row)
cout << cell << "\t";
cout << "\n";
}
} else {
cout << "Nema resenja.\n";
}
}
Objašnjenje ključnih tačaka u kodu
- valid() proverava da li je polje unutar table i da li još nije posećeno.
- tour() je srce algoritma:
- prima trenutnu poziciju (x, y), broj koraka i tablu,
- u svakom koraku pokušava svih 8 poteza,
- rekurzivno prati jednu granu dok god je validna,
- ako zapne → vrati se unazad (backtracking),
- kada ispuni n*n → uspeh.
- Backtracking deo:
Ako rekurzija ne uspe, potez se “poništava”:
Ovo je ključni trenutak – algoritam se vraća jednu poziciju unazad i pokušava drugi put.board[nx][ny] = 0; - U main() samo postavljamo tablu, start i pozivamo funkciju.
Zašto je ova verzija važna?
Iako ova verzija nije najbrža, predstavlja najvažniji obrazovni primer. Korisnicima se jasno vidi šta je:
- rekurzija,
- grananje pretrage,
- povratak unazad,
- proceduralno građenje rešenja.
Ovako detaljno komentarisano rešenje je korak ka razumevanju složenijih heuristika (Varijanta 2, Varijanta 3).
Obilazak konja – Varijanta 2 (optimizovani backtracking pomoću Warnsdorffove heuristike)
Zadatak:
Napisati program koji pronalazi putanju konja na šahovskoj tabli dimenzije n × n tako da konj obiđe
sva polja tačno jednom (Knight's Tour). U ovoj varijanti koristi se optimizovani pristup koji značajno ubrzava
izvođenje algoritma.
Ulaz: Unosi se broj n (5 ≤ n ≤ 10).
Izlaz: Ispisati tablu sa rednim brojem poteza kojim je obiđeno svako polje.
Ideja rešenja – Warnsdorffova heuristika
Osnovni backtracking isprobava sve moguće skokove konja bez reda, što dovodi do ogromnog grananja i usporava algoritam za veće table. Warnsdorffova heuristika uvodi sledeće pravilo:
„U svakom koraku biraj onaj potez konja koji ima najmanji broj daljih mogućih poteza.“
Ovo pravilo čini algoritam znatno efikasnijim i omogućava pronalaženje rešenja za veće table mnogo brže nego čisti backtracking.
Koraci algoritma
- Definisati svih 8 mogućih skokova konja.
- U svakom koraku sakupiti sve validne skokove.
- Za svaki skok izračunati njegov "stepen" – koliko ima nastavaka.
- Sortirati poteze rastuće po stepenu.
- Prvo pokušati one poteze koji ostavljaju najmanje mogućnosti.
Rešenje u C++ jeziku (Varijanta 2 – optimizovano)
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int n;
vector<vector<int>> tabla;
int dx[8] = { 2, 1, -1, -2, -2, -1, 1, 2 };
int dy[8] = { 1, 2, 2, 1, -1, -2, -2, -1 };
// Proverava da li je polje validno za kretanje
bool validno(int x, int y) {
return x >= 0 && x < n && y >= 0 && y < n && tabla[x][y] == 0;
}
// Izračunava "stepen" – koliko konj ima nastavaka iz ove pozicije
int stepen(int x, int y) {
int count = 0;
for (int i = 0; i < 8; i++) {
int nx = x + dx[i];
int ny = y + dy[i];
if (validno(nx, ny)) count++;
}
return count;
}
// Optimizovani rekurzivni obilazak
bool obilazak(int x, int y, int korak) {
if (korak == n * n)
return true;
vector<pair<int, int>> potezi;
// Skup svih mogućih poteza + njihov stepen
for (int i = 0; i < 8; i++) {
int nx = x + dx[i];
int ny = y + dy[i];
if (validno(nx, ny)) {
int s = stepen(nx, ny);
potezi.push_back({s, i});
}
}
// Warnsdorff: sortiranje po rastućem stepenu
sort(potezi.begin(), potezi.end());
// Pokušaj po najboljem redosledu
for (auto &p : potezi) {
int i = p.second;
int nx = x + dx[i], ny = y + dy[i];
tabla[nx][ny] = korak + 1;
if (obilazak(nx, ny, korak + 1))
return true;
tabla[nx][ny] = 0; // povratak (backtracking)
}
return false;
}
int main() {
cout << "Unesite n (5 <= n <= 10): ";
cin >> n;
if (n < 5 || n > 10) {
cout << "Dozvoljene vrednosti su od 5 do 10." << endl;
return 1;
}
tabla.assign(n, vector<int>(n, 0));
// Konj kreće iz gornjeg levog ugla
tabla[0][0] = 1;
if (obilazak(0, 0, 1)) {
for (auto &red : tabla) {
for (int polje : red)
cout << polje << "\t";
cout << endl;
}
} else {
cout << "Nema rešenja!" << endl;
}
return 0;
}
Objašnjenje ključnih delova
stepen(x, y)– računa koliko nastavaka dat potez ima.potezi– lista validnih poteza zajedno sa brojem budućih opcija.sort(potezi.begin(), potezi.end())– prvo pokušava najteža polja.- Na taj način algoritam drastično smanjuje broj provera.
Zašto je varijanta 2 mnogo brža?
Umesto da algoritam lutajući ispituje preko 100–1000 puta više grana, Warnsdorffov pristup vodi konja „najkritičnijim putem“ i čini verovatnijim da obilazak uspe bez povratka.
Rezultati u praksi
- Za n = 5 → rešenje se dobija gotovo instant.
- Za n = 8 (klasična tabla) → rešenje se dobija u milisekundama.
- Čisti backtracking bi za n = 8 trajao predugo bez optimizacija.
Zaključak
Ovo rešenje predstavlja značajno unapređenje osnovnog backtracking pristupa. Warnsdorffova heuristika je jednostavna za implementaciju, a pruža ogromno ubrzanje.
Ova varijanta može služiti kao uvod u još naprednije tehnike poput iterativnog poboljšanja (local search) i heuristike zasnovane na zaključivanju.
Obilazak konja – Varijanta 2 (Warnsdorffova heuristika) — dodatne implementacije
U nastavku su dve dodatne implementacije Varijante 2: Python i C#. Oba rešenja koriste Warnsdorffovo pravilo za izbor narednog poteza.
Rešenje u Pythonu (Varijanta 2)
#!/usr/bin/env python3
import sys
sys.setrecursionlimit(10000)
dx = [2, 1, -1, -2, -2, -1, 1, 2]
dy = [1, 2, 2, 1, -1, -2, -2, -1]
def valid(x, y, n, board):
return 0 <= x < n and 0 <= y < n and board[x][y] == 0
def degree(x, y, n, board):
cnt = 0
for i in range(8):
nx, ny = x + dx[i], y + dy[i]
if valid(nx, ny, n, board):
cnt += 1
return cnt
def tour(x, y, step, n, board):
if step == n * n:
return True
moves = []
for i in range(8):
nx, ny = x + dx[i], y + dy[i]
if valid(nx, ny, n, board):
moves.append((degree(nx, ny, n, board), nx, ny))
# sortiraj po rastućem stepenu (Warnsdorff)
moves.sort(key=lambda t: t[0])
for _, nx, ny in moves:
board[nx][ny] = step + 1
if tour(nx, ny, step + 1, n, board):
return True
board[nx][ny] = 0 # backtrack
return False
def main():
try:
n = int(input("Unesite n (5 <= n <= 10): ").strip())
except:
print("Nevažeći unos.")
return
if n < 5 or n > 10:
print("Dozvoljene vrednosti su od 5 do 10.")
return
board = [[0] * n for _ in range(n)]
board[0][0] = 1
if tour(0, 0, 1, n, board):
for row in board:
print("\t".join(str(x) for x in row))
else:
print("Nema rešenja.")
if __name__ == "__main__":
main()
Objašnjenje (Python)
degreeračuna koliko validnih poteza ima dato polje — to je kriterijum za Warnsdorff.- U
tourfunkciji skupljamo sve validne poteze, sortiramo ih po stepenu i pokušavamo ih u tom redu. - Rezultat se ispisuje kao matrica rednih brojeva obilaska.
Rešenje u C# (Varijanta 2)
using System;
using System.Collections.Generic;
class Program {
static int n;
static int[,] board;
static int[] dx = { 2, 1, -1, -2, -2, -1, 1, 2 };
static int[] dy = { 1, 2, 2, 1, -1, -2, -2, -1 };
static bool Valid(int x, int y) {
return x >= 0 && x < n && y >= 0 && y < n && board[x, y] == 0;
}
static int Degree(int x, int y) {
int cnt = 0;
for (int i = 0; i < 8; i++) {
int nx = x + dx[i], ny = y + dy[i];
if (Valid(nx, ny)) cnt++;
}
return cnt;
}
static bool Tour(int x, int y, int step) {
if (step == n * n) return true;
var moves = new List>(); // (degree, nx, ny)
for (int i = 0; i < 8; i++) {
int nx = x + dx[i], ny = y + dy[i];
if (Valid(nx, ny)) {
moves.Add(Tuple.Create(Degree(nx, ny), nx, ny));
}
}
// sortiramo po stepenu (rastuce)
moves.Sort((a,b) => a.Item1.CompareTo(b.Item1));
foreach (var m in moves) {
int nx = m.Item2, ny = m.Item3;
board[nx, ny] = step + 1;
if (Tour(nx, ny, step + 1)) return true;
board[nx, ny] = 0; // backtrack
}
return false;
}
static void Main() {
Console.Write("Unesite n (5 <= n <= 10): ");
if (!int.TryParse(Console.ReadLine(), out n)) return;
if (n < 5 || n > 10) {
Console.WriteLine("Dozvoljene vrednosti su od 5 do 10.");
return;
}
board = new int[n, n];
board[0, 0] = 1;
if (Tour(0, 0, 1)) {
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
Console.Write(board[i, j] + "\t");
}
Console.WriteLine();
}
} else {
Console.WriteLine("Nema rešenja.");
}
}
}
Objašnjenje (C#)
- Ista logika kao u C++ i Python verzijama:
Degreei sortiranje poteza po stepenu. - Korišćena je lista tuple-ova za sortiranje poteza; zatim se pokušava svaki potez u redu.
Predlozi za integraciju na stranicu
- Dodaj ova dva koda ispod C++ dela kao „Dodatne implementacije”.
- Možeš dodati
<details>HTML blokove oko dugih kodova da strani bude preglednija. - Za bolju interakciju — razmisli o dodavanju dugmeta za preuzimanje koda (plain .py i .cs fajlovi).
Obilazak konja – Varijanta 3 (čisto heurističko rešenje, bez backtrackinga)
Za razliku od Varijante 1 (gruba sila) i Varijante 2 (backtracking + Warnsdorff), ova varijanta uopšte ne koristi povratak unazad (backtracking).
Algoritam koristi isključivo Warnsdorffovo pravilo: u svakom koraku bira se potez koji vodi do polja sa najmanjim brojem daljih validnih poteza.
Ovaj čist heuristički pristup je veoma brz (linearna složenost O(n²)) i u praksi daje rešenje za sve table dimenzija n ≥ 5.
C++ implementacija (Varijanta 3)
#include <bits/stdc++.h>
using namespace std;
int dx[8] = {2,1,-1,-2,-2,-1,1,2};
int dy[8] = {1,2,2,1,-1,-2,-2,-1};
bool valid(int x, int y, int n, vector>& board) {
return x >= 0 && x < n && y >= 0 && y < n && board[x][y] == 0;
}
int degree(int x, int y, int n, vector>& board) {
int cnt = 0;
for (int i = 0; i < 8; i++) {
int nx = x + dx[i], ny = y + dy[i];
if (valid(nx, ny, n, board)) cnt++;
}
return cnt;
}
bool warnsdorff(int n, vector>& board, int sx = 0, int sy = 0) {
int x = sx, y = sy;
board[x][y] = 1;
for (int step = 2; step <= n*n; step++) {
int bestDeg = 9, bestX = -1, bestY = -1;
for (int i = 0; i < 8; i++) {
int nx = x + dx[i], ny = y + dy[i];
if (valid(nx, ny, n, board)) {
int d = degree(nx, ny, n, board);
if (d < bestDeg) {
bestDeg = d;
bestX = nx;
bestY = ny;
}
}
}
if (bestX == -1) return false; // heuristika propala
x = bestX;
y = bestY;
board[x][y] = step;
}
return true; // uspešno
}
int main() {
int n;
cin >> n;
vector> board(n, vector(n, 0));
if (warnsdorff(n, board)) {
for (auto& r : board) {
for (int x : r) cout << x << "\t";
cout << "\n";
}
} else {
cout << "Nema rešenja.\n";
}
}
Python implementacija (Varijanta 3)
dx = [2,1,-1,-2,-2,-1,1,2]
dy = [1,2,2,1,-1,-2,-2,-1]
def valid(x, y, n, board):
return 0 <= x < n and 0 <= y < n and board[x][y] == 0
def degree(x, y, n, board):
cnt = 0
for i in range(8):
nx, ny = x + dx[i], y + dy[i]
if valid(nx, ny, n, board):
cnt += 1
return cnt
def warnsdorff(n, board, sx=0, sy=0):
x, y = sx, sy
board[x][y] = 1
for step in range(2, n*n + 1):
bestDeg = 9
best = None
for i in range(8):
nx, ny = x + dx[i], y + dy[i]
if valid(nx, ny, n, board):
d = degree(nx, ny, n, board)
if d < bestDeg:
bestDeg = d
best = (nx, ny)
if best is None:
return False
x, y = best
board[x][y] = step
return True
def main():
n = int(input("Unesite n (>= 5): "))
board = [[0]*n for _ in range(n)]
if warnsdorff(n, board):
for row in board:
print("\t".join(str(x) for x in row))
else:
print("Nema rešenja.")
if __name__ == "__main__":
main()
C# implementacija (Varijanta 3)
using System;
class Program {
static int[] dx = { 2, 1, -1, -2, -2, -1, 1, 2 };
static int[] dy = { 1, 2, 2, 1, -1, -2, -2, -1 };
static bool Valid(int x, int y, int n, int[,] board) {
return x >= 0 && x < n && y >= 0 && y < n && board[x, y] == 0;
}
static int Degree(int x, int y, int n, int[,] board) {
int cnt = 0;
for (int i = 0; i < 8; i++) {
int nx = x + dx[i], ny = y + dy[i];
if (Valid(nx, ny, n, board)) cnt++;
}
return cnt;
}
static bool Warnsdorff(int n, int[,] board, int sx = 0, int sy = 0) {
int x = sx, y = sy;
board[x, y] = 1;
for (int step = 2; step <= n * n; step++) {
int bestDeg = 9;
int bestX = -1, bestY = -1;
for (int i = 0; i < 8; i++) {
int nx = x + dx[i], ny = y + dy[i];
if (Valid(nx, ny, n, board)) {
int d = Degree(nx, ny, n, board);
if (d < bestDeg) {
bestDeg = d;
bestX = nx;
bestY = ny;
}
}
}
if (bestX == -1)
return false;
x = bestX;
y = bestY;
board[x, y] = step;
}
return true;
}
static void Main() {
int n = int.Parse(Console.ReadLine());
int[,] board = new int[n, n];
if (Warnsdorff(n, board)) {
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
Console.Write(board[i, j] + "\t");
}
Console.WriteLine();
}
} else {
Console.WriteLine("Nema rešenja.");
}
}
}
Karakteristike Varijante 3
- Izuzetno brzo rešenje — praktično O(n²).
- Nema povratka unazad: svaki potez je konačan.
- Uvek daje rešenje za n ≥ 5 (osim retkih patoloških kombinacija startne pozicije, koje se mogu izbeći rotacijom/simetrijom).
- Minimalan kod i izuzetno elegantna heuristika.
Napomena i dodatni resurs
Ako te zanima naprednija primena konja na šahovskoj tabli, pogledaj i stranicu Skakač napada – analiza i rešenja .
Članak objašnjava kako koristiti konja za napad na tabli, prikazuje različite algoritamske pristupe i optimizacije, uključujući upotrebu unija u C++ za efikasnije praćenje pozicija. Čitanjem ovog članka možeš steći bolji uvid u strategije kretanja konja i unaprediti razumevanje backtracking-a u kontekstu šaha.
Ukratko, dok je Knight's Tour zadatak fokusiran na obilazak svih polja, članak Skakač napada pokazuje kako se konj može koristiti u napadačkim situacijama i kako algoritamski pristupi mogu biti optimizovani za praktične primene.
Izvori i reference
Neki zadaci i ideje algoritama inspirisani su sledećim izvorima, ali svi kodovi i objašnjenja su originalni i prilagođeni za SvetProgramiranja:
- GeeksforGeeks – Knight's Tour Problem
- LeetCode – arhiva zadataka (N-Queens, Word Search, Sudoku)
- Petlja / Takprog – srednjoškolski zadaci iz algoritama
Napomena: svi kodovi su kreirani posebno za SvetProgramiranja i prilagođeni za edukativnu upotrebu.
|< Priprema za drzavno takmičenje i SIO

