这是一个很经典的游戏,4399小游戏:马踏棋盘
马只能走日字,棋盘每个格子只能走一次,现在要求马跳遍整个棋盘,最终回到最初的位置
如果靠人来想挺难的,但是我们有计算机
通常采用回溯法或启发式搜索类算法求解
分析:
即可以写一个方法判断当前位置curPoint的下一步哪些位置可走(8个for循环)
创建数组points用于保存可走位置
进入下一步的顺序是按照我们next方法for循环查找的顺序
package com.company.十种算法.horse;
import java.awt.*;
import java.util.ArrayList;
/**
* Author : zfk
* Data : 10:00
*/
public class HorseChessboard {
//表示棋盘的列数
private static int X;
//表示棋盘的行数
private static int Y;
//标记棋盘的各个位置是否被访问过
private static boolean[][] vistied;
//设置一个属性,标记棋盘的所有位置是否都被访问
private static boolean finished;
public static void main(String[] args) {
//8*8的棋盘
X = 6 ; Y = 6;
//创建棋盘
int[][] chessboard = new int[Y][X];
vistied = new boolean[Y][X];
long start = System.currentTimeMillis();
//初始位置(2,1)
traversalChessboard(chessboard,1,2,1);
long end = System.currentTimeMillis();
System.out.println("执行时间:"+(end - start)+" ms");
for (int[] rows : chessboard){
for (int cols : rows){
System.out.print(cols+"\t");
}
System.out.println();
}
}
/**
* 完成骑士周游问题
* @param chessboard 棋盘
* @param row 马当前位置的行,从0开始
* @param col 马当前位置的列,从0开始
* @param step 表示当前是第几步,初始位置是第1步
*/
public static void traversalChessboard(int[][] chessboard,int row,int col,int step){
//在棋盘位置上标记步数
chessboard[row][col] = step;
//标记该位置已经访问
vistied[row][col] = true;
//获取可以走的下一位置的集合,y是行row,x是列col
ArrayList<Point> nextPoints = next(new Point(col, row));
//遍历nextPoints,只要不等于null就一直遍历
while (!nextPoints.isEmpty()){
//取出下一个可以走的位置
Point p = nextPoints.remove(0);
//判断该点是否已经访问过
if (!vistied[p.y][p.x]){
traversalChessboard(chessboard,p.y,p.x,step + 1);
}
}
//判断能否走完所有棋盘位置
if (step < X * Y && !finished){
//没有完成,需要把棋盘位置置0,且重置成未访问状态
chessboard[row][col] = 0;
vistied[row][col] = false;
}
else {
finished = true;
}
}
/**
* 根据当前的位置(Point),就是马还能走哪些位置(Point),并放入到集合中
* @param curPoint 当前位置
* @return
*/
public static ArrayList<Point> next(Point curPoint){
ArrayList<Point> points = new ArrayList<>();
Point p1 = new Point();
//判断马是否可以走5的位置
if ((p1.x = curPoint.x - 2) >= 0 && (p1.y = curPoint.y - 1) >= 0){
points.add(new Point(p1));
}
//判断马是否可以走6的位置
if ((p1.x = curPoint.x - 1) >= 0 && (p1.y = curPoint.y - 2) >= 0){
points.add(new Point(p1));
}
//判断马是否可以走7的位置
if ((p1.x = curPoint.x + 1) < X && (p1.y = curPoint.y - 2) >= 0){
points.add(new Point(p1));
}
//判断马是否可以走0的位置
if ((p1.x = curPoint.x + 2) < X && (p1.y = curPoint.y - 1) >= 0){
points.add(new Point(p1));
}
//判断马是否可以走1的位置
if ((p1.x = curPoint.x + 2) < X && (p1.y = curPoint.y + 1) < Y){
points.add(new Point(p1));
}
//判断马是否可以走2的位置
if ((p1.x = curPoint.x + 1) < X && (p1.y = curPoint.y + 2) < Y){
points.add(new Point(p1));
}
//判断马是否可以走3的位置
if ((p1.x = curPoint.x - 1) >= 0 && (p1.y = curPoint.y + 2) < Y){
points.add(new Point(p1));
}
//判断马是否可以走4的位置
if ((p1.x = curPoint.x - 2) >= 0 && (p1.y = curPoint.y + 1) < Y){
points.add(new Point(p1));
}
return points;
}
}
结果:
进入游戏验证:
从上面的算法步骤,可以看出有一定的穷举法的思想(不管策略一路走到黑)
我们可以用贪心算法进行一定的优化:
如:
当前步骤,马可以走6个位置,如何选择下一步的位置?
贪心算法:我们要走的下一步位置p1,它的可选下一步位置应当最少;将下一步位置p1集合进行非递减排序
非递减排序:可以有重复值的递增排序,如{1,1,2,2,3,3}
如上图中:
非递减排序{1,2,6,3,5,4}
,按照这个顺序遍历,就可以减少很多次递归
package com.company.十种算法.horse;
import java.awt.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
/**
* Author : zfk
* Data : 10:00
*/
public class HorseChessboard {
//表示棋盘的列数
private static int X;
//表示棋盘的行数
private static int Y;
//标记棋盘的各个位置是否被访问过
private static boolean[][] vistied;
//设置一个属性,标记棋盘的所有位置是否都被访问
private static boolean finished;
public static void main(String[] args) {
//6*6的棋盘
X = 6 ; Y = 6;
//创建棋盘
int[][] chessboard = new int[Y][X];
vistied = new boolean[Y][X];
long start = System.currentTimeMillis();
//初始位置(2,1)
traversalChessboard(chessboard,1,2,1);
long end = System.currentTimeMillis();
System.out.println("执行时间:"+(end - start)+" ms");
for (int[] rows : chessboard){
for (int cols : rows){
System.out.print(cols+"\t");
}
System.out.println();
}
}
/**
* 完成骑士周游问题
* @param chessboard 棋盘
* @param row 马当前位置的行,从0开始
* @param col 马当前位置的列,从0开始
* @param step 表示当前是第几步,初始位置是第1步
*/
public static void traversalChessboard(int[][] chessboard,int row,int col,int step){
//在棋盘位置上标记步数
chessboard[row][col] = step;
//标记该位置已经访问
vistied[row][col] = true;
//获取可以走的下一位置的集合,y是行row,x是列col
ArrayList<Point> nextPoints = next(new Point(col, row));
//对nextPoints进行非递减排序
sort(nextPoints);
//遍历nextPoints,只要不等于null就一直遍历
while (!nextPoints.isEmpty()){
//取出下一个可以走的位置
Point p = nextPoints.remove(0);
//判断该点是否已经访问过
if (!vistied[p.y][p.x]){
traversalChessboard(chessboard,p.y,p.x,step + 1);
}
}
//判断能否走完所有棋盘位置
if (step < X * Y && !finished){
//没有完成,需要把棋盘位置置0,且重置成未访问状态
chessboard[row][col] = 0;
vistied[row][col] = false;
}
else {
finished = true;
}
}
/**
* 根据当前的位置(Point),就是马还能走哪些位置(Point),并放入到集合中
* @param curPoint 当前位置
* @return
*/
public static ArrayList<Point> next(Point curPoint){
ArrayList<Point> points = new ArrayList<>();
Point p1 = new Point();
//判断马是否可以走5的位置
if ((p1.x = curPoint.x - 2) >= 0 && (p1.y = curPoint.y - 1) >= 0){
points.add(new Point(p1));
}
//判断马是否可以走6的位置
if ((p1.x = curPoint.x - 1) >= 0 && (p1.y = curPoint.y - 2) >= 0){
points.add(new Point(p1));
}
//判断马是否可以走7的位置
if ((p1.x = curPoint.x + 1) < X && (p1.y = curPoint.y - 2) >= 0){
points.add(new Point(p1));
}
//判断马是否可以走0的位置
if ((p1.x = curPoint.x + 2) < X && (p1.y = curPoint.y - 1) >= 0){
points.add(new Point(p1));
}
//判断马是否可以走1的位置
if ((p1.x = curPoint.x + 2) < X && (p1.y = curPoint.y + 1) < Y){
points.add(new Point(p1));
}
//判断马是否可以走2的位置
if ((p1.x = curPoint.x + 1) < X && (p1.y = curPoint.y + 2) < Y){
points.add(new Point(p1));
}
//判断马是否可以走3的位置
if ((p1.x = curPoint.x - 1) >= 0 && (p1.y = curPoint.y + 2) < Y){
points.add(new Point(p1));
}
//判断马是否可以走4的位置
if ((p1.x = curPoint.x - 2) >= 0 && (p1.y = curPoint.y + 1) < Y){
points.add(new Point(p1));
}
return points;
}
//根据当前这一步的所有的下一步的选择位置,进行非递减排序
public static void sort(ArrayList<Point> points){
points.sort(new Comparator<Point>() {
@Override
public int compare(Point p1, Point p2) {
//获取p1的下一步的所有位置个数
int count1 = next(p1).size();
int count2 = next(p2).size();
if (count1 < count2){
return -1;
}
else if (count1 == count2){
return 0;
}
else {
return 1;
}
}
});
}
}
执行结果:12ms 对比 112ms,效率高了很多