45-骑士周游问题

Posted liujiaqi1101

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了45-骑士周游问题相关的知识,希望对你有一定的参考价值。

1. 问题简述

将马随机放在国际象棋的8×8棋盘的某个方格中,马按走棋规则进行移动。要求每个方格只进入一次,走遍棋盘上全部64个方格
技术图片

2. 思路

2.1 步骤 (遍历 + 回溯)

  1. 创建棋盘chessBoard,一个二维数组
  2. 将当前位置设置已访问标记(当前 step),然后根据当前位置,计算出马下一步可走哪些位置(最多 8 个),将这些位置放入一个 ArrayList 当中
  3. 遍历 ArrayList 中存放的所有位置,递归着走。走得通,step+1,继续递归;走不通,就回溯
  4. 判断马是否走遍了:使用 step 和 走遍一个棋盘该走的步数 进行比较。如果没达到,则说明没有完成任务

2.2 优化 (贪心)

  • 因为马下一步可走位置有时不止一个;所以,制定不同的策略 (即马选择下一步时的算法),会得到不同的结果,效率也会有影响
  • 使用 贪心 对原来的算法进行优化
    • 确定 {局部最优解} 是什么
      • [最优策略] 让马先走 { next.next 可选步骤最少}的 next ← 贪心的局部最优解!
      • next.next 可选的步骤越多, 意味着你回溯的次数越多; 选择越少, 回溯的次数越少
    • 实现思路
      • 根据 <当前nextList 中每个Point的nextList的元素数目> 对 当前nextList 进行升序排序

3. 代码实现

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;
    }
}

4. 结果显示

4.1 未使用贪心优化

技术图片

4.2 使用贪心优化后

技术图片

以上是关于45-骑士周游问题的主要内容,如果未能解决你的问题,请参考以下文章

马踏棋盘(骑士周游问题)

骑士周游问题

第28章 算法优化体验课 - 骑士周游问题

小甲鱼数据结构和算法--马踏棋盘(骑士周游问题)

程序员必会十大算法之骑士周游问题

算法马踏棋盘算法 骑士走周游算法