
本文深入探讨a*路径搜索算法的一种单队列实现方式。许多a*伪代码会同时使用open列表(优先队列)和closed列表(集合),而该实现仅依赖一个优先队列。我们将解析其工作原理,揭示如何通过巧妙地利用节点的分数(g_score和f_score)以及优先队列的特性,隐式地管理已访问节点的状态,从而无需显式的closed集合,仍能确保算法的正确性和效率。
A*算法是一种启发式搜索算法,广泛应用于路径规划和图搜索问题。它通过评估每个节点的总成本(f_score)来指导搜索方向,f_score由两部分组成:
总成本公式为:f_score = g_score + h_score。A*算法总是优先探索f_score最低的节点。
在许多A*算法的伪代码描述中,通常会维护两个核心数据结构:
当找到一条通往某个节点的更优路径时,如果该节点已在OPEN列表中,会更新其g_score和f_score并调整其在优先队列中的位置;如果该节点已在CLOSED列表中,则需要将其从CLOSED列表中移除并重新加入OPEN列表(或直接更新其在OPEN列表中的信息,如果它也被重新加入)。
以下是一个使用Python实现的A*算法示例,它仅使用一个优先队列open,而没有显式的CLOSED集合:
from pyamaze import maze,agent,textLabel
from queue import PriorityQueue
def h(cell1,cell2):
"""计算曼哈顿距离作为启发式函数"""
x1,y1=cell1
x2,y2=cell2
return abs(x1-x2) + abs(y1-y2)
def aStar(m):
start=(m.rows,m.cols)
# g_score: 从起点到某个单元格的实际成本
g_score={cell:float('inf') for cell in m.grid}
g_score[start]=0
# f_score: g_score + h_score
f_score={cell:float('inf') for cell in m.grid}
f_score[start]=h(start,(1,1)) # 目标点为(1,1)
# open: 优先队列,存储待探索的节点
# 存储格式为 (f_score, h_score_for_tie_breaking, cell)
open=PriorityQueue()
open.put((h(start,(1,1)),h(start,(1,1)),start))
aPath={} # 存储路径,childCell:currCell
while not open.empty():
currCell=open.get()[2] # 获取f_score最低的节点
if currCell==(1,1): # 到达目标点
break
# 遍历当前节点的所有邻居
for d in 'ESNW': # 东、南、西、北
if m.maze_map[currCell][d]==True: # 如果存在通路
# 计算邻居单元格的坐标
if d=='E':
childCell=(currCell[0],currCell[1]+1)
if d=='W':
childCell=(currCell[0],currCell[1]-1)
if d=='N':
childCell=(currCell[0]-1,currCell[1])
if d=='S':
childCell=(currCell[0]+1,currCell[1])
# 计算到达邻居单元格的临时g_score和f_score
temp_g_score=g_score[currCell]+1 # 假设每一步成本为1
temp_f_score=temp_g_score+h(childCell,(1,1))
# 如果通过当前路径到达邻居单元格的f_score更低,则更新
if temp_f_score < f_score[childCell]:
g_score[childCell]= temp_g_score
f_score[childCell]= temp_f_score
open.put((temp_f_score,h(childCell,(1,1)),childCell)) # 将邻居加入优先队列
aPath[childCell]=currCell # 记录路径
# 路径重建
fwdPath={}
cell=(1,1)
while cell!=start:
fwdPath[aPath[cell]]=cell
cell=aPath[cell]
return fwdPath
if __name__=='__main__':
m=maze(5,5)
m.CreateMaze()
path=aStar(m)
a=agent(m,footprints=True)
m.tracePath({a:path})
l=textLabel(m,'A Star Path Length',len(path)+1)
m.run()该实现之所以能够仅使用一个优先队列,其核心在于对g_score和f_score的巧妙运用,以及优先队列的特性:
初始化为无穷大:
通过f_score更新实现“重访”:
传统的A*伪代码通常会明确检查节点是否在OPEN或CLOSED列表中,并根据情况进行移除或添加。例如:
if neighbor in OPEN and cost less than g(neighbor): remove neighbor from OPEN, because new path is better if neighbor in CLOSED and cost less than g(neighbor): remove neighbor from CLOSED if neighbor not in OPEN and neighbor not in CLOSED: set g(neighbor) to cost add neighbor to OPEN
与此相比,单队列实现更为简洁。它避免了在OPEN列表中查找和删除节点的复杂性(Python的PriorityQueue本身不支持高效的删除任意元素),而是选择:如果找到更好的路径,就直接将新信息(包含更低f_score的节点)再次放入优先队列。即使同一个节点在队列中出现多次,由于我们总是从队列中取出f_score最低的节点,并且只有当temp_f_score < f_score[childCell]时才更新,所以当一个节点被多次弹出时,只有第一次弹出(对应最低f_score)才会真正导致其邻居被扩展,后续弹出(对应较高的f_score)会被忽略,因为此时f_score[childCell]已经是一个更低的值。
A算法的单队列实现是一种有效且常见的策略。它通过将节点的分数(g_score和f_score)初始化为无穷大,并在发现更优路径时更新这些分数并重新将节点加入优先队列,从而隐式地管理了传统A算法中CLOSED集合的功能。这种方法简化了代码结构,避免了对CLOSED集合的显式维护和查找操作,同时仍能保证算法找到最优路径。理解这种实现方式的关键在于认识到f_score的更新机制以及优先队列的特性,它们共同协作,确保了算法的正确性和效率。
以上就是深入理解A算法:单队列实现的巧妙之处的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号