簡體   English   中英

騎士之旅需要回溯

[英]Need of backtracking in Knight’s tour

為什么我們需要回溯騎士之旅的問題? 我們可以只使用遞歸嗎? 我嘗試這樣做,但是給出了錯誤的答案,我無法弄清楚代碼或邏輯哪里出了問題。

import java.util.*;
public class Solution {

    public static void main(String[] args) {
        Scanner s=new Scanner(System.in);
        int[][] ans=new int[8][8];
        for(int d=0;d<8;d++){
            for(int e=0;e<8;e++){
                ans[d][e]=-1;
            }
        }
        int[] x={2,1,-2,1,-1,-2,-1,2};
        int[] y={1,2,1,-2,-2,-1,2,-1};
        func(x,y,ans,7,7,1);
    }

    public static void func(int[] x,int[] y,int[][] ans,int i,int j,int count){
        if(count==64){
            for(int d=0;d<8;d++){
                for(int e=0;e<8;e++){
                    System.out.print(ans[d][e]+" ");
                }
                System.out.println();
            }
        }
        if(ans[i][j]!=-1){
            return;
        }
        else{
            ans[i][j]=count;
            for(int u=0;u<8;u++){
                if(i+x[u]>=0 && i+x[u]< 8 && j+y[u]>=0 && j+y[u]<8){
                    func(x,y,ans,i+x[u],j+y[u],count+1);
                }
            }
        }
        return;
    }
}

騎士巡回問題中需要回溯至關重要。 您尚未在代碼中實現回溯的事實是其無法正常工作的主要原因。

要修復它,您必須至少清除方法末尾的位置。 即,當您執行ans[i][j] = count必須將其與ans[i][j] = -1平衡以清除該正方形-您未這樣做。

這不是代碼的唯一問題,而是主要問題。

存在回溯的替代方法。 您可以以遞歸級別創建一個新板,它是當前板的副本,但是通常認為這浪費了內存。

這是我最終得到的代碼:

// The board size.
private static final int SIZE = 8;
// Contents of board squares when empty.
private static final int EMPTY = -1;
// The 8 possible x,y moves for the knight.
private static final int[] x = {2, 1, -2, 1, -1, -2, -1, 2};
private static final int[] y = {1, 2, 1, -2, -2, -1, 2, -1};

public static void printBoard(int[][] board) {
    // Print out the board.
    for (int d = 0; d < SIZE; d++) {
        for (int e = 0; e < SIZE; e++) {
            System.out.print(board[d][e] + " ");
        }
        System.out.println();
    }

}

public static boolean knightsMove(int[][] board, int i, int j, int count) {
    boolean finished = false;
    // Only step onto empty squares that are on the board.
    if (i >= 0 && i < SIZE && j >= 0 && j < SIZE && board[i][j] == EMPTY) {
        // Mark my route.
        board[i][j] = count;
        // Count up.
        count += 1;
        // Are we done?
        if (count > SIZE * SIZE) {
            System.out.println("=== Solution ===");
            // Print the solution.
            printBoard(board);
            // Finished now.
            return true;
        }
        if (count == SIZE * SIZE) {
            // Nearly there - print something to show progress.
            System.out.println("=== Try === (" + i + "," + j + ")=" + count);
            // Print the current state.
            printBoard(board);
        }
        // Look around to try all moves from here.
        for (int u = 0; u < x.length && !finished; u++) {
            // Try the next place.
            finished = knightsMove(board, i + x[u], j + y[u], count);
        }
        // Clear my trail - you missed this.
        board[i][j] = EMPTY;
    } else {
        // Cannot go there.
        return false;
    }
    return finished;
}

public static void knightsMove() {
    int[][] board = new int[SIZE][SIZE];
    // Clear to EMPTY.
    for (int d = 0; d < board.length; d++) {
        for (int e = 0; e < board[d].length; e++) {
            board[d][e] = EMPTY;
        }
    }
    // Start at (7,7) with count 1.
    knightsMove(board, 7, 7, 1);
}

耐心等待-需要花費很長時間才能完成-如您所料。 在我的電腦上,大約需要20分鍾才能到達:

=== Solution ===
29 42 51 60 27 22 17 24 
50 59 28 41 52 25 20 15 
43 30 61 26 21 16 23 18 
62 49 58 53 40 19 14 7 
57 44 31 48 33 8 3 10 
36 63 34 39 54 11 6 13 
45 56 37 32 47 2 9 4 
64 35 46 55 38 5 12 1

我進行了以下更改:

  1. 添加了注釋-您應該始終注釋您的代碼。
  2. xy數組設為static -不需要將其作為參數。
  3. 將所有檢查(機上和空的)放在一個地方。
  4. 將木板打印在未命中的地方,以保持您的娛樂感。
  5. 使用static final EMPTY表示為空。
  6. 完成后返回boolean結果以在那里停止搜索。
  7. 添加了回溯。
  8. 使用更好的名稱,例如boardknightsMove
  9. 電路板尺寸使用恆定的SIZE
  10. 可能有一些小的調整。

請不要使用此代碼對你的功課-你將學會做這個自己,了解為什么每個部分的工作更多。

遞歸是當函數/方法調用自身時。 您的func方法會自行調用,因此您的程序使用了遞歸。 (順便說一句:標識符應該提供信息。調用function沒有什么用; tour會更好)。

回溯是當您嘗試一個分支之后嘗試另一個分支,並僅在完成當前分支后才檢查下一個分支,從而探討了分支問題。 您的程序會執行此操作,因此它正在使用回溯。

回溯也可以通過堆棧和循環來實現,如下所示:

 State initialState = new State(); // empty board, no moves
 Stack<State> stack = new Stack<State>();
 stack.add(initialState);
 while ( ! stack.isEmpty()) {
      State current = stack.pop();
      for (State next : current.getSuccesorStates()) {
           if (next.isLegal()) {
                 stack.add(next);
           }
      }
 }

這不使用遞歸。 但是,像代碼中那樣,通過遞歸(使用程序堆棧)執行此操作的頻率更高。

所以:您的問題需要回溯才能解決。 盡管您正在使用它,但它不需要遞歸。

額外的功勞:您的代碼失敗,因為您在完成對單元格的訪問后應該取消標記它們。 這樣,當您嘗試下一個分支(該分支不會跳到那里)時,該單元格就可以自由跳轉。 請參閱//MISSING注釋。

    if(ans[i][j]!=-1){
        return;
    }
    else{
        ans[i][j]=count;
        for(int u=0;u<8;u++){
            if(i+x[u]>=0 && i+x[u]< 8 && j+y[u]>=0 && j+y[u]<8){
                func(x,y,ans,i+x[u],j+y[u],count+1);
            }
        }
    }
    // MISSING: un-mark last marking, so that other branches can be explored
    ans[i][j]=-1
    return;

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM