[易读易懂] 骑士游历算法 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次失败的尝试

    原文作者:骑士周游问题
    原文地址: https://blog.csdn.net/MingshanJia/article/details/79970039
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞