回溯算法之馬踏棋盤(騎士周遊)

馬踏棋盤

馬踏棋盤(騎士周遊)是4399的一個小遊戲,遊戲規則很簡單:
玩過象棋嗎?這款和國內外的象棋都不一樣,裏面只有一隻馬在跳,但是跳過的格子都會出現數字來圍困你,所以記得不要被這些數字圍住,否則遊戲就結束了。來挑戰一下新玩法吧。
雖然遊戲規則很簡單,但是如果想要通關的話,卻是很有難度的,下面我們通過算法來尋找通關方法。
在這裏插入圖片描述

回溯算法

回溯算法的思想其實很簡單:我們在尋找解決方案,當遇到有多種選擇方案時,我們先選擇一個方案進行嘗試,如果走不通,則返回選擇另外的方案進行嘗試。
對應到我們的遊戲中,則:如下圖,我們所處現在的位置有8種格子可走,我們可以先選擇編號爲0的格子嘗試走下去,如果無法通關,我們可以重新返回到當前位置,嘗試走編號爲1的格式,仍然不行再返回選擇2…
在這裏插入圖片描述
但是,當我們走了格子0後,我們又會有多種選擇,這其實是一個遞歸過程了。

代碼實現

import numpy as np


class KnightTour:
    """
    馬踏棋盤(騎士周遊)小遊戲,以"日"的走法將整個棋盤走滿,並且不能重複走
    """

    def __init__(self, size):
        self.X = size
        self.Y = size
        # 棋盤的二維數組,用於記錄每一步所走的位置
        self.chessboard = np.zeros([self.X, self.Y], dtype=np.int32)
        # 用於記錄每個位置是否已經走過
        self.visited = np.full([self.X, self.Y], False, dtype=bool)
        # 是否完成遊戲的標記
        self.finished = False
        # 用於存儲所有解法
        self.all_solution = []

    def solution(self, x, y, get_all=False):
        """
        獲取馬踏棋盤的走法
        :param x: 出發位置的橫座標,從0開始
        :param y: 出發位置的縱座標
        :param get_all: 是否獲取所有走法
        :return:
        """
        if get_all:
            self.tour_all(x, y, 1)
            print('總共有 %d 種解法' % (len(self.all_solution)))
            for i in range(len(self.all_solution)):
                print('第%d種解法:' % (i + 1))
                print(self.all_solution[i])
        else:
            self.tour(x, y, 1)
            print(self.chessboard)

    def tour(self, x, y, step):
        """
        通過遞歸和回溯的方法尋找解法,找到一種解決即停止
        :param x: 當前位置的橫座標
        :param y: 當前位置的縱座標
        :param step: 當前的步數
        :return:
        """
        self.visited[x][y] = True
        self.chessboard[x][y] = step
        nexts = self.get_next(x, y)
        # 通過貪心算法進行優化,按照下一步的所有可走位置的下一步的可走位置數量進行升序排序
        # 這樣先進行遞歸的數量會比較少
        nexts.sort(key=lambda x: len(self.get_next(x[0], x[1])))
        for p in nexts:
            if not self.visited[p[0]][p[1]]:
                self.tour(p[0], p[1], step+1)

        if (step < self.X * self.Y) & (not self.finished):  # 如果已經無路可走但仍未結束,或者處於回溯過程中
            self.chessboard[x][y] = 0
            self.visited[x][y] = False
        else:  # 已經完成遊戲
            self.finished = True

    def tour_all(self, x, y, step):
        """
        通過遞歸和回溯的方法尋找所有解法
        :param x: 當前位置的橫座標
        :param y: 當前位置的縱座標
        :param step: 當前的步數
        :return:
        """
        self.visited[x][y] = True
        self.chessboard[x][y] = step
        nexts = self.get_next(x, y)
        # 通過貪心算法進行優化,按照下一步的所有可走位置的下一步的可走位置數量進行升序排序
        # 這樣先進行遞歸的數量會比較少
        # nexts.sort(key=lambda x: len(self.get_next(x[0], x[1])))
        for p in nexts:
            if not self.visited[p[0]][p[1]]:
                self.tour_all(p[0], p[1], step+1)

        # 已經按照規則將棋盤走滿,獲得一種解法
        if step == self.X * self.Y:
            # print("====")
            # print(self.chessboard)
            self.all_solution.append(self.chessboard.copy())

        # 開始回溯,尋找其他解法
        self.chessboard[x][y] = 0
        self.visited[x][y] = False

    def get_next(self, x, y):
        """
        返回當前位置的下一步所有可走位置集合
        :return:
        """
        res = []
        if (x - 1 >= 0) & (y - 2 >= 0):
            res.append([x - 1, y - 2])

        if (x - 2 >= 0) & (y - 1 >= 0):
            res.append([x - 2, y - 1])

        if (x + 1 < self.X) & (y - 2 >= 0):
            res.append([x + 1, y - 2])

        if (x + 2 < self.X) & (y - 1 >= 0):
            res.append([x + 2, y - 1])

        if (x - 2 >= 0) & (y + 1 < self.Y):
            res.append([x - 2, y + 1])

        if (x - 1 >= 0) & (y + 2 < self.Y):
            res.append([x - 1, y + 2])

        if (x + 2 < self.X) & (y + 1 < self.Y):
            res.append([x + 2, y + 1])

        if (x + 1 < self.X) & (y + 2 < self.Y):
            res.append([x + 1, y + 2])

        return res


if __name__ == '__main__':
    game = KnightTour(5)
    game.solution(0, 0, True)

歡迎關注同名公衆號:“我就算餓死也不做程序員”。
交個朋友,一起交流,一起學習,一起進步。在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章