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次失败的尝试