元のアルゴリズムが BFS であった場合、「最小」はエッジの全順序によって誘導される辞書式順序に従っている最小の最短パスを探していOrd
ます (もちろん、「最短」はパスの長さに従っています)。
amit によって提案された重みを微調整するという考えは自然なものですが、情報の破棄を避けるために重みがパスの長さに匹敵するビット数を持つ必要があるため、あまり実用的ではないと思います。アルゴリズムは桁違いに遅くなります。
ありがたいことに、これは A* に 2 つの単純で安価な変更を加えることで実現できます。
- ゴールに到達したら、任意の最短パスをゴールに戻す代わりに、最短パスに属するすべてのノードを訪問するように、パスの長さが長くなるまでノードを訪問し続ける必要があります。
- パスを再構築するとき、最短パスに寄与するノードのセットを構築します。
start
このセットは、すべての最短パス エッジを考慮すると DAG 構造を持ち、この DAG で からへの辞書編集最小パスを簡単に見つけることができます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
警告: クヌースの言葉を引用すると、私はそれが正しいことを証明しただけで、試したわけではありません。
パフォーマンスへの影響に関しては、最小限に抑える必要があります。検索ループは、従来の A* よりも 1 単位高いスコアを持つノードのみを訪問し、再構築フェーズは、最短パスに属するノード数で準線形です。ほとんどの場合、最短経路が 1 つしかない場合、影響は小さくなります。この特殊なケースを最適化することもできます。たとえばancestor
、従来のケースのようにノードを記憶することで、複数の先祖がある場合 (つまり、 の場合path_length[y] == path_length_through_x
) に特別なエラー値を設定します。検索ループが終了したら、ancestor
従来の A* と同様にパスを取得しようとします。パスの構築時にエラー値が発生した場合にのみ、フル パスの再構築を実行する必要があります。