回溯算法之马踏棋盘(骑士周游)

马踏棋盘

马踏棋盘(骑士周游)是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)

欢迎关注同名公众号:“我就算饿死也不做程序员”。
交个朋友,一起交流,一起学习,一起进步。在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值