问题描述:迷宫指的是充满复杂通道,很难找到从入口或迷宫中某个位置到达出口的道路,道路复杂难辨,人进去不容易出来的事物。
要求:以二维数组模拟一个矩形迷宫,利用随机深度优先、随机广度优先或随机普里姆算法生成不含有回路的迷宫并找到迷宫中任意两点的正确路径。
问题分析:
(1)迷宫用二维数组表示,为方便运算,将迷宫中的墙和通路可以用-1和0表示。
(2)迷宫不含有回路,说明所有通路之间连通。
(3)迷宫存在多条分支路线,任意两点可达。
(4)生成迷宫的算法可选三种,但BFS和Prim的相似度较高,故选取DFS和BFS两种算法进行设计。
(5)软件功能采用Python+Pyqt5进行可视化,界面操作简单。
可视化图形界面
使用Qt的ui编辑工具创建一个界面,并用pyuic工具将ui文件转换为Python类,命名为ui.py供主程序直接调用。
ui文件包含的主要内容
Ui文件主要包含内容
QLabel
7个
QGraphicsView
1个
QlineEdit
2个
QCombox
2个
QPushButton
3个
功能:
从用户接受迷宫的行数和列数以及迷宫生成求解方式
一键生成迷宫并可视化展示
一键求解和展示
一键更换迷宫的入口和出口
递归DFS生成迷宫算法
深度优先搜索总是对最近才发现的结点v的出发边进行探索,直到该结点的所有出发边都被发现为止。一但结点v的所有出发边都被发现,搜索则“回溯”到v的前驱结点(v是经过该结点才被发现的),来搜索该前驱结点的出发边。该过程一直持续到从源结点可以达到的所有结点都被发现为止。如果还存在尚未发现的结点,则深度优先搜索将从这些未被发现的结点中任选一个作为新的源点,并重复同样的搜索过程。该算法重复整个过程,直到图中的所有结点都被发现为止。
Prim生成迷宫算法
原理:普里姆算法(Prim算法),图论中的一种算法,可在加权连通图里搜索最小生成树。意即由此算法搜索到的边子集所构成的树中,不但包括了连通图里的所有顶点,且其所有边的权值之和亦为最小。随机prim最小生成树算法就是使用最小生成树的思想,将所有的奇数点(横纵坐标均为奇数的点)连成一个连通分量,而且每相邻的两个点之间的距离都为1,因此这个方法也可以看作是一种特殊的随机广度优先算法。
-
初始全是墙
-
构建一墙一通路形式
-
随机选择一个通路,并将周围墙入容器,标记该通路
-
在墙容器中随机选取一堵墙,如果墙两边的通路没有同时被标记,则打通该墙,并将原来未被标记的通路周围的墙加入容器,然后将该通路标记,最后移除该墙
-
重复操作 4,直到墙容器为空,说明该算法完成,最后将被标记的通路清除标记
class PRIMg(object):
def init(self, width=11, height=11):
self.width = (width // 2) * 2 + 1
self.height = (height // 2) * 2 + 1
self.start = [1, 0]
self.destination = [self.height - 2, self.width - 1]
self.matrix = None
def generate_matrix_prim(self):
self.matrix = -np.ones((self.height, self.width))
def check(row, col):
temp_sum = 0
for d in [[0, 1], [0, -1], [1, 0], [-1, 0]]:
temp_sum += self.matrix[row + d[0]][col + d[1]]
return temp_sum < -3
list = []
row, col = (np.random.randint(1, self.height - 1) // 2) * 2 + 1,(np.random.randint(1, self.width - 1) // 2) * 2 + 1
list.append((row, col, -1, -1))
while len(list) != 0:
row, col, r_, c_ = list.pop(np.random.randint(0, len(list)))
if check(row, col):
self.matrix[row, col] = 0
if r_ != -1 and row == r_:
self.matrix[row][min(col, c_) + 1] = 0
elif r_ != -1 and col == c_:
self.matrix[min(row, r_) + 1][col] = 0
for d in [[0, 2], [0, -2], [2, 0], [-2, 0]]:
row_, col_ = row + d[0], col + d[1]
if row_ > 0 and row_ < self.height - 1 and col_ > 0 and col_<self.width - 1 and self.matrix[row_][col_] == -1:
list.append((row_, col_, row, col))
self.matrix[self.start[0], self.start[1]] = 0
self.matrix[self.destination[0], self.destination[1]] = 0
DFS求解迷宫算法
该寻路方法,应该算是比较简单算法,学习数据结构栈时一般就会接触该算法。
从起点开始,将当前点入栈,向某一方向前进。若周围方向均不能继续前进,则回退,直到找到终点或栈为空,就不再贴图
BFS求解迷宫算法
利用辅助队列。
def bfs(t):
global que, dx, dy, record, st
lis = {}
visited = [(st[0], st[1])]
que = Queue()
que.put(t)
while que:
temp = que.get()
if temp[0] == dx and temp[1] == dy:
break
for (kx, ky) in [[1, 0], [-1, 0], [0, 1], [0, -1]]:
x = kx + temp[0]
y = ky + temp[1]
if (x > 0 and y > 0 and x < ROW_NUM and y < COL_NUM and maz[x][y] == 0 and (x, y) not in visited):
que.put((x, y))
visited.append((x, y))
lis[(x, y)] = (temp[0], temp[1])
if (x == dx and y == dy):
break
record.append((dx, dy))
(x, y) = lis[(dx, dy)]
record.append((x, y))
while (x, y) != (st[0], st[1]):
(x, y) = lis[x, y]
record.append((x, y))
Keep(record)
功能测试
初始化UI后只有生成的按钮可以点击,点击生成按钮之后才可以继续点击随机起点和终点,每次迷宫的解只能生成一次,在重新点击生成后或重新点击随机起点和终点之后可再次点击求解。
算法测试
DFS生成迷宫算法
递归调用本方法,二维向量,有备忘录,时间复杂度应为O(n²)。大约能生成95个长宽大小的迷宫,深度优先算法运行的效率较低,一次只能生成一个较长的通路,路径长但是曲折度低,分叉少但不易破解。
Prim生成迷宫算法
复杂度,空间:O(n²),时间:O(n²)。该算法处理以广度优先,从不同区域生成不同的形状,最后交汇,生成的岔路比较多,分支多,比较自然但易破解。
DFS、BFS求解迷宫算法
分别使用栈和队列两种数据结构。DFS就是递归回溯,使用栈记录节点的情况,找到终点则停止,找到的不可走的边界则返回,对内存消耗较少。而广度优先搜索属于同时进行不同方向的搜索,走到歧途则剪枝,相当于树的层序遍历。索次数大概和边长的指数级别有关系。广搜属于一种盲目搜寻法,目的是系统地展开并检查图中的所有节点,以找寻结果。但是利用辅助队列可以提升性能。
在程序中两种搜索算法都可以轻松对该迷宫产生求解。
对后续工作展望
①后续将进一步优化算法。
②后续将增加算法,加入Kruskal算法,Dijkstra,A*等方法对迷宫进行生成和求解。
③增加可视化弹窗或标签提示算法完成时间。
程序源代码仓库
https://gitee.com/hao-zihang/maze-algorithm-2021