Python 实现递归回溯以生成迷宫
我试图创建一个递归的createmaze函数,但是,我被卡住了,因为我不知道如何递归调用它并放置墙 有人能告诉我如何编辑代码使其工作吗?谢谢 编辑:因为我没有添加迷宫类,所以我想我应该添加它来帮助查看整个代码Python 实现递归回溯以生成迷宫,python,recursive-backtracking,Python,Recursive Backtracking,我试图创建一个递归的createmaze函数,但是,我被卡住了,因为我不知道如何递归调用它并放置墙 有人能告诉我如何编辑代码使其工作吗?谢谢 编辑:因为我没有添加迷宫类,所以我想我应该添加它来帮助查看整个代码 class Maze: def __init__(self, Width, Height): assert Width>= 1 and Height>= 1 self.Width= Width self.Height=
class Maze:
def __init__(self, Width, Height):
assert Width>= 1 and Height>= 1
self.Width= Width
self.Height= Height
self.board = np.zeros((Width, Height), dtype=WALL_TYPE)
self.board.fill(EMPTY)
def set_borders(self):
self.board[0, :] = self.board[-1, :] = WALL
self.board[:, 0] = self.board[:, -1] = WALL
def is_wall(self, x, y):
assert self.in_maze(x, y)
return self.board[x][y] == WALL
def set_wall(self, x, y):
assert self.in_maze(x, y)
self.board[x][y] = WALL
def create_maze(Width, Height, seed=None):
Width = (Width // 2) * 2 + 1
Height = (Height // 2) * 2 + 1
if seed is not None:
np.random.seed(seed)
maze = Maze(Width, Height)
maze.set_borders()
x, y = rand(0, Width // 2) * 2, rand(0, Height // 2) * 2
maze.set_wall(x, y)
visited = []
visited.append((x,y))
while len(visited):
start = visited[-1]
if maze.in_maze(x - 2, y):
visited.append((x - 2, y))
if maze.in_maze(x + 2, y):
visited.append((x + 2, y))
if maze.in_maze(x, y - 2):
visited.append((x, y - 2))
if maze.in_maze(x, y + 2):
visited.append((x, y + 2))
visited.remove(start) # This goes somewhere but I don't know where
if not maze.is_wall(x,y):
maze.set_wall(x,y)
create_maze() #recurse?
初始问题
好的,首先,你通常不会像这样设置递归函数。由于需要反复调用同一个函数,因此不希望每次调用都重新创建迷宫对象。您希望在递归函数调用之外执行所有设置和计算,并且只递归地执行一个非常特定的作业
安装程序
我假设迷宫类的设置有点像这样:
随机输入
班级迷宫:
定义初始自身、宽度、高度:
self.width=width//2*2+1
self.height=height//2*2+1
这将为迷宫数据False:path、True:wall创建一个二维数组
self.cells=[
[对于rangeself.width中的x为真]
对于y,在自身高度范围内
]
def set_路径自身,x,y:
self.cells[y][x]=假
def设置_wallself,x,y:
self.cells[y][x]=真
思考迷宫的方法
好的,现在我可以开始递归生成本身了。现在,我不再采取加墙的方法,而是用墙填满整个迷宫,然后自己挖路径
在我们的迷宫中,尽管我们将其视为一个简单的开/关二维网格,带有墙和路径,但将其更像是一系列节点连接和它们之间的链接是很有帮助的。我可以看到你开始实施这个方法,你确保你的迷宫宽度和高度是奇数,例如宽度//2*2+1,你只选择了偶数单元格,尽管我不知道为什么
下面是一个快速图表,可以直观地看出我的意思:
每个红色圆圈都是一个节点,正如您所看到的,每个奇数块都包含一个节点,并且始终是一个路径块。我们将自动假定每个奇数块将包含一条路径,因此包含一个节点。这意味着,当我们生成迷宫时,当我们挖通通道时,我们将一次移动两个细胞,这样我们就可以在节点和灰色细胞之间创建链接
算法
我将实现的算法的本质如下:
沿着随机的方向移动,挖掘前进的道路,跟踪我去过的地方
重复,直到我走到尽头
“回溯”我去过的地方,直到我找到一条至少有一条可行路径的路径。转到步骤1
以下是更详细的相同步骤:
沿着随机的方向移动,跟踪我去过的地方
1.1。我们环顾每个方向,看看我们的移动选项在哪里
1.2。在存在有效路径的位置选择一个随机方向
1.3。移动到新节点记住,我们移动了两个单元格
重复,直到我走到尽头
2.1。继续重复第一步中的过程
2.2。如果在第一步中,您没有找到任何路径选项,则表明您已进入死胡同
回顾我去过的地方
3.1。按照您的足迹回溯以前访问过的节点
3.2。重复此操作,直到找到至少有一条未访问路径的节点为止
3.3。转到第1步,如中所示,开始在新路径中沿随机方向行走
现在使用递归函数实现这些步骤。通过移动两个单元格到新节点的每一步都将是一个新的函数调用,使用新的x-y坐标。以下是相同的步骤,但是递归的:
沿着任意方向移动,跟踪我去过的地方
1.1。随机选择一个方向,并检查它是否还没有被访问过(例如,如果我们已经沿着它走了,我们会已经挖通了,所以它将是一条路径)。因此,选择任何一个方向,这是一堵墙,如未访问
1.2。现在沿该方向移动两个单元,但请记住将两个节点之间的节点单元和链接单元都设置为路径,否则您就跳过了一堵墙。记住,当“移动”到新单元时,我们将使用新节点的x-y坐标再次调用该函数
重复,直到你到达一个死胡同
2.1。如果在第一步中,您发现所有方向都包含路径(例如,您已经访问了该节点的每个方向),则需要回溯
2.2。现在回溯一下,我们将退出当前函数调用。这意味着我们正在向后移动到先前的函数,该函数最初将我们移动到当前节点
回溯直到找到一条路径
3.1。退出函数调用后,您现在已使用先前的x-y坐标移回上一个节点。现在进入第一步,在那里寻找潜在的方向,如果没有,则进入第二步,进一步回溯
代码
好了,所有的理论和计划都完成了,我们现在可以把我们的设计实现成代码了
我将创建递归函数作为迷宫类的方法,如下所示:
班级迷宫:
...
构造函数和其他方法都在这里
...
def创建_mazeself,x,y:
我们的递归函数在这里
这意味着要完全创建我们的迷宫,我们称之为maze.create_maze1,1替换1,1,不管你的起始坐标是什么
因此,让我们来浏览一下我们前面设计的每个步骤,并将它们转换成代码
1.1选择一个随机可行的方向
def创建_mazeself,x,y:
将当前单元格设置为路径,这样我们以后就不会返回此处
self.set_路径self,x,y
我们以随机顺序创建一个方向列表,我们可以尝试
所有方向=[[1,0]、-1,0]、[0,1]、[0,1]、[0,1]]
随机.shuffl\u方向
我们不断尝试列表中的下一个方向,直到没有方向为止
当所有方向>0时:
我们删除并返回方向列表中的最后一项
方向到尝试=所有方向.pop
使用随机方向计算新节点的坐标。
当我们在每个方向上移动两个单元格到下一个节点时,我们使用*2
节点x=x+方向尝试[0]*2
节点y=y+方向尝试[1]*2
检查测试节点是否为墙(如未访问)
如果self.is_wallnode_x,node_y:
成功代码:我们找到了一条路
失败代码:我们找不到路径
如果当前单元格是墙,并且单元格在迷宫边界内,则返回的函数
def是_wallself,x,y:
检查坐标是否在迷宫网格内
如果0:
方向到尝试=所有方向.pop
节点x=x+方向尝试[0]*2
节点y=y+方向尝试[1]*2
如果self.is_wallnode_x,node_y:
成功代码:我们找到了一条路
将链接单元设置在从/移动到路径的两个节点之间
链接单元格\ux=x+方向\u到\utry[0]
链接单元格y=y+方向到尝试[1]
self.set\u pathlink\u cell\u x,link\u cell\u y
移动到我们的新节点。请记住,我们每天都在调用该函数
我们移动的时间,所以我们再次调用它,但使用更新的x和y坐标
self.create_mazenode_x,node_y
失败代码:我们找不到路径
2.1。如果我们所有的方向都包含他们访问过的路径,那么我们需要回溯
2.2。为了回溯,我们退出函数调用,这会将我们带到上一个节点
结束函数的一种简单方法是调用return。这将停止在函数中运行任何其他代码,并返回到调用它的前一个函数
失败代码:我们找不到路径
回来
将其添加到递归函数中,它现在变成:
def创建_mazeself,x,y:
self.set_路径x,y
所有方向=[[1,0]、-1,0]、[0,1]、[0,1]、[0,1]]
随机.shuffl\u方向
当所有方向>0时:
方向到尝试=所有方向.pop
节点x=x+方向尝试[0]*2
节点y=y+方向尝试[1]*2
如果self.is_wallnode_x,node_y:
链接单元格\ux=x+方向\u到\utry[0]
链接单元格y=y+方向到尝试[1]
self.set\u pathlink\u cell\u x,link\u cell\u y
self.create_mazenode_x,node_y
失败代码:我们找不到路径
回来
3.1。再次尝试步骤1,如果没有,请转至步骤2
基本上,我们现在已经编写了一个功能齐全的迷宫生成程序,尽我所能使用了一个相当好的递归方法。一旦函数到达死胡同,它将返回,返回到上一个函数,并继续该过程,直到该函数到达死胡同,等等
最终代码
随机输入
班级迷宫:
定义初始自身、宽度、高度:
self.width=width//2*2+1
self.height=height//2*2+1
这将为迷宫数据False:path、True:wall创建一个二维数组
self.cells=[
[对于rangeself.width中的x为真]
对于y,在自身高度范围内
]
def set_路径自身,x,y:
self.cells[y][x]=假
def设置_wallself,x,y:
self.cells[y][x]=真
如果当前单元格是墙,则返回的函数,
如果细胞在迷宫的范围内
def是_wallself,x,y:
检查坐标是否在迷宫网格内
如果没有初始问题
好的,首先,你通常不会像这样设置递归函数。由于需要反复调用同一个函数,因此不希望每次调用都重新创建迷宫对象。您希望在递归函数调用之外执行所有设置和计算,并且只递归地执行一个非常特定的作业
安装程序
我假设你的迷宫课程设置是
有点像这样:
随机输入
班级迷宫:
定义初始自身、宽度、高度:
self.width=width//2*2+1
self.height=height//2*2+1
这将为迷宫数据False:path、True:wall创建一个二维数组
self.cells=[
[对于rangeself.width中的x为真]
对于y,在自身高度范围内
]
def set_路径自身,x,y:
self.cells[y][x]=假
def设置_wallself,x,y:
self.cells[y][x]=真
思考迷宫的方法
好的,现在我可以开始递归生成本身了。现在,我不再采取加墙的方法,而是用墙填满整个迷宫,然后自己挖路径
在我们的迷宫中,尽管我们将其视为一个简单的开/关二维网格,带有墙和路径,但将其更像是一系列节点连接和它们之间的链接是很有帮助的。我可以看到你开始实施这个方法,你确保你的迷宫宽度和高度是奇数,例如宽度//2*2+1,你只选择了偶数单元格,尽管我不知道为什么
下面是一个快速图表,可以直观地看出我的意思:
每个红色圆圈都是一个节点,正如您所看到的,每个奇数块都包含一个节点,并且始终是一个路径块。我们将自动假定每个奇数块将包含一条路径,因此包含一个节点。这意味着,当我们生成迷宫时,当我们挖通通道时,我们将一次移动两个细胞,这样我们就可以在节点和灰色细胞之间创建链接
算法
我将实现的算法的本质如下:
沿着随机的方向移动,挖掘前进的道路,跟踪我去过的地方
重复,直到我走到尽头
“回溯”我去过的地方,直到我找到一条至少有一条可行路径的路径。转到步骤1
以下是更详细的相同步骤:
沿着随机的方向移动,跟踪我去过的地方
1.1。我们环顾每个方向,看看我们的移动选项在哪里
1.2。在存在有效路径的位置选择一个随机方向
1.3。移动到新节点记住,我们移动了两个单元格
重复,直到我走到尽头
2.1。继续重复第一步中的过程
2.2。如果在第一步中,您没有找到任何路径选项,则表明您已进入死胡同
回顾我去过的地方
3.1。按照您的足迹回溯以前访问过的节点
3.2。重复此操作,直到找到至少有一条未访问路径的节点为止
3.3。转到第1步,如中所示,开始在新路径中沿随机方向行走
现在使用递归函数实现这些步骤。通过移动两个单元格到新节点的每一步都将是一个新的函数调用,使用新的x-y坐标。以下是相同的步骤,但是递归的:
沿着任意方向移动,跟踪我去过的地方
1.1。随机选择一个方向,并检查它是否还没有被访问过(例如,如果我们已经沿着它走了,我们会已经挖通了,所以它将是一条路径)。因此,选择任何一个方向,这是一堵墙,如未访问
1.2。现在沿该方向移动两个单元,但请记住将两个节点之间的节点单元和链接单元都设置为路径,否则您就跳过了一堵墙。记住,当“移动”到新单元时,我们将使用新节点的x-y坐标再次调用该函数
重复,直到你到达一个死胡同
2.1。如果在第一步中,您发现所有方向都包含路径(例如,您已经访问了该节点的每个方向),则需要回溯
2.2。现在回溯一下,我们将退出当前函数调用。这意味着我们正在向后移动到先前的函数,该函数最初将我们移动到当前节点
回溯直到找到一条路径
3.1。退出函数调用后,您现在已使用先前的x-y坐标移回上一个节点。现在进入第一步,在那里寻找潜在的方向,如果没有,则进入第二步,进一步回溯
代码
好了,所有的理论和计划都完成了,我们现在可以把我们的设计实现成代码了
我将创建递归函数作为迷宫类的方法,如下所示:
班级迷宫:
...
构造函数和其他方法都在这里
...
def创建_mazeself,x,y:
我们的递归函数在这里
这意味着要完全创建我们的迷宫,我们称之为maze.create_maze1,1替换1,1,不管你的起始坐标是什么
因此,让我们来浏览一下我们前面设计的每个步骤,并将它们转换成代码
1.1选择一个随机可行的方向
def创建_mazeself,x,y:
将当前单元格设置为路径,这样我们以后就不会返回此处
self.set_路径self,x,y
我们以随机顺序创建一个方向列表,我们无法
雷
所有方向=[[1,0]、-1,0]、[0,1]、[0,1]、[0,1]]
随机.shuffl\u方向
我们不断尝试列表中的下一个方向,直到没有方向为止
当所有方向>0时:
我们删除并返回方向列表中的最后一项
方向到尝试=所有方向.pop
使用随机方向计算新节点的坐标。
当我们在每个方向上移动两个单元格到下一个节点时,我们使用*2
节点x=x+方向尝试[0]*2
节点y=y+方向尝试[1]*2
检查测试节点是否为墙(如未访问)
如果self.is_wallnode_x,node_y:
成功代码:我们找到了一条路
失败代码:我们找不到路径
如果当前单元格是墙,并且单元格在迷宫边界内,则返回的函数
def是_wallself,x,y:
检查坐标是否在迷宫网格内
如果0:
方向到尝试=所有方向.pop
节点x=x+方向尝试[0]*2
节点y=y+方向尝试[1]*2
如果self.is_wallnode_x,node_y:
成功代码:我们找到了一条路
将链接单元设置在从/移动到路径的两个节点之间
链接单元格\ux=x+方向\u到\utry[0]
链接单元格y=y+方向到尝试[1]
self.set\u pathlink\u cell\u x,link\u cell\u y
移动到我们的新节点。请记住,我们每天都在调用该函数
我们移动的时间,所以我们再次调用它,但使用更新的x和y坐标
self.create_mazenode_x,node_y
失败代码:我们找不到路径
2.1。如果我们所有的方向都包含他们访问过的路径,那么我们需要回溯
2.2。为了回溯,我们退出函数调用,这会将我们带到上一个节点
结束函数的一种简单方法是调用return。这将停止在函数中运行任何其他代码,并返回到调用它的前一个函数
失败代码:我们找不到路径
回来
将其添加到递归函数中,它现在变成:
def创建_mazeself,x,y:
self.set_路径x,y
所有方向=[[1,0]、-1,0]、[0,1]、[0,1]、[0,1]]
随机.shuffl\u方向
当所有方向>0时:
方向到尝试=所有方向.pop
节点x=x+方向尝试[0]*2
节点y=y+方向尝试[1]*2
如果self.is_wallnode_x,node_y:
链接单元格\ux=x+方向\u到\utry[0]
链接单元格y=y+方向到尝试[1]
self.set\u pathlink\u cell\u x,link\u cell\u y
self.create_mazenode_x,node_y
失败代码:我们找不到路径
回来
3.1。再次尝试步骤1,如果没有,请转至步骤2
基本上,我们现在已经编写了一个功能齐全的迷宫生成程序,尽我所能使用了一个相当好的递归方法。一旦函数到达死胡同,它将返回,返回到上一个函数,并继续该过程,直到该函数到达死胡同,等等
最终代码
随机输入
班级迷宫:
定义初始自身、宽度、高度:
self.width=width//2*2+1
self.height=height//2*2+1
这将为迷宫数据False:path、True:wall创建一个二维数组
self.cells=[
[对于rangeself.width中的x为真]
对于y,在自身高度范围内
]
def set_路径自身,x,y:
self.cells[y][x]=假
def设置_wallself,x,y:
self.cells[y][x]=真
如果当前单元格是墙,则返回的函数,
如果细胞在迷宫的范围内
def是_wallself,x,y:
检查坐标是否在迷宫网格内
如果为0,是否需要关于递归回溯工作原理的教程?你的问题可能会在你的代码需要修复时出现,而据我所知,你需要帮助理解算法。我了解它是如何工作的,只是不知道如何实现它。这不是问题的一部分宽度=宽度//2*2+1->宽度|=1你需要关于递归回溯工作原理的教程吗?你的问题可能会在你的代码需要修复时出现,而据我所知,你需要帮助理解算法。我了解它是如何工作的,只是不知道如何很好地实现它。这不是问题的一部分宽度=宽度//2*2+1->宽度|=1伟大的解释!你可能想考虑在你的方法中修正这些错误,他们需要有自我作为第一个论点,特别是因为你明确地使用了自我。还可以公平地指出,print将使用_str______方法的结果,并且只有在未实现_str__的情况下才会返回到_repr____方法。这意味着uuu str_uuuu方法返回当前状态的可读版本,这就是您当前的uu repr_uuu方法所做的,并且u repr_uu应返回计算机对状态的表示。谢谢你
伙计!我对我的特殊要求有意见,但你的解释帮助我理解了应该改变什么。谢谢很好的解释!你可能想考虑在你的方法中修正这些错误,他们需要有自我作为第一个论点,特别是因为你明确地使用了自我。还可以公平地指出,print将使用_str______方法的结果,并且只有在未实现_str__的情况下才会返回到_repr____方法。这意味着uuu str_uuuu方法返回当前状态的可读版本,这就是您当前的uu repr_uuu方法所做的,并且u repr_uu应返回计算机对状态的表示。你真了不起,伙计!我对我的特殊要求有意见,但你的解释帮助我理解了应该改变什么。谢谢
>>> maze.create_maze(1, 1)
>>> print(maze)
██████████████████
██ ██ ██
██████ ██ ██ ██
██ ██ ██ ██ ██
██ ██ ██████ ██
██ ██ ██
██ ██████████ ██
██ ██
██████████████████
>>> maze.create_maze(4, 4)
██
██████ ██████
██
██████████████
██ ██
██ ██████████
██ ██
██████████ ██
██