规则介绍
一道著名的谜题。
象棋中的马的行走方式是斜做走,即向一个方向移动一格后再向垂直方向移动两格为最终落子点,或向一个方向移动两格后再向垂直方向移动一格为最终落子点,如下图,图中白马可向有马轮廓的位置移动。
算法的规则是:马已经落子的格子不能再次落子,即下图 1标识的格不能在落子,在以上规则的前提下将所有棋盘的格子落子一次,这就是马踏棋盘。
算法思路
运用递归算法和回溯算法实现,具体思路如下图
代码实现
代码如下
/** 棋盘 */
private static int[][] checkerboard ;
/** 行走轨迹 */
private static List<int[]> historyStep = new LinkedList<>();
/** 行走最大轨迹 */
private static List<int[]> maxHistoryStep = new LinkedList<>();
/** 小脚 */
private static final int SMALL_STEPS = 1;
/** 大脚 */
private static final int BIG_STEPS = 2;
/** 能落子的最大步数 */
private static int maxStep;
/** 新建棋盘 */
private static void createCheckerboard(int row){
checkerboard = new int[row][row];
maxStep = row*row;
}
/** 向左上左移动 */
private static int[] liftUpLiftMove(int[] stepBefore){
int[] stepAfter = {stepBefore[0] - BIG_STEPS,stepBefore[1] - SMALL_STEPS};
// x/y小于0表示出界,棋盘上对应的点不等于0表示已落过子
if ( stepAfter[0] < 0 || stepAfter[1] < 0 ){
return null;
}
return stepAfter;
}
/** 向左上右移动 */
private static int[] liftUpRightMove(int[] stepBefore){
int[] stepAfter = {stepBefore[0] - SMALL_STEPS,stepBefore[1] - BIG_STEPS};
if ( stepAfter[0] < 0 || stepAfter[1] < 0 ){
return null;
}
return stepAfter;
}
/** 向右上左移动 */
private static int[] rightUpLiftMove(int[] stepBefore){
int[] stepAfter = {stepBefore[0] + SMALL_STEPS,stepBefore[1] - BIG_STEPS};
// x大于等于棋盘格数或y小于0表示出界,棋盘上对应的点不等于0表示已落过子
if ( stepAfter[0] >= checkerboard.length || stepAfter[1] < 0 ){
return null;
}
return stepAfter;
}
/** 向右上右移动 */
private static int[] rightUpRightMove(int[] stepBefore){
int[] stepAfter = {stepBefore[0] + BIG_STEPS,stepBefore[1] - SMALL_STEPS};
if ( stepAfter[0] >= checkerboard.length || stepAfter[1] < 0 ){
return null;
}
return stepAfter;
}
/** 向左下左移动 */
private static int[] liftDownLiftMove(int[] stepBefore){
int[] stepAfter = {stepBefore[0] - BIG_STEPS,stepBefore[1] + SMALL_STEPS};
if ( stepAfter[0] < 0 || stepAfter[1] >= checkerboard.length ){
return null;
}
return stepAfter;
}
/** 向左下右移动 */
private static int[] liftDownRightMove(int[] stepBefore){
int[] stepAfter = {stepBefore[0] - SMALL_STEPS,stepBefore[1] + BIG_STEPS};
if ( stepAfter[0] < 0 || stepAfter[1] >= checkerboard.length){
return null;
}
return stepAfter;
}
/** 向右下左移动 */
private static int[] rightDownLiftMove(int[] stepBefore){
int[] stepAfter = {stepBefore[0] + SMALL_STEPS,stepBefore[1] + BIG_STEPS};
if ( stepAfter[0] >= checkerboard.length || stepAfter[1] >=checkerboard.length){
return null;
}
return stepAfter;
}
/** 向右下右移动 */
private static int[] rightDownRightMove(int[] stepBefore){
int[] stepAfter = {stepBefore[0] + BIG_STEPS,stepBefore[1] + SMALL_STEPS};
if ( stepAfter[0] >= checkerboard.length || stepAfter[1] >= checkerboard.length){
return null;
}
return stepAfter;
}
/** 计算下一步落点 */
private static boolean nextStep(int[] drop){
// 如果该位置已落过子,则直接退出
if (checkerboard[drop[1]][drop[0]] != 0){
return false;
}
// 记录落子位置
historyStep.add(drop);
checkerboard[drop[1]][drop[0]] = 1;
// 达到最大落子数
if (historyStep.size() == maxStep){
maxHistoryStep.clear();
maxHistoryStep.addAll(historyStep);
return true;
}
// 记录最大落子方式
if (historyStep.size() > maxHistoryStep.size()){
maxHistoryStep.clear();
maxHistoryStep.addAll(historyStep);
}
int [] stepNext ;
if (!Objects.isNull( stepNext = liftUpLiftMove(drop))){
// 继续落下一步
if (nextStep(stepNext)) {
// 如果已到最大落子数则直接返回
return true;
}
}
if (!Objects.isNull( stepNext = liftUpRightMove(drop))){
// 继续落下一步
if (nextStep(stepNext)){
// 如果已到最大落子数则直接返回
return true;
}
}
if (!Objects.isNull( stepNext = rightUpLiftMove(drop))){
// 继续落下一步
if (nextStep(stepNext)){
// 如果已到最大落子数则直接返回
return true;
}
}
if (!Objects.isNull( stepNext = rightUpRightMove(drop))){
// 继续落下一步
if (nextStep(stepNext)){
// 如果已到最大落子数则直接返回
return true;
}
}
if (!Objects.isNull( stepNext = liftDownLiftMove(drop))){
// 继续落下一步
if (nextStep(stepNext)){
// 如果已到最大落子数则直接返回
return true;
}
}
if (!Objects.isNull( stepNext = liftDownRightMove(drop))){
// 继续落下一步
if (nextStep(stepNext)){
// 如果已到最大落子数则直接返回
return true;
}
}
if (!Objects.isNull( stepNext = rightDownLiftMove(drop))){
// 继续落下一步
if (nextStep(stepNext)){
// 如果已到最大落子数则直接返回
return true;
}
}
if (!Objects.isNull( stepNext = rightDownRightMove(drop))){
// 继续落下一步
if (nextStep(stepNext)){
// 如果已到最大落子数则直接返回
return true;
}
}
// 落子失败,移除记录
historyStep.remove(historyStep.size()-1);
checkerboard[drop[1]][drop[0]] = 0;
soutListAllHis();
return false;
}
public static void main(String[] args) {
int lattice = 6;
createCheckerboard(lattice);
int xStart = (int)(Math.random()*lattice);
int yStart = (int)(Math.random()*lattice);
xStart = 3;
yStart = 2;
int [] stepStart = {xStart,yStart};
System.out.println("Arrays.toString(stepStart) = " + Arrays.toString(stepStart));
nextStep(stepStart);
soutListAll();
}
private static void soutListAll() {
System.out.println("maxHistoryStep.size() = " + maxHistoryStep.size());
maxHistoryStep.forEach(a-> System.out.print(Arrays.toString(a) + ","));
System.out.println();
}
实现效果
如下是行走路径,棋盘从左至有是x轴方向,从上到下是y轴方向,[0,0]表示右上角的第一格子
[3, 2],[1, 1],[3, 0],[2, 2],[0, 1],[2, 0],[4, 1],[5, 3],[4, 5],[3, 3],[1, 4],[0, 2],[1, 0],[3, 1],[5, 0],[4, 2],[5, 4],[3, 5],[2, 3],[0, 4],[1, 2],[0, 0],[2, 1],[4, 0],[5, 2],[4, 4],[2, 5],[1, 3],[0, 5],[2, 4],[0, 3],[1, 5],[3, 4],[5, 5],[4, 3],[5, 1]
行走路线如下图 ,五角星是开始所在位置
温馨提示
计算时,棋盘不要太大,已不能太小(不能小于3),棋盘太多计算速度非常慢,以6*6的棋盘为例,假如最终解法的落子方式都是在行走的最后一种方式(这种方式不可能存在),则需计算836次落子方式,即2108次落子方式,这个数量级有段恐怖就过多解释了。