Python 四大迷宫生成算法实现(1): 递归回溯算法

本文详细介绍了一种使用递归回溯算法生成迷宫的方法。通过深度优先搜索,算法随机选择起点,标记为已访问,并在遇到死胡同时回溯。文章提供了完整的Python代码实现,包括地图类的设计和主函数的讲解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

递归回溯算法简介

下图是算法使用的地图,地图最外围默认是一圈墙,其中白色单元是迷宫单元,黑色单元是墙,相邻白色单元之前的墙是可以被去掉的。可以看到这个地图中所有的迷宫单元在地图中的位置(X,Y),比如(1,1),(5,9)都是奇数,可以表示成(2 * x+1, 2 * y+1), x和y的取值范围从0到4。在迷宫生成算法中会用到这个表示方式。同时迷宫的长度和宽度必须为奇数。
地图示例
递归回溯是一个深度优先算法,如果当前单元有相邻的未访问过的迷宫单元,就一直向前搜索,直到当前单元没有未访问过的迷宫单元,才返回查找之前搜索路径上未访问的迷宫单元,所以用堆栈来维护已访问过的迷宫单位。

算法主循环,重复下面步骤2直到堆栈为空:
1 随机选择一个迷宫单元作为起点,加入堆栈并标记为已访问
2 当堆栈非空时,从栈顶获取一个迷宫单元(不用出栈),进行循环

  • 如果当前迷宫单元有未被访问过的相邻迷宫单元
    • 随机选择一个未访问的相邻迷宫单元
    • 去掉当前迷宫单元与相邻迷宫单元之间的墙
    • 标记相邻迷宫单元为已访问,并将它加入堆栈
  • 否则,当前迷宫单元没有未访问的相邻迷宫单元
    • 则栈顶的迷宫单元出栈

关键代码介绍

保存基本信息的地图类

地图类用于保存和获取迷宫算法使用的基础地图信息。

  1. 先创建一个map类, 初始化参数设置地图的长度和宽度,并设置保存地图信息的二维数据map的值为0, 值为0表示该单元可以移动,值为1表示该单元是墙。定义在算法中用到的Enum 类MAP_ENTRY_TYPE 和 WALL_DIRECTION。
class MAP_ENTRY_TYPE(Enum):
	MAP_EMPTY = 0,
	MAP_BLOCK = 1,

class WALL_DIRECTION(Enum):
	WALL_LEFT = 0,
	WALL_UP = 1,
	WALL_RIGHT = 2,
	WALL_DOWN = 3,

class Map():
	def __init__(self, width, height):
		self.width = width
		self.height = height
		self.map = [[0 for x in range(self.width)] for y in range(self.height)]
  1. 在map类中添加将整个map单元设置为某个值的函数,设置某个单元为某个值的函数,和某个单元是否被访问的函数。
	def resetMap(self, value):
		for y in range(self.height):
			for x in range(self.width):
				self.setMap(x, y, value)
	
	def setMap(self, x, y, value):
		if value == MAP_ENTRY_TYPE.MAP_EMPTY:
			self.map[y][x] = 0
		elif value == MAP_ENTRY_TYPE.MAP_BLOCK:
			self.map[y][x] = 1
	
	def isVisited(self, x, y):
		return self.map[y][x] != 1
  1. 在map类中添加一个显示地图的函数,可以看到,这边只是简单的打印出所有节点的值,值为1时打印’#"表示强,或1的意思上面已经说明,在后面显示寻路算法结果时,会使用到值2,表示一条从开始节点到目标节点的路径。
	def showMap(self):
		for row in self.map:
			s = ''
			for entry in row:
				if entry == 0:
					s += ' 0'
				elif entry == 1:
					s += ' #'
				else:
					s += ' X'
			print(s)

算法主函数介绍

doRecursiveBacktracker 函数 先调用resetMap函数将地图都设置为墙,迷宫单元所在位置为墙表示未访问。有个注意点是地图的长宽和迷宫单元的位置取值范围的对应关系。
假如地图的宽度是31,长度是21,对应的迷宫单元的位置取值范围是 x(0,15), y(0,10), 因为迷宫单元(x,y)对应到地图上的位置是(2 * x+1, 2 * y+1)。
recursiveBacktracker 函数就是上面算法主循环的实现。

# recursive backtracker algorithm
def recursiveBacktracker(map, width, height):
	startX, startY = (randint(0, width-1), randint(0, height-1))
	print("start(%d, %d)" % (startX, startY))
	map.setMap(2*startX+1, 2*startY+1, MAP_ENTRY_TYPE.MAP_EMPTY)
	
	checklist = [] 
	checklist.append((startX, startY))
	while len(checklist):
		# use checklist as a stack, get entry from the top of stack 
		entry = checklist[-1]
		if not checkAdjacentPos(map, entry[0], entry[1], width, height, checklist):
			# the entry has no unvisited adjacent entry, so remove it from checklist
			checklist.remove(entry)
			
def doRecursiveBacktracker(map):
	# set all entries of map to wall
	map.resetMap(MAP_ENTRY_TYPE.MAP_BLOCK)
	recursiveBacktracker(map, (map.width-1)//2, (map.height-1)//2)

checkAdjacentPos 函数检查当前迷宫单元的是否有未访问的相邻单元,如果有,则随即选取一个相邻单元,标记未已访问,并去掉当前迷宫单元与相邻迷宫单元之间的墙。如果没有,则不做操作。

# find unvisited adjacent entries of four possible entris
# then add random one of them to checklist and mark it as visited
def checkAdjacentPos(map, x, y, width, height, checklist):
	directions = []
	if x > 0:
		if not map.isVisited(2*(x-1)+1, 2*y+1):
			directions.append(WALL_DIRECTION.WALL_LEFT)
				
	if y > 0:
		if not map.isVisited(2*x+1, 2*(y-1)+1):
			directions.append(WALL_DIRECTION.WALL_UP)

	if x < width -1:
		if not map.isVisited(2*(x+1)+1, 2*y+1):
			directions.append(WALL_DIRECTION.WALL_RIGHT)
		
	if y < height -1:
		if not map.isVisited(2*x+1, 2*(y+1)+1):
			directions.append(WALL_DIRECTION.WALL_DOWN)
		
	if len(directions):
		direction = choice(directions)
		if direction == WALL_DIRECTION.WALL_LEFT:
				map.setMap(2*(x-1)+1, 2*y+1, MAP_ENTRY_TYPE.MAP_EMPTY)
				map.setMap(2*x, 2*y+1, MAP_ENTRY_TYPE.MAP_EMPTY)
				checklist.append((x-1, y))
		elif direction == WALL_DIRECTION.WALL_UP:
				map.setMap(2*x+1, 2*(y-1)+1, MAP_ENTRY_TYPE.MAP_EMPTY)
				map.setMap(2*x+1, 2*y, MAP_ENTRY_TYPE.MAP_EMPTY)
				checklist.append((x, y-1))
		elif direction == WALL_DIRECTION.WALL_RIGHT:
				map.setMap(2*(x+1)+1, 2*y+1, MAP_ENTRY_TYPE.MAP_EMPTY)
				map.setMap(2*x+2, 2*y+1, MAP_ENTRY_TYPE.MAP_EMPTY)
				checklist.append((x+1, y))
		elif direction == WALL_DIRECTION.WALL_DOWN:
			map.setMap(2*x+1, 2*(y+1)+1, MAP_ENTRY_TYPE.MAP_EMPTY)
			map.setMap(2*x+1, 2*y+2, MAP_ENTRY_TYPE.MAP_EMPTY)
			checklist.append((x, y+1))
		return True
	else:
		# if not find any unvisited adjacent entry
		return False

代码的初始化

可以调整地图的长度,宽度,注意长度和宽度必须为奇数。

def run():
	WIDTH = 31
	HEIGHT = 21
	map = Map(WIDTH, HEIGHT)
	doRecursiveBacktracker(map)
	map.showMap()	
	
if __name__ == "__main__":
	run()

执行的效果图如下,start 表示第一个随机选择的迷宫单元。迷宫中’#'表示墙,'0’表示通道。
算法生成的迷宫

完整代码

使用python3.7编译

from random import randint, choice
from enum import Enum

class MAP_ENTRY_TYPE(Enum):
	MAP_EMPTY = 0,
	MAP_BLOCK = 1,

class WALL_DIRECTION(Enum):
	WALL_LEFT = 0,
	WALL_UP = 1,
	WALL_RIGHT = 2,
	WALL_DOWN = 3,

class Map():
	def __init__(self, width, height):
		self.width = width
		self.height = height
		self.map = [[0 for x in range(self.width)] for y in range(self.height)]
	
	def resetMap(self, value):
		for y in range(self.height):
			for x in range(self.width):
				self.setMap(x, y, value)
	
	def setMap(self, x, y, value):
		if value == MAP_ENTRY_TYPE.MAP_EMPTY:
			self.map[y][x] = 0
		elif value == MAP_ENTRY_TYPE.MAP_BLOCK:
			self.map[y][x] = 1
	
	def isVisited(self, x, y):
		return self.map[y][x] != 1

	def showMap(self):
		for row in self.map:
			s = ''
			for entry in row:
				if entry == 0:
					s += ' 0'
				elif entry == 1:
					s += ' #'
				else:
					s += ' X'
			print(s)

# find unvisited adjacent entries of four possible entris
# then add random one of them to checklist and mark it as visited
def checkAdjacentPos(map, x, y, width, height, checklist):
	directions = []
	if x > 0:
		if not map.isVisited(2*(x-1)+1, 2*y+1):
			directions.append(WALL_DIRECTION.WALL_LEFT)
				
	if y > 0:
		if not map.isVisited(2*x+1, 2*(y-1)+1):
			directions.append(WALL_DIRECTION.WALL_UP)

	if x < width -1:
		if not map.isVisited(2*(x+1)+1, 2*y+1):
			directions.append(WALL_DIRECTION.WALL_RIGHT)
		
	if y < height -1:
		if not map.isVisited(2*x+1, 2*(y+1)+1):
			directions.append(WALL_DIRECTION.WALL_DOWN)
		
	if len(directions):
		direction = choice(directions)
		#print("(%d, %d) => %s" % (x, y, str(direction)))
		if direction == WALL_DIRECTION.WALL_LEFT:
				map.setMap(2*(x-1)+1, 2*y+1, MAP_ENTRY_TYPE.MAP_EMPTY)
				map.setMap(2*x, 2*y+1, MAP_ENTRY_TYPE.MAP_EMPTY)
				checklist.append((x-1, y))
		elif direction == WALL_DIRECTION.WALL_UP:
				map.setMap(2*x+1, 2*(y-1)+1, MAP_ENTRY_TYPE.MAP_EMPTY)
				map.setMap(2*x+1, 2*y, MAP_ENTRY_TYPE.MAP_EMPTY)
				checklist.append((x, y-1))
		elif direction == WALL_DIRECTION.WALL_RIGHT:
				map.setMap(2*(x+1)+1, 2*y+1, MAP_ENTRY_TYPE.MAP_EMPTY)
				map.setMap(2*x+2, 2*y+1, MAP_ENTRY_TYPE.MAP_EMPTY)
				checklist.append((x+1, y))
		elif direction == WALL_DIRECTION.WALL_DOWN:
			map.setMap(2*x+1, 2*(y+1)+1, MAP_ENTRY_TYPE.MAP_EMPTY)
			map.setMap(2*x+1, 2*y+2, MAP_ENTRY_TYPE.MAP_EMPTY)
			checklist.append((x, y+1))
		return True
	else:
		# if not find any unvisited adjacent entry
		return False
			
# recursive backtracker algorithm
def recursiveBacktracker(map, width, height):
	startX, startY = (randint(0, width-1), randint(0, height-1))
	print("start(%d, %d)" % (startX, startY))
	map.setMap(2*startX+1, 2*startY+1, MAP_ENTRY_TYPE.MAP_EMPTY)
	
	checklist = [] 
	checklist.append((startX, startY))
	while len(checklist):
		# use checklist as a stack, get entry from the top of stack 
		entry = checklist[-1]
		if not checkAdjacentPos(map, entry[0], entry[1], width, height, checklist):
			# the entry has no unvisited adjacent entry, so remove it from checklist
			checklist.remove(entry)
			
def doRecursiveBacktracker(map):
	# set all entries of map to wall
	map.resetMap(MAP_ENTRY_TYPE.MAP_BLOCK)
	recursiveBacktracker(map, (map.width-1)//2, (map.height-1)//2)

def run():
	WIDTH = 31
	HEIGHT = 21
	map = Map(WIDTH, HEIGHT)
	doRecursiveBacktracker(map)
	map.showMap()	
	
if __name__ == "__main__":
	run()
<think>嗯,用户想用Python实现递归算法解决迷宫问题。首先我得回忆一下迷宫问题的基本思路。通常迷宫问题需要通过递归来尝试不同的路径,直到找到出口。记得引用里的内容提到,递归需要逼近退出条件,否则会无限递归,导致栈溢出。所以首先要确定递归的终止条件。 迷宫通常用二维数组表示,0表示通路,1是墙。起点和终点坐标需要明确。递归函数应该接收当前位置,然后尝试上下左右四个方向移动。每次移动前要检查是否越界,是否是墙,或者已经走过。如果走到终点,就返回True,否则继续探索。 用户提供的引用3中有一些代码片段,比如valid函数检查位置是否有效,neighbors获取相邻位置。这些可以作为参考。不过用户希望用递归,而不是像A*这样的算法。所以可能需要一个回溯的机制,当一条路走不通时,标记为死路并回溯。 另外,引用2提到递归方法中的局部变量是独立的,但引用类型如数组会被共享。所以迷宫的状态需要被修改,比如走过的路径标记为2,死路标记为3,这样在回溯时知道哪些路径已经尝试过。 需要确保递归函数的结构正确:首先检查当前位置是否为终点,如果是则返回成功。否则,尝试四个方向,递归调用自身。如果所有方向都走不通,回溯并标记当前位置为死路。 可能的错误点包括没有正确标记已走过的路径,导致无限循环。比如,如果不标记,可能会重复走同一个位置。此外,递归深度过大可能导致栈溢出,但一般迷宫问题规模应该不会太大。 测试用例的话,可以设计一个小迷宫,比如3x3,手动验证路径是否正确。可视化可能用pygame,但用户目前只需要递归求解,所以先关注算法本身。 总结步骤: 1. 定义迷宫结构,0为路,1为墙。 2. 确定起点和终点。 3. 递归函数:检查当前位置是否终点。 4. 尝试四个方向,检查是否有效(不越界,不是墙,未走过)。 5. 递归调用,如果返回True,说明找到路径,否则回溯。 6. 标记死路,防止重复访问。 需要处理的情况:多条路径时,找到一条即可,或者找到最短路径?用户可能只需要找到任一解,所以遇到终点就返回。 代码结构可能如下: def solve_maze(maze, x, y): if (x, y) 是终点: return True if 当前位置无效: return False 标记当前位置为已走过 对四个方向递归调用solve_maze 如果有任一方向返回True,返回True 否则标记为死路,返回False 需要将maze作为参数传递,或者使用类来维护状态。根据引用3中的代码,可能使用全局变量或修改传入的maze。在Python中,列表是可变对象,修改会反映到原变量,所以可以直接修改。 测试时,注意迷宫边界,避免索引错误。比如,引用3中的valid函数检查x和y是否在0到len(maze)-1之间。这些检查在递归函数中也需要处理。 最后,用户可能需要示例代码和解释,以及相关问题。比如如何优化,如何处理更复杂迷宫,或者可视化方法。</think>根据递归算法原理和迷宫问题特点,我们将通过以下步骤实现Python解决方案: ### 一、迷宫问题递归实现步骤 1. **迷宫表示**:使用二维数组$maze[y][x]$表示迷宫结构(坐标轴方向) 2. **移动方向**:定义四个移动方向(上→右→下→左) 3. **标记机制**: - 2:已走过的路径 - 3:死胡同 4. **终止条件**: - 到达终点坐标 - 当前位置不可通行 ### 二、Python实现代码 ```python class MazeSolver: WALL = 1 PATH = 0 WALKED = 2 DEAD = 3 def __init__(self, maze, start, end): self.maze = [row[:] for row in maze] # 创建迷宫副本 self.start = start self.end = end self.path = [] def is_valid(self, x, y): """验证坐标有效性""" if x < 0 or y < 0: return False if x >= len(self.maze[0]) or y >= len(self.maze): return False return self.maze[y][x] in (self.PATH, self.WALKED) def solve(self, x=None, y=None): """递归求解主函数""" if x is None: x, y = self.start # 终止条件判断 if (x, y) == self.end: self.path.append((x, y)) return True # 当前位置有效性验证 if not self.is_valid(x, y): return False # 标记当前路径 self.maze[y][x] = self.WALKED self.path.append((x, y)) # 递归探索四个方向(顺序影响路径选择) directions = [(0, 1), (1, 0), (0, -1), (-1, 0)] for dx, dy in directions: next_x, next_y = x + dx, y + dy if self.solve(next_x, next_y): return True # 回溯标记死胡同 self.maze[y][x] = self.DEAD self.path.pop() return False # 使用示例 maze = [ [0, 1, 0, 0, 0], [0, 1, 0, 1, 0], [0, 0, 0, 1, 0], [0, 1, 1, 1, 0], [0, 0, 0, 1, 0] ] solver = MazeSolver(maze, (0,0), (4,4)) if solver.solve(): print("找到路径:", solver.path) else: print("无可行路径") ``` ### 三、核心逻辑说明 1. **递归栈管理**:每次递归调用都会创建新的栈帧保存当前状态[^2] 2. **路径回溯**:当相邻位置都无法到达终点时,执行`self.path.pop()`回溯 3. **空间复杂度**:$O(n)$,n为迷宫路径长度 4. **时间复杂度**:最坏情况$O(4^{n})$,实际通过标记机制避免重复计算 ### 四、算法优化建议 1. **方向优先级**:根据终点位置调整探索顺序可提升效率 2. **记忆化搜索**:缓存已探索位置的可行性判断 3. **路径可视化**:使用pygame实时显示探索过程(参考引用3)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值