44-骑士周游问题
将马随机放在国际象棋的8×8棋盘的某个方格中,马按走棋规则进行移动。要求每个方格只进入一次,走遍棋盘上全部64个方格。
1. 思路
1.1 步骤 (遍历 + 回溯)
- 创建棋盘 chessBoard,一个二维数组
- 将当前位置设置已访问标记(当前 step),然后根据当前位置,计算出马下一步可走哪些位置(最多 8 个),将这些位置放入一个 ArrayList 当中
- 遍历 ArrayList 中存放的所有位置,递归着走。走得通,step+1,继续递归;走不通,就回溯
- 判断马是否走遍了:使用 step 和 走遍一个棋盘该走的步数 进行比较。如果没达到,则说明没有完成任务
1.2 对 ↑ 进行优化 (贪心)
- 因为马下一步可走位置有时不止一个;所以,制定不同的策略 (即马选择下一步时的算法),会得到不同的结果,效率也会有影响
- 使用 贪心 对原来的算法进行优化
- 确定 {局部最优解} 是什么
- [最优策略] 让马先走 { next.next 可选步骤最少}的 next ← 贪心的局部最优解!
- next.next 可选的步骤越多, 意味着你回溯的次数越多; 选择越少, 回溯的次数越少
- 实现思路
- 根据 <当前nextList 中每个Point的nextList的元素数目> 对 当前nextList 进行升序排序
- 确定 {局部最优解} 是什么
2. 代码实现
public class TravelChessBoard {
private static int X; // 棋盘列数
private static int Y; // 棋盘行数
private static int[][] chessBoard;
// 标记整个棋盘各个位置的访问情况(一维)
private static boolean[] isVisited;
// 所有位置是否都已被访问
private static boolean finished;
public static void main(String[] args) {
X = 8;
Y = 8;
chessBoard = new int[Y][X];
isVisited = new boolean[X * Y];
long start = System.currentTimeMillis();
travelChessBoard(0, 0, 1); // 假定初始位置 (0,0)
long end = System.currentTimeMillis();
System.out.println("共耗时 " + (end - start) + " ms");
// 输出棋盘
for(int[] rows : chessBoard) {
for(int step : rows)
System.out.printf("%3d ", step);
System.out.println();
}
}
/**
* 骑士周游问题
* @param row 马当前的行坐标[0...X-1]
* @param col 马当前的纵坐标[0...Y-1]
* @param step 当前这是第几步 (初始值1; 刚开始把马放到棋盘上就已经算第1步了)
*/
public static void travelChessBoard(int row, int col, int step) {
// 假定可以 (ง •_•)ง
chessBoard[row][col] = step;
isVisited[row * X + col] = true;
ArrayList<Point> nextList = getNextPositions(new Point(col, row));
sortByNextSize(nextList); // 用贪心优化
while(! nextList.isEmpty()) {
Point p = nextList.remove(0);
// 判断该点是否已经访问过
if(! isVisited[p.y * X + p.x])
travelChessBoard(p.y, p.x, step + 1);
} // 说明当前这个位置没有可走的下一步了
if(step < X * Y && ! finished) {
// 实则不行 (*/ω\*)
chessBoard[row][col] = 0;
isVisited[row * X + col] = false;
} else {
// 成了 (●ˇ∀ˇ●)
finished = true;
}
}
public static void sortByNextSize(ArrayList<Point> list) {
// 根据 {当前nextList中每个Point的nextList的元素数目} 对 当前nextList 进行升序排序
list.sort(new Comparator<Point>() {
@Override
public int compare(Point o1, Point o2) {
int size1 = getNextPositions(o1).size();
int size2 = getNextPositions(o2).size();
// ASC
if(size1 < size2)
return -1;
else if(size1 > size2)
return 1;
else
return 0;
}
});
}
/**
* 根据 马 的当前位置, 计算出马的下一步可以走哪些位置
* @param curPoint 封装马当前位置的Point对象
* @return 下一步可走位置组成的集合
*/
public static ArrayList<Point> getNextPositions(Point curPoint) {
ArrayList<Point> nextList = new ArrayList<>();
Point p1 = new Point();
// 能不能走 (5) 的位置
if((p1.x = curPoint.x - 2) >= 0 && (p1.y = curPoint.y - 1) >= 0)
nextList.add(new Point(p1));
// 能不能走 (6) 的位置
if((p1.x = curPoint.x - 1) >= 0 && (p1.y = curPoint.y - 2) >= 0)
nextList.add(new Point(p1));
// 能不能走 (7) 的位置
if((p1.x = curPoint.x + 1) < X && (p1.y = curPoint.y - 2) >= 0)
nextList.add(new Point(p1));
// 能不能走 (0) 的位置
if((p1.x = curPoint.x + 2) < X && (p1.y = curPoint.y - 1) >= 0)
nextList.add(new Point(p1));
// 能不能走 (1) 的位置
if((p1.x = curPoint.x + 2) < X && (p1.y = curPoint.y + 1) < Y)
nextList.add(new Point(p1));
// 能不能走 (2) 的位置
if((p1.x = curPoint.x + 1) < X && (p1.y = curPoint.y + 2) < Y)
nextList.add(new Point(p1));
// 能不能走 (3) 的位置
if((p1.x = curPoint.x - 1) >= 0 && (p1.y = curPoint.y + 2) < Y)
nextList.add(new Point(p1));
// 能不能走 (4) 的位置
if((p1.x = curPoint.x - 2) >= 0 && (p1.y = curPoint.y + 1) < Y)
nextList.add(new Point(p1));
return nextList;
}
}
3. 结果对比
未使用贪心优化:
使用贪心优化后: