1. 程式人生 > 其它 >馬踏棋盤演算法(騎士周遊問題)

馬踏棋盤演算法(騎士周遊問題)

技術標籤:演算法與資料結構騎士周遊問題馬踏棋盤演算法演算法java

一、問題概述

馬踏棋盤演算法也被稱為騎士周遊問題。問題內容是:

將馬隨機放在國際象棋的 n×n 棋盤的某個方格中, 馬按走棋規則(馬走日字)進行移動。要求每個方格只能走一次, 走遍棋盤上全部方格,求可行的路徑。

在這裡插入圖片描述

二、思路分析

馬踏棋盤問題實際上是圖的深度優先搜尋(DFS)的應用。

該問題的解決思想為:

  1. 假設以 V 為起點,首先找出在指定規則下 V 點下一步可能的落點。
  2. 下一步的可能的落點中選擇一個點(假設是 U 點),然後走到 U 點。
  3. 再以 U 點為起點,找出指定規則下 U 點下一步可能的落點。
  4. 在下一步可能的落點中選擇一個點(假設是 W 點),然後走到 W 點。
  5. 如此迴圈下去,直至走完了所有的格子。

需要注意的是,在下一步可能的落點中選擇一個點這個操作具體怎麼進行呢?是隨機選擇一個點的嗎?

並不是隨機的,這裡面用到了貪心演算法:為了讓之後的選擇儘可能少一點,一般會在下一步可能的落點選項中優先選擇這樣的一個點,這個點的特點是它的下一步可能的落點的個數最少

舉個栗子,假如 V0 點的下一步可能的落點有 U0、U1、U2,而 U0 的下一步可能的落點只有 W0,U1下一步可能的落點有 W1、W2,U2 下一步可能的落點有 W3、W4、W5。由於 U0 的下一步可能的落點個數只有 1 個,是 U0、U1、U2 中最少的,因此 V0 下一步將會優先選擇走到 U0。

在這裡插入圖片描述

三、程式碼實現

首先是實現找到下一步可能的落點,我們首先來看下圖:

在這裡插入圖片描述

從圖中可以知道,任意一個點下一步的落點至多有 8 種可能,對於每種可能我們都需要考慮到。因此找到下一步可能的落點實現程式碼如下:

/**
 * 獲取當前點下一步可以走的點的集合
 * @param curPoint  當前點
 */
public static ArrayList<Point> next(Point curPoint){
    ArrayList<Point> points = new ArrayList<>();    // 當前點下一步可選的點集合
    Point point = new
Point(); /* 針對 8 種情況的判斷 */ if ((point.x=curPoint.x-2)>=0 && (point.y=curPoint.y-1)>=0){ // 左 2 上 1 points.add(new Point(point)); } if ((point.x=curPoint.x-1)>=0 && (point.y=curPoint.y-2)>=0){ // 左 1 上 2 points.add(new Point(point)); } if ((point.x=curPoint.x+1)<col && (point.y=curPoint.y-2)>=0){ // 右 1 上 2 points.add(new Point(point)); } if ((point.x=curPoint.x+2)<col && (point.y=curPoint.y-1)>=0){ points.add(new Point(point)); } if ((point.x=curPoint.x-2)>=0 && (point.y=curPoint.y+1)<row){ points.add(new Point(point)); } if ((point.x=curPoint.x-1)>=0 && (point.y=curPoint.y+2)<row){ points.add(new Point(point)); } if ((point.x=curPoint.x+1)<col && (point.y=curPoint.y+2)<row){ points.add(new Point(point)); } if ((point.x=curPoint.x+2)<col && (point.y=curPoint.y+1)<row){ points.add(new Point(point)); } return points; }

接下來,為了方便從下一步可能的落點中選擇出最佳的落點,我們需要對下一步落點集合中的元素按照每個元素的下一步的可能落點個數升序排序,這也是貪心思想的依據:

/**
 * 將集合中的 Point 物件根據其下一步可移動選項的個數升序排序
 * @param points Point 物件集合
 */
public static void sort(ArrayList<Point> points){
    points.sort(new Comparator<Point>() {
        @Override
        public int compare(Point o1, Point o2) {
            return next(o1).size() - next(o2).size();
        }
    });
}

以上是比較核心的程式碼。

下面是馬踏棋盤問題的完整程式碼實現

public class HorseStepBoard {

    private static final int row = 6;
    private static final int col = 6;
    private static int[][] chessboard = new int[row][col];  // 棋盤
    private static int[][] visited = new int[row][col];    // 標識某個點是否已經被訪問。0-未訪問,1-已訪問
    private static boolean finished = false;    // 標識是否全部走完

    public static void main(String[] args) {

        traversalChessboard(0,0,1);

        for (int[] item : chessboard){
            System.out.println(Arrays.toString(item));
        }
    }

    /**
     * 從 (x,y) 開始執行馬踏棋盤演算法
     * @param x 當前點的行座標
     * @param y 當前點的列座標
     * @param step 當前的步數
     */
    public static void traversalChessboard(int x, int y, int step){

        chessboard[x][y] = step;    // 記錄當前點為第幾步到達的
        visited[x][y] = 1; // 標記當前點已經被訪問

        ArrayList<Point> nextPoints = next(new Point(x, y));    // 獲取當前點的下一步移動可選項集合
        sort(nextPoints);   // 將下一步移動的可選項集合按照它們的再下一步的可選項個數升序排序,此處體現了貪心

        /* 只要下一步可移動的選項個數大於 0,就繼續往下走 */
        while (nextPoints.size() > 0){
            Point nextPoint = nextPoints.remove(0); // 排序後,移動到的下一個點的下一步的可選項個數時最小的
            if (visited[nextPoint.x][nextPoint.y] == 0){
                traversalChessboard(nextPoint.x, nextPoint.y, step+1);
            }
        }

        /*
            因為中途可能會走錯,所以會有一個回溯的過程
        */
        if (step < row*col && !finished){   // 如果還未走完
            chessboard[x][y] = 0;   // 回溯時,要把走錯的棋盤座標置 0
            visited[x][y] = 0;  // 訪問標誌置為 0
        }else{
            finished = true;    // 如果步數達到了最大步數,說明走完了,就把標誌位置為 true
        }
    }


    /**
     * 獲取當前點下一步可以走的點的集合
     * @param curPoint  當前點
     */
    public static ArrayList<Point> next(Point curPoint){
        ArrayList<Point> points = new ArrayList<>();    // 當前點下一步可選的點集合
        Point point = new Point();

        /* 針對 8 種情況的判斷 */
        if ((point.x=curPoint.x-2)>=0 && (point.y=curPoint.y-1)>=0){    // 左 2 上 1
            points.add(new Point(point));
        }
        if ((point.x=curPoint.x-1)>=0 && (point.y=curPoint.y-2)>=0){    // 左 1 上 2
            points.add(new Point(point));
        }
        if ((point.x=curPoint.x+1)<col && (point.y=curPoint.y-2)>=0){   // 右 1 上 2
            points.add(new Point(point));
        }
        if ((point.x=curPoint.x+2)<col && (point.y=curPoint.y-1)>=0){
            points.add(new Point(point));
        }
        if ((point.x=curPoint.x-2)>=0 && (point.y=curPoint.y+1)<row){
            points.add(new Point(point));
        }
        if ((point.x=curPoint.x-1)>=0 && (point.y=curPoint.y+2)<row){
            points.add(new Point(point));
        }
        if ((point.x=curPoint.x+1)<col && (point.y=curPoint.y+2)<row){
            points.add(new Point(point));
        }
        if ((point.x=curPoint.x+2)<col && (point.y=curPoint.y+1)<row){
            points.add(new Point(point));
        }
        return points;
    }

    /**
     * 將集合中的 Point 物件根據其下一步可移動選項的個數升序排序
     * @param points Point 物件集合
     */
    public static void sort(ArrayList<Point> points){
        points.sort(new Comparator<Point>() {
            @Override
            public int compare(Point o1, Point o2) {
                return next(o1).size() - next(o2).size();
            }
        });
    }
}

執行結果如下:
在這裡插入圖片描述