随机prim算法全解python实现
目录
0.概述
随机prim算法是一种较为通用的迷宫生成算法,网上的教学也蛮成熟的,这里就目前的学习需要写一篇文章帮助需要找资料的小伙伴提供一个参考。
参考蛮多的,但是很少有人把整个具体的方案提出来,这里会根据具体需求用到算法上去。那第一部分就是算法的概要,接着是设计的一些观念,你可以参照目录看看你需要什么。
1.算法流程
先大概阐述一下随机prim算法的流程。
首先,你需要把一个n*n的地图初始化。初始化的方法是将整个迷宫变为田字格的形式具体你可以参照下图。
0 1 0 1 0
1 1 1 1 1
0 1 0 1 0
1 1 1 1 1
0 1 0 1 0
实现起来还是很简单的,只需要让矩阵的行和列中出现奇数的点位标记为墙就好了。但需要注意的是,这样做的弊端就是你的矩阵行列最大值必须是奇数,你也可以通过设置保护方法来转换设置为偶数时的情况。但我们最好将这个矩阵设置为全奇数规格,这样初始化后是很标准田字格,然后坐标超出规格的都认为是外墙因为我们需要这个迷宫为全封闭的。
先了解几个概念,
1:seeker当前查找的单元格,我们将seeker访问到的单元格附近的有效墙添加至墙每当我们需要添加墙了就要找seeker的坐标,而墙体变动的时候seeker也可能会跟着变动,至于seeker如何变动,我们待会再聊。
2:可访问的单元格分为2种状态,分别是被访问和未被访问,你可能需要两种状态位去标记。
3:墙状态被分为3种,分别是:打破的墙、还存在的墙、无效墙。这样给你分类的原因是,有一种情况可能导致循环无限添加墙而无法退出,原因是,当一面墙连通2个已访问单元格的时候,我们需要墙存在而不是打破它,但如果这面墙和还存在的墙为同一标签,它就会被无限添加,这种连通2歌已访问单元格的墙我们称他为无效墙,后面我们会遇到这>为同一种类型,这个不会有什么影响。
以上就是本文涉及的3种独立的概念。接下来是算法的流程:
流程1:我们将seeker初始化,至于初始化为什么点都是无所谓的,默认可以是第一个点(0,0)。
流程2:将seeker附近的墙添加至墙列表。
流程3:从墙列表中随机选择一个墙进行判断,如果是无效墙则标记一下然后从列表中删除该墙。如果是有效墙,则把他打通,同时把那个未被访问单元格标记为已访问然后标记为seeker的位置。
流程4:返回过程2.
流程5:当墙列表为空的时候程序结束。
具体的流程可以参照下图(好丑啊):
2.数据组织
在写程序之前,我们需要先规划好底层数据格式,虽然在本文看不出这个模块的重要性,但是在使用这个算法的时候,我遇到的绝大部分讨厌的问题就是因为底层的东西没有规划好,到后面突然想加一个有趣的功能导致对接困难,所以我们在一开始尽可能的把数据封装好。
第一点:迷宫数据选取。本文选择的数据结构是Pythonh扩展包numpy的ndarray类型数组对象。
第二点:标记选择。
0:未访问路径.
1:未访问墙.
2:已访问后标记的路径,包括墙,墙在被打破后也属于可行进路径.
3:已访问但不打破的墙.(无效墙)
第三定:迷宫类方法:
属性:
迷宫的本体矩阵
大小参数(用于初始化迷宫等)
seeker
墙列表
方法:
1:初始化迷宫
2:将一个seeker周围所有墙插入墙列表
3:摧毁墙
4:生成迷宫(需要用到2和3)
5:返回迷宫矩阵(因为用了类)
3:实现代码
import numpy as np
import random
class PrimMaze():
def __init__(self):
self.image = np.array([]) # 类型备份,因为后面我需要迷宫成2值状态
self.size = (15, 15)
self.maze = self.initmaze() # 初始化
self.size = np.shape(self.maze)
self.seeker = self.initseeker()
self._walls = [] # 墙列表
self.createmaze() # 生成迷宫
# 初始化规格
def initmaze(self):
在这里插入图片描述
x, y = self.size
maze = np.zeros(self.size)
for i in range(x):
for j in range(y):
if i % 2 == 1 or j % 2 == 1:
maze[i, j] = 1
return maze
# 初始化seeker
def initseeker(self):在这里插入图片描述
self.maze[0, 0] = 2
return (0, 0)
# 将墙插入列表
def insertwall(self):
size = np.shape(self.maze)
if self.seeker[0] + 1 < size[0] and self.maze[self.seeker[0] + 1, self.seeker[1]] == 1:
self._walls.append((self.seeker[0] + 1, self.seeker[1]))
if self.seeker[0] - 1 > 0 and self.maze[self.seeker[0] - 1, self.seeker[1]] == 1:
self._walls.append((self.seeker[0] - 1, self.seeker[1]))
if self.seeker[1] + 1 < size[1] and self.maze[self.seeker[0], self.seeker[1] + 1] == 1:
self._walls.append((self.seeker[0], self.seeker[1] + 1))
if self.seeker[1] - 1 > 0 and self.maze[self.seeker[0], self.seeker[1] - 1] == 1:
self._walls.append((self.seeker[0], self.seeker[1] - 1))
self._walls = list(set(self._walls))
# 摧毁墙
def destroywall(self, wall):
x = wall[0]
y = wall[1]
# 纵墙
if wall[0] % 2 == 1:
# 上边和下边都访问过,无效墙
if self.maze[x - 1, y] == 2 and self.maze[x + 1, y] == 2:
self.maze[x, y] = 3
return True
# 穿透
else:
self.maze[x, y] = 2
if self.maze[x - 1, y] == 2:
self.maze[x + 1, y] = 2
self.seeker = (x + 12.数据组织</span>, y)
return True
elif self.maze[x + 1, y] == 2:
self.maze[x - 1, y] = 2
self.seeker = (x - 1, y)
return True
# 横墙
if wall[1] % 2 == 1:
# 左边和右边都访问过,无效墙
if self.maze[x, y - 1] == 2 and self.maze[x, y + 1] == 2:
self.maze[x, y] = 3
return True
# 穿透
else:
self.maze[x, y] = 2
if self.maze[x, y - 1] == 2:
self.maze[x, y + 1] = 2
self.seeker = (x, y + 1)
return True
elif self.maze[x, y + 1] == 2:
self.maze[x, y - 1] = 2
self.seeker = (x, y - 1)
return True
print(wall, "invalid wall , can not destroy!")
return False
# 将迷宫初始化,
def createmaze(self):在这里插入图片描述
while True:
self.insertwall()
temp = self._walls.pop(random.randint(0, np.shape(self._walls)[0] - 1))
self.destroywall(temp)
if self._walls == []:
break
# 返回迷宫序列
def displaymaze(self):
return self.maze
说明:如果你需要使用这串代码,你只需要创建这个类对象,然后使用返回迷宫序列的方法也就是displaymaze(),该方法会返回一个ndarray对象,这个矩阵就是迷宫本身。实际使用的效果图如下(第一个是拿到矩阵后用pygame画的,你可以用方向键实实在在移动的哦):
说明:上图是pygameu做的小界面,将就看一下吧,下图是一个5*5的迷宫矩阵,是一个ndarray数组。你拿到数组的时候可以直接使用,比如像上图一样绘图?可呢?
4.额外的想法
这一部分是关于这个算法的一些想法和问题,第一点就是我在上面忽略了一开始想到但是忘记了的细节,就是关于无效墙的处理,如果加入seeker点变动才添加墙的话,就能成功避免无效墙需要额外标记的问题,但是额外标记其实也没什么大问题,所以就没在写这篇博客的时候改。其次,这个算法在某些角度上去看效率是很低的,你永远最多只能一次添加3面墙,有的时候只能添加1面墙,这对于遍历整个矩阵有相当大的阻碍作用,所以你若能有幸需要做超大迷宫设计,这个算法还是有超多优化空间的,但这里就先不提了。
关于一些游戏性的问题:如果你对迷宫的趣味行问题感兴趣那不妨看看这个部分。首先是多维迷宫的构建,我想到了一个很有趣的构建机制,就是将大小不一的各种迷宫堆叠再选取相同的点进行允许通过,这样就能从一个迷宫到达另一个迷宫,这里指的不是纯粹的三位迷宫,纯粹的三位迷宫也可以使用上述算法直接得出,这里指的是更像关卡类的设计,同时该算法生成的是完美迷宫,也就是迷宫内部i不存咋闭环,那么如果我么能够在迷宫里设计闭环且让另一个迷宫能够通向这个闭环就能够达成更有趣的迷宫设计(虽然我知道没人会闲着没事干玩迷宫)。甚至在迷宫游戏中加入更多的收集要素。后面如果我真的闲着无聊,有可能会对这些想法进行实现。