2

I'm trying to solve the knight's tour problem using backtracking. I think the algorithm I have should work. I've tried but I can't figure out why it isn't working. It results in an infinite loop.

However if I comment out the line that back track solutionBoard[dst.x][dst.y]=-1; it works! I just don't understand why! Any help would be appreciated.

private int solutionBoard[][] = new int [8][8];

// The eight possible moves a knight can make from any given position
private static final Point[] MOVES = new Point[] { new Point(-2, -1),
        new Point(-2, 1), new Point(2, -1), new Point(2, 1),
        new Point(-1, -2), new Point(-1, 2), new Point(1, -2),
        new Point(1, 2) };

private int count = 0;

public KnightsTour_DFS(){
    // board is 0- 7
    //initialize visited
    for(int i =0; i<8;i++){
        for(int j = 0; j< 8; j++){
            solutionBoard[i][j] = -1;
        }
    }

    solutionBoard[0][0]=count++;
    if(findTour(0, 0)){
        System.out.println("Tour found!!");
        printSolution();
    }
}

public boolean findTour(int x, int y){
    if(x <0 || y <0 || x>7 || y > 7 ){
        return false;
    }
    if(count == 64){
        //we've covered all node
        return true;
    }
    for(int i = 0; i < this.MOVES.length; i++){
        Point dst =  new Point(x + MOVES[i].x, y + MOVES[i].y);
        if(canMove(dst)){
            solutionBoard[dst.x][dst.y]=count++;
            if(findTour(dst.x, dst.y)){
                System.out.println("Solution shown on board\n");
                return true;
            }
            else{
                count --;
                solutionBoard[dst.x][dst.y]=-1;
            }
        }       
    }
    return false;
}

private void printSolution() {
    System.out.println("Solution shown on board\n");
    for (int[] rows : solutionBoard) {
        for (int r : rows) {
            System.out.printf("%2d ", r);
        }
        System.out.println();
    }
}
public boolean canMove(Point destination){
    if(destination.x<0 || destination.y<0 || destination.x>7|| destination.y>7){
        return false;
    }
    if(solutionBoard[destination.x][destination.y] != -1){
        //already visited 
        return false;
    }
    return true;
}
false
  • 10,264
  • 13
  • 101
  • 209
12rad
  • 829
  • 2
  • 13
  • 28
  • What do you mean, it works when you remove that line? Does it terminate, or does it find a correct solution? Are you sure that it's nor just terribly slow? – tobias_k Jul 03 '14 at 15:59
  • It terminates and finds a correct solution. – 12rad Jul 03 '14 at 16:00
  • When I run it without that line it just returns `false`, and if I print the "solution" there are duplicate numbers in it. – tobias_k Jul 03 '14 at 16:01
  • I agree with @tobias_k; there's no way this can work after removing that line, unless the very first path you try is correct (in which case it's immaterial whether that line's there or not, since it won't be executed). Again: there is no way this code works with that line removed. – Patrick87 Jul 03 '14 at 16:05
  • I set the first visited node to solutionBoard[0][0]=count++; and then later solutionBoard[dst.x][dst.y]=count++; – 12rad Jul 03 '14 at 16:06
  • 1
    In fact, what @tobias_k observes is correct: by not removing values you've set, you're bound to set duplicates, run out of free squares before count gets to 64, and not find a tour at all. – Patrick87 Jul 03 '14 at 16:07
  • Does Knight's Tour also work with a smaller board? If so, reduce the board size and try again, maybe its just slow. – tobias_k Jul 03 '14 at 16:08
  • @tobias_k yeah you're right. Looking closely there are duplicate values in the solution. I'll try with a smaller board. – 12rad Jul 03 '14 at 16:11
  • 1
    Seems to work okay with a 5x5 board starting in the middle square. Even with 7x7 finishes quickly with seemingly correct solution. Is 8x8 special? – tobias_k Jul 03 '14 at 16:25
  • 2
    See what [wikipedia has to say about brute force on 8x8](http://en.wikipedia.org/wiki/Knights_tour#Brute_force_algorithms) – tobias_k Jul 03 '14 at 16:33
  • +1 for the comment by tobias_k : It may just take awfully, afully long. The Warnsdorff's rule (also mentioned on the Wikipedia site) will allow you to solve this in a few milliseconds). – Marco13 Jul 03 '14 at 16:42
  • @12rad See my updated answer for how to speed up the search tremendouly. – tobias_k Jul 03 '14 at 19:18
  • Does this answer your question? [Knight's tour backtrack implementation choosing the step array](https://stackoverflow.com/questions/21511683/knights-tour-backtrack-implementation-choosing-the-step-array) – tkruse Apr 07 '20 at 05:38

1 Answers1

5

Your algorithm seems to work just fine, yielding correct results for smaller problem instances, like a 5x5 or 7x7 board. It seems like the 8x8 board is just too big for the brute force / backtracking approach.

Still, you can simplify your findTour method, making it easier to understand and to debug:

public boolean findTour(int x, int y, int c) {
    solutionBoard[x][y] = c;
    if (c == size*size) {
        return true;
    }
    for (Point p : MOVES) {
        Point dst =  new Point(x + p.x, y + p.y);
        if (canMove(dst) && findTour(dst.x, dst.y, c + 1)) {
            return true;
        }       
    }
    solutionBoard[x][y] = -1;
    return false;
}

Example-Output for findTour(0, 0, 1) with size = 7 (need to adapt all code to variable size!)

 1 14  3 38  5 34  7 
12 39 10 33  8 37 26 
15  2 13  4 25  6 35 
40 11 32  9 36 27 44 
19 16 21 24 45 48 29 
22 41 18 31 28 43 46 
17 20 23 42 47 30 49    

Still better: Use one of the other algorithms mentioned in the Wikipedia article, for example the rather simple Warnsdorff heuristic: "We move the knight so that we always proceed to the square from which the knight will have the fewest onward moves." We can achieve this by sorting the moves...

public Point[] sortedPoints(final int x, final int y) {
    Point[] sorted = Arrays.copyOf(MOVES, MOVES.length);
    Arrays.sort(sorted, new Comparator<Point>() {
        public int compare(Point p1, Point p2) {
            return Integer.signum(nextMoves(p1) - nextMoves(p2));
        };
        private int nextMoves(Point p) {
            Point dst = new Point(x + p.x, y + p.y);
            if (canMove(dst)) {
                int s = 0;
                for (Point m : MOVES) {
                    Point dst2 = new Point(dst.x + m.x, dst.y + m.y);
                    if (canMove(dst2)) {
                        s++;
                    }
                }
                return s;
            } else {
                return 999;
            }
        }
    });
    return sorted;
}

... and changing the successors-loop to for (Point p : sortedPoints(x, y)). Results:

size     findTour calls without and with heuristic
5x5                     76497       25 
7x7                     8947880     49
8x8                     ???         64
20x20                   ???         400

Indeed, for all the sizes I tried, the findTour method was called exactly size^2 times, i.e. it found the tour on first try, without backtracking at all.

tobias_k
  • 81,265
  • 12
  • 120
  • 179
  • 1
    You could also change the `findTours` method to using a stack instead of recursion, otherwise you'll get a `StackOverflowError` on boards larger than 66x66. (And if using the heuristic the first choice really works all the time, then you'll need not even that.) – tobias_k Jul 04 '14 at 07:53