核心思想:
栈的特性是后进先出,因此我们可以将走过的路径放入一个栈中,在搜索上下左右四个方向的过程中,如果出现走不通的情况,就进行出栈操作,回退到上一个方向,继续搜索其四个方向是否有通路。一旦栈空了,意味着回退到原点的时候也没有路,也就意味着迷宫没有解
python栈数据结构的实现与导入相关包:
import numpy as np
import networkx as nx
import matplotlib.pylab as plt
import random
class Stack:
def __init__(self) -> None:
self.items=[]
def push(self,item):
self.items.append(item)
def pop(self):
return self.items.pop()
def peek(self):
return self.items[-1]
def isEmpty(self):
return self.items ==[]
def size(self):
return len(self.items)
步骤一:迷宫初始化
首先定义迷宫类,并设置迷宫二维数组属性和迷宫通路,定义三个方法,分别用于
- 生成随机迷宫
- 迷宫求解并返回通路
可视化迷宫
class Maze:
def __init__(self) -> None:
self.maze=[
[1,1,1,1,1,1,1,1,1,1],
[1,0,0,1,0,0,0,1,0,1],
[1,0,0,1,0,0,0,1,0,1],
[1,0,0,0,0,1,1,0,0,1],
[1,0,1,1,1,0,0,0,0,1],
[1,0,0,0,1,0,0,0,0,1],
[1,0,1,0,0,0,1,0,0,1],
[1,0,1,1,1,0,1,1,0,1],
[1,1,0,0,0,0,0,0,0,1],
[1,1,1,1,1,1,1,1,1,1]
]
self.route=None
步骤二:随机生成迷宫二维数组
这里引用1.1 迷宫生成算法——用python在minecraft中生成一个迷宫 - 知乎的随机生成二维迷宫数组的代码
def maze_generalizer(self,m=10,n=10):
'''来源于https://zhuanlan.zhihu.com/p/363956880'''
def inborder(x,y):
return x<n and x>=0 and y<m and y>=0
# 记录每个点是否经过过
visited = [[False for i in range(m)] for j in range(n)]
# 记录迷宫形状,注意n行,m列的迷宫应该包含2n+1行,2m+1列
# 因为a条平行的通路之间和两侧一共还有a+1堵墙
map = [[0 for i in range(2*m+1)] for j in range(2*n+1)]
dx=[-1,1,0,0]
dy=[0,0,-1,1]
#分别是上下左右的位移矢量
#起始点位置和终止点位置
startposx=0
startposy=0
endposx=m-1
endposy=n-1
#px,py是指针当前指向的位置,visited[px][py]对应于map[2px+1][2py+1]
px = startposx
py = startposy
visited[px][py]=True
visitedpos=[(px,py)]#之前走过的所有点构成的列表
map[2*px+1][2*py+1]=1
sum=1 #经过点的总数,当达到mn时迷宫就构建好了
while(sum<m*n):
walkable=[] #存储px,py点前后左右四个点中能到达的点对应的i
for i in range(4):
#计算新的点的位置
tx=px+dx[i]
ty=py+dy[i]
if inborder(tx,ty) and not visited[tx][ty]:#能到达就将i加入walkable
walkable.append(i)
if not walkable or (px==endposx and py==endposy):
#如果walkable为空,或已到达终点,则向之前的位置随机传送
px,py=random.choice(visitedpos)
else:
#随机选择方向行进
i = random.choice(walkable)
px += dx[i]
py += dy[i]
visited[px][py]=True
visitedpos.append((px,py))
#将到达的点和前后两点之间的墙赋值为1(路)
map[2*px+1][2*py+1]=1
map[1+2*px-dx[i]][1+2*py-dy[i]]=1
sum+=1
self.maze=np.where(np.array(map)>0,0,1).tolist() #替换0,1
print('成功生成新迷宫')
def four_direction(self,x,y):
'''返回上,下,左,右的坐标'''
return (x+1,y),(x-1,y),(x,y-1),(x,y+1)
def search(self,start_x,start_y,end_x,end_y):
stack=Stack()
stack.push((start_x,start_y)) #添加起始位置
while stack.size()>0:
current_node=stack.peek() #当前节点
if current_node==(end_x,end_y):#如果是终点
self.route=stack.items
self.maze[start_x][start_y]='S' #标记起点
self.maze[end_x][end_y]='E' #标记终点
return True
#搜索上下左右四个方向,看能不能走
for dir in self.four_direction(*current_node): #元组解包
if self.maze[dir[0]][dir[1]]==0:
stack.push(dir) #可以走,压入栈中
self.maze[dir[0]][dir[1]]=2 #标记为已经走过
break
else: #四个方向都行不通
self.maze[dir[0]][dir[1]]=2 #标记为已经走过
stack.pop() #回退(即最后走的路出栈)
'''如果到这了,说明栈空,没有路'''
print('没有路')
return False
步骤三:定义搜索迷宫函数
注意这里对不同的点进行了标记,方便后面进行可视化
def four_direction(self,x,y):
'''返回上,下,左,右的坐标'''
return (x+1,y),(x-1,y),(x,y-1),(x,y+1)
def search(self,start_x,start_y,end_x,end_y):
stack=Stack()
stack.push((start_x,start_y)) #添加起始位置
while stack.size()>0:
current_node=stack.peek() #当前节点
if current_node==(end_x,end_y):#如果是终点
self.route=stack.items
self.maze[start_x][start_y]='S' #标记起点
self.maze[end_x][end_y]='E' #标记终点
return True
#搜索上下左右四个方向,看能不能走
for dir in self.four_direction(*current_node): #元组解包
if self.maze[dir[0]][dir[1]]==0:
stack.push(dir) #可以走,压入栈中
self.maze[dir[0]][dir[1]]=2 #标记为已经走过
break
else: #四个方向都行不通
self.maze[dir[0]][dir[1]]=2 #标记为已经走过
stack.pop() #回退(即最后走的路出栈)
'''如果到这了,说明栈空,没有路'''
print('没有路')
return False
步骤四:利用networkx库进行可视化
这里采用了{坐标:值}的数据结构进行存储,方便后面的画图。如果没有调用search先进行画图,会画出迷宫本来的图
def draw(self):
route=self.route
plt.figure(figsize=(8,8))
plt.subplot(111)
G=nx.Graph()
rownum,colnum=len(self.maze),len(self.maze[0])
map_data={(i,j):self.maze[i][j] for i in range(rownum) for j in range(colnum)} #地图信息
pos={}
for coordinate,attr in map_data.items():
pos[coordinate]=coordinate
G.add_node(coordinate,attribute=attr) #节点的坐标为id,属性代表是哪种(障碍还是通路)
nx.draw_networkx_nodes(G,nodelist=[coordinate],pos={coordinate:coordinate},
node_color='red' if attr==1 else 'yellow' if attr==2 else 'grey' if attr==0 else 'green' if attr=='E' else 'orange',label=coordinate)
if self.route:
link=[(route[nodenum],route[nodenum+1]) for nodenum in range(len(route)-1)]
for index,node in enumerate(link):
G.add_edge(node[0],node[1],order=index+1) #顺序
nx.draw_networkx_edges(G,edgelist=[(node[0],node[1])],pos=pos,width=8)
最终代码:
import numpy as np
import networkx as nx
import matplotlib.pylab as plt
import random
class Stack:
def __init__(self) -> None:
self.items=[]
def push(self,item):
self.items.append(item)
def pop(self):
return self.items.pop()
def peek(self):
return self.items[-1]
def isEmpty(self):
return self.items ==[]
def size(self):
return len(self.items)
class Maze:
def __init__(self) -> None:
self.maze=[
[1,1,1,1,1,1,1,1,1,1],
[1,0,0,1,0,0,0,1,0,1],
[1,0,0,1,0,0,0,1,0,1],
[1,0,0,0,0,1,1,0,0,1],
[1,0,1,1,1,0,0,0,0,1],
[1,0,0,0,1,0,0,0,0,1],
[1,0,1,0,0,0,1,0,0,1],
[1,0,1,1,1,0,1,1,0,1],
[1,1,0,0,0,0,0,0,0,1],
[1,1,1,1,1,1,1,1,1,1]
]
self.route=None
def maze_generalizer(self,m=5,n=5):
'''来源于https://zhuanlan.zhihu.com/p/363956880'''
def inborder(x,y):
return x<n and x>=0 and y<m and y>=0
# 记录每个点是否经过过
visited = [[False for i in range(m)] for j in range(n)]
# 记录迷宫形状,注意n行,m列的迷宫应该包含2n+1行,2m+1列
# 因为a条平行的通路之间和两侧一共还有a+1堵墙
map = [[0 for i in range(2*m+1)] for j in range(2*n+1)]
dx=[-1,1,0,0]
dy=[0,0,-1,1]
#分别是上下左右的位移矢量
#起始点位置和终止点位置
startposx=0
startposy=0
endposx=m-1
endposy=n-1
#px,py是指针当前指向的位置,visited[px][py]对应于map[2px+1][2py+1]
px = startposx
py = startposy
visited[px][py]=True
visitedpos=[(px,py)]#之前走过的所有点构成的列表
map[2*px+1][2*py+1]=1
sum=1 #经过点的总数,当达到mn时迷宫就构建好了
while(sum<m*n):
walkable=[] #存储px,py点前后左右四个点中能到达的点对应的i
for i in range(4):
#计算新的点的位置
tx=px+dx[i]
ty=py+dy[i]
if inborder(tx,ty) and not visited[tx][ty]:#能到达就将i加入walkable
walkable.append(i)
if not walkable or (px==endposx and py==endposy):
#如果walkable为空,或已到达终点,则向之前的位置随机传送
px,py=random.choice(visitedpos)
else:
#随机选择方向行进
i = random.choice(walkable)
px += dx[i]
py += dy[i]
visited[px][py]=True
visitedpos.append((px,py))
#将到达的点和前后两点之间的墙赋值为1(路)
map[2*px+1][2*py+1]=1
map[1+2*px-dx[i]][1+2*py-dy[i]]=1
sum+=1
self.maze=np.where(np.array(map)>0,0,1).tolist() #替换0,1
print('成功生成新迷宫')
def four_direction(self,x,y):
'''返回上,下,左,右的坐标'''
return (x+1,y),(x-1,y),(x,y-1),(x,y+1)
def search(self,start_x,start_y,end_x,end_y):
stack=Stack()
stack.push((start_x,start_y)) #添加起始位置
while stack.size()>0:
current_node=stack.peek() #当前节点
if current_node==(end_x,end_y):#如果是终点
self.route=stack.items
self.maze[start_x][start_y]='S' #标记起点
self.maze[end_x][end_y]='E' #标记终点
return True
#搜索上下左右四个方向,看能不能走
for dir in self.four_direction(*current_node): #元组解包
if self.maze[dir[0]][dir[1]]==0:
stack.push(dir) #可以走,压入栈中
self.maze[dir[0]][dir[1]]=2 #标记为已经走过
break
else: #四个方向都行不通
self.maze[dir[0]][dir[1]]=2 #标记为已经走过
stack.pop() #回退(即最后走的路出栈)
'''如果到这了,说明栈空,没有路'''
print('没有路')
return False
def draw(self):
route=self.route
plt.figure(figsize=(8,8))
plt.subplot(111)
G=nx.Graph()
rownum,colnum=len(self.maze),len(self.maze[0])
map_data={(i,j):self.maze[i][j] for i in range(rownum) for j in range(colnum)} #地图信息
pos={}
for coordinate,attr in map_data.items():
pos[coordinate]=coordinate
G.add_node(coordinate,attribute=attr) #节点的坐标为id,属性代表是哪种(障碍还是通路)
nx.draw_networkx_nodes(G,nodelist=[coordinate],pos={coordinate:coordinate},
node_color='red' if attr==1 else 'yellow' if attr==2 else 'grey' if attr==0 else 'green' if attr=='E' else 'orange',label=coordinate)
if self.route:
link=[(route[nodenum],route[nodenum+1]) for nodenum in range(len(route)-1)]
for index,node in enumerate(link):
G.add_edge(node[0],node[1],order=index+1) #顺序
nx.draw_networkx_edges(G,edgelist=[(node[0],node[1])],pos=pos,width=8)
a=Maze()
a.maze_generalizer(5,5) #生成11x11迷宫
a.draw()
a.search(1,1,9,9)
a.draw()
结果图展示: