ダイクストラのアルゴリズムが負の重みで機能しない理由を理解しようとしています。 Shortest Paths の例を読んで、次のシナリオを理解しようとしています。
2
A-------B
\ /
3 \ / -2
\ /
C
ウェブサイトから:
エッジがすべて左から右に向けられていると仮定すると、Aで始まる場合、ダイクストラのアルゴリズムはd(A、A)+ length(Edge)を最小化するEdge(A、x)、つまり(A、B)を選択します。次に、d(A、B)= 2を設定し、d(A、y)+ d(y、C)を最小化する別のエッジ(y、C)を選択します。唯一の選択肢は(A、C)で、d(A、C)= 3に設定されます。ただし、AからBへ、Cを介して、全長1の最短パスを見つけることはありません。
次のダイクストラの実装を使用すると、d [B]が1
に更新されない理由を理解できません(アルゴリズムが頂点Cに到達すると、Bでリラックスを実行し、d [B]が等しいことを確認します2
に変更するため、その値を1
に更新します。
Dijkstra(G, w, s) {
Initialize-Single-Source(G, s)
S ← Ø
Q ← V[G]//priority queue by d[v]
while Q ≠ Ø do
u ← Extract-Min(Q)
S ← S U {u}
for each vertex v in Adj[u] do
Relax(u, v)
}
Initialize-Single-Source(G, s) {
for each vertex v V(G)
d[v] ← ∞
π[v] ← NIL
d[s] ← 0
}
Relax(u, v) {
//update only if we found a strictly shortest path
if d[v] > d[u] + w(u,v)
d[v] ← d[u] + w(u,v)
π[v] ← u
Update(Q, v)
}
おかげで、
メイア
あなたが提案したアルゴリズムは、実際にはこのグラフで最短経路を見つけますが、一般的にすべてのグラフではありません。たとえば、次のグラフを検討してください。
例のように、エッジが左から右に向けられていると仮定します。
アルゴリズムは次のように機能します。
d(A)
をzero
に設定し、他の距離をinfinity
に設定します。A
を展開し、d(B)
を1
に、d(C)
をzero
に、d(D)
を99
に設定します。C
を展開しますが、最終的な変更はありません。B
を展開しますが、効果はありません。D
を展開し、d(B)
を-201
に変更します。ただし、この最後でd(C)
は0
のままです。C
への最短パスの長さは-200
です。そのため、場合によっては、アルゴリズムが距離を正確に計算できません。さらに、各ノードから開始ノードA
に到達する方法を示すバックポインターを格納する場合でも、C
からA
に戻る間違ったパスを取得して終了します。
グラフに負のサイクルがない場合、つまり、合計された重みがゼロより小さいサイクルの場合、ダイクストラは負の重みに対しても機能することに注意してください。
もちろん、templatetypedef Dijkstraで作成された例で、負のサイクルがなくても実際に失敗するのはなぜかと疑問に思われるかもしれません。これは、ターゲットノードに到達するとすぐにアルゴリズムを保持する別の停止基準を使用しているためです(または、すべてのノードが一度解決されると、彼は正確に指定しませんでした)。負の重みのないグラフでは、これは正常に機能します。
優先度キュー(ヒープ)が空になったときにアルゴリズムを停止する代替停止基準を使用している場合(この停止基準は質問でも使用されています)、dijkstraは、負の重みを持つグラフでも正しい距離を見つけますが、負のサイクル。
ただし、この場合、負のサイクルのないグラフのダイクストラの漸近的な時間範囲は失われます。これは、負の重みによりより良い距離が見つかった場合、以前に解決されたノードをヒープに再挿入できるためです。このプロパティは、ラベル修正と呼ばれます。
アルゴリズムのどこでもSを使用しませんでした(変更する以外)。 dijkstraの考え方は、頂点がS上にあると、再び修正されることはありません。この場合、BがSの内側にあると、Cを介して再び到達することはありません。
この事実により、O(E + VlogV)の複雑さが保証されます[そうでなければ、エッジを複数回繰り返し、頂点を複数回繰り返します]
つまり、投稿したアルゴリズムは、dijkstraのアルゴリズムで約束されているように、O(E + VlogV)にない可能性があります。
ダイクストラは貪欲なアプローチであるため、頂点がこのループで訪問済みとしてマークされると、後で到達するためのコストの低い別のパスがあったとしても、再度評価されることはありません。また、このような問題は、グラフにネガティブエッジが存在する場合にのみ発生する可能性があります。
貪欲なアルゴリズム、名前が示唆するように、常にその時点で最良と思われる選択を行います。特定のポイントで最適化(最大化または最小化)する必要がある目的関数があります。 貪欲なアルゴリズムは各ステップで貪欲な選択をします目的関数が最適化されるようにします。 Greedyアルゴリズムには、最適解を計算するためのショットが1つしかありませんなので、決して戻って決定を逆にすることはありません
BとCの間を行き来するとどうなるか考えてみてください...
(グラフが方向付けられていない場合のみ関連)
編集済み:問題は、AC *を含むパスが負のウェイトエッジが存在する場合にABよりも優れているという事実に関係していると考えているため、負の重みのエッジでは、ACに進んだ後にBに到達することを選択すると、ABよりも良い経路を見つけることは不可能です。
"2)負の重みを持つグラフの最短パスにダイクスラのアルゴリズムを使用できます。1つのアイデアとして、最小の重み値を計算し、すべての重みに正の値(最小の重み値の絶対値に等しい)を追加し、ダイクスラのアルゴリズムを実行できます変更されたグラフの場合。このアルゴリズムは機能しますか?」
すべての最短パスが同じ長さでない限り、これは絶対に機能しません。たとえば、長さが2つのエッジの最短パスを指定し、各エッジに絶対値を追加すると、合計パスコストが2 * | max negative weight |だけ増加します。一方、長さが3エッジの別のパスは、パスコストが3 * | max negative weight |だけ増加します。したがって、異なるパスはすべて、異なる量だけ増加します。
TL; DR:投稿した擬似コードの場合、負の重みで機能します。
重要なのはダイクストラのアルゴリズムには3つのバージョンがありますが、この質問の答えはすべてこれらのバリアント間の違いを無視しています。
for
- loopを使用して頂点を緩和します。これは、ダイクストラのアルゴリズムを実装する最も簡単な方法です。時間の複雑さはO(V ^ 2)です。バージョン1と2は、負の重みを持つグラフでは失敗します(そのような場合に正しい答えが得られれば、それは単なる偶然です)が、バージョン3はまだ動作します。
元の質問の下に投稿された擬似コードは上記のバージョン3なので、負の重みで動作します。
アルゴリズム(第4版) からの良いリファレンスがあります。これは、上記のバージョン2および3のJava実装を含みます:
Q.ダイクストラのアルゴリズムは負の重みで機能しますか?
A.はい、いいえ。頂点を優先度キューに複数回キューイングできるかどうかに応じて、ダイクストラのアルゴリズムと呼ばれる2つの最短パスアルゴリズムがあります。重みが負でない場合、2つのバージョンが一致します(頂点が複数回キューに登録されることはないため)。 DijkstraSP.Javaで実装されたバージョン(頂点を複数回キューに入れることができます)は、負のEdge重み(ただし負のサイクルはありません)が存在する場合は正しいですが、最悪の場合の実行時間は指数関数的です。 (エッジが重み付きのダイグラフに負の重みを持つEdgeがある場合、DijkstraSP.Javaは例外をスローするため、プログラマーはこの指数関数的な動作に驚かないように注意します。) 2回以上(たとえば、marked []配列を使用して緩和された頂点をマークする)、アルゴリズムはE log V時間で実行されることが保証されますが、負の重みを持つエッジがある場合、誤った結果が生じる可能性があります.
実装の詳細と、Bellman-Fordアルゴリズムとバージョン3の接続については、 この回答はzhihuから を参照してください。それも私の答えです(ただし、中国語で)。現在、私はそれを英語に翻訳する時間がありません。誰かがこれを行い、stackoverflowでこの答えを編集できたら本当に感謝しています。