I code the Knight's tour algorithm in c++ using Backtracking method. But it seems too slow or stuck in infinite loop for n > 7 (bigger than 7 by 7 chessboard).
The question is: What is the Time complexity for this algorithm and how can I optimize it?!
The Knight's Tour problem can be stated as follows:
Given a chess board with n × n squares, find a path for the knight that visits every square exactly once.
Here is my code:
#include <iostream>
#include <iomanip>
using namespace std;
int counter = 1;
class horse {
public:
horse(int);
bool backtrack(int, int);
void print();
private:
int size;
int arr[8][8];
void mark(int &);
void unmark(int &);
bool unvisited(int &);
};
horse::horse(int s) {
int i, j;
size = s;
for (i = 0; i <= s - 1; i++)
for (j = 0; j <= s - 1; j++)
arr[i][j] = 0;
}
void horse::mark(int &val) {
val = counter;
counter++;
}
void horse::unmark(int &val) {
val = 0;
counter--;
}
void horse::print() {
cout << "\n - - - - - - - - - - - - - - - - - -\n";
for (int i = 0; i <= size - 1; i++) {
cout << "| ";
for (int j = 0; j <= size - 1; j++)
cout << setw(2) << setfill ('0') << arr[i][j] << " | ";
cout << "\n - - - - - - - - - - - - - - - - - -\n";
}
}
bool horse::backtrack(int x, int y) {
if (counter > (size * size))
return true;
if (unvisited(arr[x][y])) {
if ((x - 2 >= 0) && (y + 1 <= (size - 1))) {
mark(arr[x][y]);
if (backtrack(x - 2, y + 1))
return true;
else
unmark(arr[x][y]);
}
if ((x - 2 >= 0) && (y - 1 >= 0)) {
mark(arr[x][y]);
if (backtrack(x - 2, y - 1))
return true;
else
unmark(arr[x][y]);
}
if ((x - 1 >= 0) && (y + 2 <= (size - 1))) {
mark(arr[x][y]);
if (backtrack(x - 1, y + 2))
return true;
else
unmark(arr[x][y]);
}
if ((x - 1 >= 0) && (y - 2 >= 0)) {
mark(arr[x][y]);
if (backtrack(x - 1, y - 2))
return true;
else
unmark(arr[x][y]);
}
if ((x + 2 <= (size - 1)) && (y + 1 <= (size - 1))) {
mark(arr[x][y]);
if (backtrack(x + 2, y + 1))
return true;
else
unmark(arr[x][y]);
}
if ((x + 2 <= (size - 1)) && (y - 1 >= 0)) {
mark(arr[x][y]);
if (backtrack(x + 2, y - 1))
return true;
else
unmark(arr[x][y]);
}
if ((x + 1 <= (size - 1)) && (y + 2 <= (size - 1))) {
mark(arr[x][y]);
if (backtrack(x + 1, y + 2))
return true;
else
unmark(arr[x][y]);
}
if ((x + 1 <= (size - 1)) && (y - 2 >= 0)) {
mark(arr[x][y]);
if (backtrack(x + 1, y - 2))
return true;
else
unmark(arr[x][y]);
}
}
return false;
}
bool horse::unvisited(int &val) {
if (val == 0)
return 1;
else
return 0;
}
int main() {
horse example(7);
if (example.backtrack(0, 0)) {
cout << " >>> Successful! <<< " << endl;
example.print();
} else
cout << " >>> Not possible! <<< " << endl;
}
output for the example (n = 7) above is like this:
Since at each step you have 8 possibilities to check and this has to be done for each cell (minus the last one) the time complexity of this algorithm is O(8^(n^2-1)) = O(8^(n^2)) where n is the number of squares on the edges of the checkboard. To be precise this is the worst case time complexity (time taken to explore all the possibilities if none is found or if it is the last one).
As for the optimizations there can be 2 types of improvements:
You're calculating x-2, x-1, x+1, x+2 and the same for y at least the double of the times. I can suggest to rewrite things like this:
int sm1 = size - 1;
int xm2 = x - 2;
int yp1 = y + 1;
if((xm2 >= 0) && (yp1 <= (sm1))){
mark(arr[x][y]);
if(backtrack(xm2, yp1))
return true;
else
unmark(arr[x][y]);
}
int ym1 = y-1;
if((xm2 >= 0) && (ym1 >= 0)){
mark(arr[x][y]);
if(backtrack(xm2, ym1))
return true;
else
unmark(arr[x][y]);
}
note the reusing of precalculated values also in subsequent blocks.
I've found this to be more effective than what I was especting; meaning that variable assignment and recall is faster than doing the operation again.
Also consider saving sm1 = s - 1;
and area = s * s;
in the constructor instead of calculating each time.
However this (being an implementation improvement and not an algorithm improvement) will not change the time complexity order but only divide the time by a certain factor. I mean time complexity O(8^(n^2)) = k*8^(n^2) and the difference will be in a lower k factor.
I can think this:
counter % 8 == 4
for example or better counter > 2*n && counter % 8 == 4
Bye