ダイクストラのアルゴリズムは次のように教えられました
while pqueue is not empty:
distance, node = pqueue.delete_min()
if node has been visited:
continue
else:
mark node as visited
if node == target:
break
for each neighbor of node:
pqueue.insert(distance + distance_to_neighbor, neighbor)
しかし、私はアルゴリズムに関するいくつかの読書をしてきましたが、多くのバージョンでは挿入ではなく減少キーを使用しています。
これはなぜですか、2つのアプローチの違いは何ですか?
ノードを再挿入するのではなく減少キーを使用する理由は、優先度キュー内のノードの数を小さくして、優先度キューのデキューの総数を小さくし、各優先度キューのコストを低く抑えるためです。
ノードを新しい優先度で優先度キューに再挿入するダイクストラのアルゴリズムの実装では、グラフのmエッジごとに1つのノードが優先度キューに追加されます。つまり、優先キューにはm個のエンキュー操作とm個のデキュー操作があり、合計実行時間はO(m Te + m Td)、Te 優先度キューにエンキューするのに必要な時間とTd 優先度キューからデキューするのに必要な時間です。
減少キーをサポートするダイクストラのアルゴリズムの実装では、ノードを保持する優先度キューはその中のn個のノードで始まり、アルゴリズムの各ステップで1つのノードが削除されます。これは、ヒープデキューの総数がnであることを意味します。各ノードには、それにつながる各Edgeに対して潜在的に1回ずつ減少キーが呼び出されるため、実行される減少キーの総数は最大でmです。これにより、ランタイムが(n Te + n Td + m Tk)、Tk 減少キーを呼び出すのに必要な時間です。
では、これはランタイムにどのような影響を与えますか?これは、使用する優先度キューによって異なります。さまざまな優先度キューと、さまざまなダイクストラのアルゴリズム実装の全体的なランタイムを示す簡単な表を次に示します。
Queue | T_e | T_d | T_k | w/o Dec-Key | w/Dec-Key
---------------+--------+--------+--------+-------------+---------------
Binary Heap |O(log N)|O(log N)|O(log N)| O(M log N) | O(M log N)
Binomial Heap |O(log N)|O(log N)|O(log N)| O(M log N) | O(M log N)
Fibonacci Heap | O(1) |O(log N)| O(1) | O(M log N) | O(M + N log N)
ご覧のように、ほとんどのタイプの優先度キューでは、漸近ランタイムに実際の違いはなく、キーの減少バージョンの方がはるかに優れているとは限りません。ただし、 フィボナッチヒープ 優先度キューの実装を使用する場合、減少キーを使用すると、ダイクストラのアルゴリズムは漸近的に効率的になります。
要するに、減少キーと適切な優先度キューを使用すると、エンキューとデキューを続けた場合、ダイクストラの漸近ランタイムを可能な範囲を超えてドロップできます。
この点に加えて、Gabowの最短経路アルゴリズムなどのいくつかのより高度なアルゴリズムは、ダイクストラのアルゴリズムをサブルーチンとして使用し、キーの減少の実装に大きく依存しています。彼らは、有効な距離の範囲を事前に知っていれば、その事実に基づいて超効率的な優先度キューを構築できるという事実を使用します。
お役に立てれば!
2007年には、減少キーバージョンと挿入バージョンの使用の実行時間の違いを調査した論文がありました。 http://www.cs.utexas.edu/users/shaikat/papers/TR-07-54.pdf を参照してください
彼らの基本的な結論は、ほとんどのグラフに減少キーを使用しないことでした。特にスパースグラフの場合、非減少キーは減少キーバージョンよりも大幅に高速です。詳細については、ペーパーを参照してください。