[python算法:基础数据结构]利用栈解决迷宫问题+networkx可视化

 核心思想:

栈的特性是后进先出,因此我们可以将走过的路径放入一个栈中,在搜索上下左右四个方向的过程中,如果出现走不通的情况,就进行出栈操作,回退到上一个方向,继续搜索其四个方向是否有通路。一旦栈空了,意味着回退到原点的时候也没有路,也就意味着迷宫没有解

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)

步骤一:迷宫初始化

首先定义迷宫类,并设置迷宫二维数组属性和迷宫通路,定义三个方法,分别用于

  1. 生成随机迷宫
  2. 迷宫求解并返回通路

可视化迷宫

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()

结果图展示:

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值