Python 实现递归法解决迷宫问题的示例代码
作者:HibiscusToYou 发布时间:2021-01-31 08:14:23
标签:Python,递归,迷宫
迷宫问题
问题描述:
迷宫可用方阵 [m, n] 表示,0 表示可通过,1 表示不能通过。若要求左上角 (0, 0) 进入,设计算法寻求一条能从右下角 (m-1, n-1) 出去的路径。
示例图:
此示例图基本参数为:
m:对应
x 轴n:对应 y 轴
绿色线代表期望输出的路径
算法思路
标记当前所在位置
如果此时所在位置为终点,说明可以到达终点,退出递归;
否则,则存在 4 种可能的移动方向即上、下、左、右,遍历这 4 个方向,如果这 4 个方向存在相邻值为 0 的点,则将当前点坐标标记为该相邻值为 0 的点坐标,进入递归
直观理解为:
上图中红色圈的相邻值为 0 的点有 3 个,则会依次遍历这 3 个点寻求某一条件并进入递归
实现过程
标记函数
def mark(maze, pos):
"""
标记函数,用来标记历史走过的位置
:param maze: 一个 m*n 大小的二维矩阵迷宫
:param pos: 当前需要标记的位置坐标 pos = (x, y),x = pos[0], y = pos[1]
"""
maze[pos[0]][pos[1]] = 2 # 将走过的位置标记为 2
移动函数
def move(maze, pos):
"""
移动函数,用来测试当前位置是否可继续移动,移动条件为当前位置为 0
:param maze: 一个 m*n 大小的二维矩阵迷宫
:param pos: 当前需要标记的位置坐标 pos = (x, y),x = pos[0], y = pos[1]
:return: bool 类型
"""
return maze[pos[0]][pos[1]] == 0
核心函数 - 路径查找函数
def find_path(maze, start, end):
"""
路径查找函数
:param maze: 一个 m*n 大小的二维矩阵迷宫
:param start: 起始点位置坐标,start = (1, 1)
:param end: 终点坐标,end = (m, n)
:return: bool 类型
"""
mark(maze, start) # 将起始位置标记
if start == end: # 路径查找(递归)终止条件为到达终点
move_path.append(start)
return True
# 未到达终点时,存在 4 种可能的移动方向,即上 (-1, 0),下 (1, 0),左 (0, -1),右 (0, 1)
move_direction = [
(-1, 0), (1, 0), (0, -1), (0, 1)
]
direction = ['↑', '↓', '←', '→']
for i in range(4): # 遍历 4 种可能的方向
next_start = (start[0] + move_direction[i][0], start[1] + move_direction[i][1]) # 下一个可能的起始点坐标
if move(maze, next_start): # 找出存在 0 即可移动的下一个起始点坐标,进入递归
if find_path(maze, next_start, end):
# 这里之所以仍然添加起始点坐标是因为当查询到下一个位置就是终点或者可到达终点时记录此时位置
move_path.append(start)
path_direction.append(direction[i]) # 记录路径方向
return True
return False # 遍历递归了 4 种可能方向后仍不能到达终点则说明无法走出迷宫
算法到这里基本上已经算完成,整体上不算太复杂
美化输出
生成带有移动路径数据的迷宫矩阵
def path_maze(maze, directions_map):
"""
生成带有移动路径的迷宫矩阵
:param maze: 一个 m*n 大小的二维矩阵迷宫
:param directions_map: 一个记录移动方向坐标的字典,有 ↑,↓,←,→ 4 个元素
:return: path_maze
"""
n, m = len(maze[0]), len(maze)
for x in range(1, m-1):
for y in range(1, n-1):
maze[x][y] = maze[x][y] if maze[x][y] != 2 else 0 # 将标记的 2 还原为 0
for x in range(m):
for i in range(1, 2 * n - 1, 2):
maze[x].insert(i, ' ') # 重初始化 maze,在每两个元素间插入占位符 ' ' 3 个空格
for x in range(1, 2 * m - 1, 2):
maze.insert(x, [' ', ' '] * (n-1) + ['']) # 插入两种空格占位符 ' ' 和 ' '
for direction in directions_map:
for directions_position in directions_map[direction]:
i, j = directions_position
i = 2 * i
j = 2 * j
if direction == "↑":
maze[i - 1][j] = "↑"
if direction == "↓":
maze[i + 1][j] = "↓"
if direction == "←":
maze[i][j] = " ← "
if direction == "→":
maze[i][j + 1] = " → "
return maze
生成的带路径数据的迷宫矩阵部分数据截图如下:
美化打印迷宫矩阵
def print_maze(maze, text='原始迷宫为:', end1=' ', end2='\n\n', xs=0, xe=0, ys=0, ye=0):
"""
输出迷宫矩阵,非必要,可注释删除
:param maze: 一个 m*n 大小的二维矩阵迷宫
:param text: 输出提示
:param end1: 控制每行尾结束符
:param end2: 控制每行尾结束符
:param xs: 控制是否输出最上方的 1 环,0 为输出,1 为不输出
:param xe: 控制是否输出最上方的 1 环,0 为输出,1 为不输出
:param ys: 控制是否输出最上方的 1 环,0 为输出,1 为不输出
:param ye: 控制是否输出最上方的 1 环,0 为输出,1 为不输出
"""
print(text)
n, m = len(maze[0]), len(maze)
for x in range(xs, m-xe):
for y in range(ys, n-ye):
print(maze[x][y], end=end1)
print(end=end2)
最终输出结果:
效果尚可
完整代码
# -*- coding: utf-8 -*-
"""
Created on 2020/1/11 10:51
Author : zxt
File : maze_recursion.py
Software: PyCharm
"""
from random import randint
def mark(maze, pos):
"""
标记函数,用来标记历史走过的位置
:param maze: 一个 m*n 大小的二维矩阵迷宫
:param pos: 当前需要标记的位置坐标 pos = (x, y),x = pos[0], y = pos[1]
"""
maze[pos[0]][pos[1]] = 2 # 将走过的位置标记为 2
def move(maze, pos):
"""
移动函数,用来测试当前位置是否可继续移动,移动条件为当前位置为 0
:param maze: 一个 m*n 大小的二维矩阵迷宫
:param pos: 当前需要标记的位置坐标 pos = (x, y),x = pos[0], y = pos[1]
:return: bool 类型
"""
return maze[pos[0]][pos[1]] == 0
move_path = [] # 记录能成功到达出口的移动路径坐标
path_direction = [] # 记录能成功到达出口的移动路径方向
def find_path(maze, start, end):
"""
路径查找函数
:param maze: 一个 m*n 大小的二维矩阵迷宫
:param start: 起始点位置坐标,start = (1, 1)
:param end: 终点坐标,end = (m, n)
:return: bool 类型
"""
mark(maze, start) # 将起始位置标记
if start == end: # 路径查找(递归)终止条件为到达终点
move_path.append(start)
return True
# 未到达终点时,存在 4 种可能的移动方向,即上 (-1, 0),下 (1, 0),左 (0, -1),右 (0, 1)
move_direction = [
(-1, 0), (1, 0), (0, -1), (0, 1)
]
direction = ['↑', '↓', '←', '→']
for i in range(4): # 遍历 4 种可能的方向
next_start = (start[0] + move_direction[i][0], start[1] + move_direction[i][1]) # 下一个可能的起始点坐标
if move(maze, next_start): # 找出存在 0 即可移动的下一个起始点坐标,进入递归
if find_path(maze, next_start, end):
# 这里之所以仍然添加起始点坐标是因为当查询到下一个位置就是终点或者可到达终点时记录此时位置
move_path.append(start)
path_direction.append(direction[i]) # 记录路径方向
return True
return False # 遍历递归了 4 种可能方向后仍不能到达终点则说明无法走出迷宫
def gen_maze(m, n):
"""
生成随机迷宫阵列
:param m: int 类型
:param n: int 类型
:return: maze
"""
m += 2
n += 2 # m 和 n 均 +2 是为了构造最外层的 1
maze = [[1 for i in range(n)] for j in range(m)] # 初始化大小为 m * n,值全为 1 的二维矩阵
for x in range(1, m-1):
for y in range(1, n-1):
"""
这里 x, y 取值范围为 x ∈ [1, m-1),y ∈ [1, n-1) 是因为我们令此迷宫的最外层(四周)均为 1,如:
考察 3 * 3 矩阵,一种可能的阵列为:
[
_ |←--- n:y ---→|
↑ [1, 1, 1, 1, 1],
| [1, 0, 1, 0, 1],
m:x [1, 0, 0, 1, 1],
| [1, 1, 0, 0, 1],
↓ [1, 1, 1, 1, 1]
]
"""
if (x == 1 and y == 1) or (x == m - 2 and y == n - 2):
maze[x][y] = 0 # 起始点和终点必为 0
else:
maze[x][y] = randint(0, 1) # 在最外层均为 1 的情况下内部随机取 0,1
return maze
def print_maze(maze, text='原始迷宫为:', end1=' ', end2='\n\n', xs=0, xe=0, ys=0, ye=0):
"""
输出迷宫矩阵,非必要,可注释删除
:param maze: 一个 m*n 大小的二维矩阵迷宫
:param text: 输出提示
:param end1: 控制每行尾结束符
:param end2: 控制每行尾结束符
:param xs: 控制是否输出最上方的 1 环,0 为输出,1 为不输出
:param xe: 控制是否输出最上方的 1 环,0 为输出,1 为不输出
:param ys: 控制是否输出最上方的 1 环,0 为输出,1 为不输出
:param ye: 控制是否输出最上方的 1 环,0 为输出,1 为不输出
"""
print(text)
n, m = len(maze[0]), len(maze)
for x in range(xs, m-xe):
for y in range(ys, n-ye):
print(maze[x][y], end=end1)
print(end=end2)
def path_maze(maze, directions_map):
"""
生成带有移动路径的迷宫矩阵
:param maze: 一个 m*n 大小的二维矩阵迷宫
:param directions_map: 一个记录移动方向坐标的字典,有 ↑,↓,←,→ 4 个元素
:return: path_maze
"""
n, m = len(maze[0]), len(maze)
for x in range(1, m-1):
for y in range(1, n-1):
maze[x][y] = maze[x][y] if maze[x][y] != 2 else 0 # 将标记的 2 还原为 0
for x in range(m):
for i in range(1, 2 * n - 1, 2):
maze[x].insert(i, ' ') # 重初始化 maze,在每两个元素间插入占位符 ' ' 3 个空格
for x in range(1, 2 * m - 1, 2):
maze.insert(x, [' ', ' '] * (n-1) + ['']) # 插入两种空格占位符 ' ' 和 ' '
for direction in directions_map:
for directions_position in directions_map[direction]:
i, j = directions_position
i = 2 * i
j = 2 * j
if direction == "↑":
maze[i - 1][j] = "↑"
if direction == "↓":
maze[i + 1][j] = "↓"
if direction == "←":
maze[i][j] = " ← "
if direction == "→":
maze[i][j + 1] = " → "
return maze
def main():
# maze = gen_maze(m=10, n=12)
maze = \
[
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1],
[1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1],
[1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1],
[1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1],
[1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1],
[1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1],
[1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1],
[1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1],
[1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1],
[1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
] # 输入样式矩阵,这里最外层用 1 环包围住,目的是方便后续的处理,可以用 gen_maze() 函数自生成
print_maze(maze)
if find_path(maze, start=(1, 1), end=(10, 12)):
mp = move_path[::-1]
pd = path_direction[::-1]
# 这里 pos[0] 和 pos[1] 都要 -1 是因为原来的递归计算中存在最外层的 1 环
print('坐标移动顺序为:', [(pos[0]-1, pos[1]-1) for pos in mp])
path_direction_map = {
'↑': [],
'↓': [],
'←': [],
'→': []
} # 路径方向的映射表
for i in range(len(pd)):
path_direction_map[pd[i]].append(mp[i])
maze = path_maze(maze, path_direction_map)
print_maze(maze, text='迷宫移动路径为:', end1='', end2='\n', xs=1, xe=1, ys=1, ye=1)
else:
print('此迷宫无解')
if __name__ == '__main__':
main()
来源:https://blog.csdn.net/qq_40772371/article/details/103938992


猜你喜欢
- <span id="tiao">3</span><a href="javascr
- git还原到某次commit并强制推送远程不可逆提交一、reset1.git log查看提交记录git log2.选择某次提交的commit
- 一个大于1的自然数,除了1和它本身外,不能被其他自然数(质数)整除(2, 3, 5, 7等),换句话说就是该数除了1和它本身以外不再有其他的
- 1. 使用readline模块逐行读取流数据1.1. 创建Interface对象在readline模块中,通过Interface对象的使用来
- 服务端渲染及session鉴权服务端渲染服务端渲染简单来说就是前端页面是由服务器通过字符串拼接动态生成的,客户端不需要额外通过Ajax请求参
- 1.实现效果2.实现原理echarts官网:series-lines注意:流动特效只支持非平滑曲线(smooth:false)series-
- GO的锁和原子操作分享上次我们说到协程,我们再来回顾一下:协程类似线程,是一种更为轻量级的调度单位线程是系统级实现的,常见的调度方法是时间片
- 数组去重复和数组排序'数组名次 Function Sort(ary,stra) KeepChecking =&n
- 前言在vue里,组件之间的作用域是独立的,父组件跟子组件之间的通讯可以通过prop属性来传参,但是在兄弟组件之间通讯就比较麻烦了。比如A组件
- 现状≠将来?程序员做设计本身就很悲哀,纠结于客户与坚持之间就更是如此。无论我今后的路会怎么走,我想始终不变的事情就是与客户博弈了。无论是放弃
- 一、环境由于这学期开了图像处理这门课,所以想着在各种实验开始之前自己先动手试一下图像处理那首先要配个环境嘛,配环境真的是我长久以来的噩梦了,
- 导读我们在使用selenium打开google浏览器的时候,默认打开的是一个新的浏览器窗口,而且里面不带有任何的浏览器缓存信息。当我们想要爬
- 前言.net core来势已不可阻挡。既然挡不了,那我们就顺应它。了解它并学习它。今天我们就来看看和之前.net版本的配置文件读取方式有何异
- Innodb数据库对于已经删除的数据只是标记为删除,并不真正释放所占用的磁盘空间,这就导致InnoDB数据库文件不断增长。如果在创建数据库的
- 前言GraphQL是一种新的API设计语言,它提供了更加灵活、高效的API查询方式。与RESTful API相比,GraphQL可以更好地满
- 四 PetShop之ASP.NET缓存如果对微型计算机硬件系统有足够的了解,那么我们对于Cache这个名词一定是耳熟能详的。在CPU以及主板
- 在对float零值判断时往往只需要和0做==即可,所以曾经int和float都用==0来做对比,比如下方: in
- 1.操作系统:Windows7 64bitPython版本:3.8下载地址:https://www.python.org/downloads
- 顺序表即线性表的顺序存储结构。它是通过一组地址连续的存储单元对线性表中的数据进行存储的,相邻的两个元素在物理位置上也是相邻的。比
- 多继承以及MRO顺序1. 单独调用父类的方法# coding=utf-8print("******多继承使用类名.__init__