[易讀易懂] 騎士遊歷演算法 Knight's Tour Problem
阿新 • • 發佈:2018-11-09
1、問題描述
在一個N*M的棋盤上,在任意位置放置一個騎士,騎士的走"日字",和象棋中的馬一樣。
問該騎士能否不重複遍歷整個棋盤。下面的方法本質還是窮舉,所以就寫成可以計算出共有多少種不同的遍歷方法。
2、分析與思路
根據題意,騎士走的下一步可能在棋盤上有多種選擇(最多8種),需要選擇1種,然後繼續走下去,直到無處可走。
無處可走時有兩種情況:
情況一:成功完成了遍歷,那麼接下來就通過回溯(回到上一步的位置,重新選擇下一步的位置),尋找其他的走法。
情況二:未完成遍歷,接下來還是要通過回溯繼續尋找能夠完成遍歷的走法。
以上可以知這是一個DFS(深度優先搜尋)問題,並且需要回溯。
3、程式碼(Java版)
演算法可以統計出共有多少中不同的遍歷方法,以及多少種失敗的嘗試。並可以給出每次無法前進時棋盤的狀態和每步走法。
/* * Quesion: Kight's tour in n*m board 騎士(棋盤上走日字)遊歷問題,n*m棋盤,從角出發,能否不重複的遍歷整個棋盤,有幾種不同的遍歷方法 * Author: Mingshan Jia * Date: 2018/4/16 * */ /* *┼——┼——┼——┼——┼——┼ *│ │ 4│ │5 │ │ *┼——┼——┼——┼——┼——┼ *│ 3│ │ │ │6 │ *┼——┼——┼——┼——┼——┼ *│ │ │█ │ │ │ 每走一步後按照1~8的位置次序去嘗試走下一步,DFS *┼——┼——┼——┼——┼——┼ *│ 2│ │ │ │7 │ *┼——┼——┼——┼——┼——┼ *│ │ 1│ │8 │ │ *┼——┼——┼——┼——┼——┼ **/ package com.exercise; import java.util.ArrayList; import java.util.Collections; public class Solution { static final int[] xMove= {2,1,-1,-2,-2,-1,1,2}; //xMove和yMove一起組成每一步的偏移量 static final int[] yMove= {-1,-2,-2,-1,1,2,2,1}; public void solveKnightTravel(int n,int m) { int weight=0; //權重值代表第幾步走到該位置; int count=0; //完全遍歷的解數; int countFail=0; //失敗嘗試次數 int[][] A=new int[n][m]; ArrayList<Boolean> resList=new ArrayList<Boolean>(); //儲存無法繼續走時棋盤的狀態(false或true,true代表遍歷完成) knightJump(A,n-1,0,resList,weight); //核心DFS,從(n-1,0)的位置開始跳 count=Collections.frequency(resList, true); //true的次數:不同的遍歷數 countFail=Collections.frequency(resList, false);//false的次數:失敗的走法 System.out.println("一共有"+count+"種不同的遍歷方法"); //3*4的情況下有2種走法,5*5有304種 System.out.println("一共有"+countFail+"次失敗的嘗試"); } public void knightJump(int[][] A, int row, int col,ArrayList<Boolean> resList,int weight){ boolean sign=false; //遍歷完成標誌 if(!displacable(A,row,col)) { //若該位置可走 weight++; A[row][col]=weight; if(nowhereCanGo(A,row,col)) { //走到無路可走時,繪出棋盤的狀態圖,並檢測是否完成 printBoard(A); if(traverseCompleted(A)) { sign= true; } resList.add(sign); System.out.println(sign); } for(int k=0;k<8;k++) { //走下一步 int nextRow=row+xMove[k]; int nextCol=col+yMove[k]; if (nextRow<0 || nextRow>=A.length || nextCol<0 || nextCol>=A[0].length) { continue; } knightJump(A,nextRow,nextCol,resList,weight); } A[row][col]=0; //回溯的操作,來到這裡說明該位置下一步不可走,於是將該位置置0並減少權重,也就是說上一步不選擇走此處,而去嘗試下一個位置 weight--; } } public boolean nowhereCanGo(int[][] A, int row, int col) { //檢測該位置是否無處可走 boolean res=true; for(int i=0;i<8;i++) { res=res&&displacable(A,row+xMove[i],col+yMove[i]); } return res; } public boolean displacable(int[][] A,int row,int col) { //檢測該位置能否放置 if(!(row>=0&&row<A.length&&col>=0&&col<A[0].length&&A[row][col]==0)) return true; else return false; } public void printBoard(int[][] A) { //無法繼續行走時畫出棋盤 for (int i = 0; i < A.length; i++) { for (int j = 0; j < A[0].length; j++) { if (A[i][j] < 10) { System.out.print(" " + A[i][j]); } else System.out.print(A[i][j]); System.out.print(" "); } System.out.println(); } } public boolean traverseCompleted(int[][] A) { //通過圖總權重數判斷是否完成了遍歷 int sum=0; for(int i=0;i<A.length;i++) for(int j=0;j<A[0].length;j++) sum+=A[i][j]; if(sum==(1+A.length*A[0].length)*A.length*A[0].length/2) //如果sum=1+2+...+N*M,則完成遍歷 return true; else return false; } public static void main(String[] args) { Solution solution=new Solution(); solution.solveKnightTravel(3,4); } }
3*3的結果演示如下
7 2 5
4 0 8
1 6 3
false
3 8 5
6 0 2
1 4 7
false
一共有0種不同的遍歷方法
一共有2次失敗的嘗試