如果原始算法是 BFS,您正在寻找最短路径中的最小路径,其中“最小”是根据Ord
边缘上的某些总顺序引起的词典顺序(当然“最短”是根据路径长度)。
amit 建议的调整权重的想法是很自然的,但我认为这不是很实用,因为权重需要具有与路径长度相当的位数以避免丢弃信息,这会使算法慢几个数量级。
值得庆幸的是,这仍然可以通过对 A* 进行两个简单且廉价的修改来完成:
- 一旦我们到达目标,我们应该继续访问节点直到路径长度增加,而不是返回一个任意的最短路径到目标,这样我们就访问了所有属于最短路径的节点。
- 在重建路径时,我们构建了有助于最短路径的节点集。在考虑所有最短路径边时,该集合具有 DAG 结构,现在很容易在该 DAG 中找到词典编纂的最小路径
start
,goal
这是所需的解决方案。
从示意图上看,经典的 A* 是:
path_length = infinity for every node
path_length[start] = 0
while score(goal) > minimal score of unvisited nodes:
x := any unvisited node with minimal score
mark x as visited
for y in unvisited neighbors of x:
path_length_through_x = path_length[x] + d(x,y)
if path_length[y] > path_length_through_x:
path_length[y] = path_length_through_x
ancestor[y] = x
return [..., ancestor[ancestor[goal]], ancestor[goal], goal]
哪里score(x)
代表path_length[x] + heuristic(x, goal)
。
我们简单地将严格的while
循环不等式变成非严格的不等式,并添加路径重建阶段:
path_length = infinity for every node
path_length[start] = 0
while score(goal) >= minimal score of unvisited nodes:
x := any unvisited node with minimal score
mark x as visited
for y in unvisited neighbors of x:
path_length_through_x = path_length[x] + d(x,y)
if path_length[y] > path_length_through_x:
path_length[y] = path_length_through_x
optimal_nodes = [goal]
for every x in optimal_nodes: // note: we dynamically add nodes in the loop
for y in neighbors of x not in optimal_nodes:
if path_length[x] == path_length[y] + d(x,y):
add y to optimal_nodes
path = [start]
x = start
while x != goal:
z = undefined
for y in neighbors of x that are in optimal_nodes:
if path_length[y] == path_length[x] + d(x,y):
z = y if (x,y) is smaller than (x,z) according to Ord
x = z
append x to path
return path
警告:引用 Knuth 的话,我只是证明它是正确的,没有尝试过。
至于性能影响,应该是最小的:搜索循环只访问得分比经典 A* 高 1 个单位的节点,并且重建阶段在属于最短路径的节点数量上是准线性的。如果正如您所暗示的那样,在大多数情况下只有一条最短路径,则影响较小。您甚至可以针对这种特殊情况进行优化,例如通过ancestor
在经典情况中记住一个节点,当有多个祖先时(即 when path_length[y] == path_length_through_x
),您将其设置为一个特殊的错误值。ancestor
搜索循环结束后,您将尝试像经典 A* 中那样检索路径;如果在构建路径时遇到错误值,您只需要执行完整路径重建。