1. 程式人生 > >[易讀易懂] 騎士遊歷演算法 Knight's Tour Problem

[易讀易懂] 騎士遊歷演算法 Knight's Tour Problem

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次失敗的嘗試