Robin Pourtaud - Personal Blog
ベルマンフォードアルゴリズム - 例を使って説明!
ベルマンフォードアルゴリズム - 例を使って説明!
ベルマンフォードアルゴリズムは、負のサイクルがない重み付き有向グラフの最短経路アルゴリズムです。
By Robin Pourtaud on

はじめに

ベルマンフォードアルゴリズムは、負のサイクルがない有向グラフ内で、ソースから他のすべての頂点への最短経路を見つける最初のアルゴリズムの1つです。これは動的計画法のアルゴリズムで、グラフのエッジを反復的に緩和します。この記事では、このアルゴリズムを説明するための例を示します!

ベルマンフォードアルゴリズムの主なステップ

  1. 初期化: まず、ソースを除くすべての頂点がソースから無限に遠いと仮定します。ソース自体はもちろん距離0です。目標は、その頂点の重みを反復的に更新することです。
  2. 緩和: ここで魔法が起こります。グラフのすべてのエッジについて、ソース頂点の重みとエッジの重みの合計が目的地の頂点の重みより小さいかどうかを確認します。そうであれば、目的地の頂点の重みを更新します。たとえば、最初のエッジの値が4の場合、目的地の重みは4になります。
  3. 終了: 緩和ステップを回繰り返します。これは、2つの頂点間の最短経路は最大でエッジを持つことができるためです。回の反復後も重みに変化がある場合、グラフに負のサイクルがあることを意味します。

ベルマンフォードは負のサイクルでは機能しません

負のサイクルは、総重みが厳密に負のサイクルです。この文脈では、AからCに行く場合、無限にAに戻って重みを無限に減らすのが最短経路であると想像できます。ベルマンフォードアルゴリズムは、この場合には明らかに機能しません。

**しかしながら、**アルゴリズムが最短経路を見つけるのに回以上の反復を必要としないことを既に確立しているため、この特性を使用して負のサイクルを検出できます。回の反復後も重みに変化がある場合、グラフに負のサイクルがあることを意味します。

Diagram

他のアルゴリズムの実装は、反復回数を減らすことができます。

アルゴリズムアルゴリズムを深く掘り下げましょう!

Pythonスタイルの疑似コードでは、次のようにアルゴリズムを実装できます:

def bellman_ford(G: Graph, p: function(int, int) -> int, source: int) -> [prev: List[int], weight: List[int]]:
    '''ベルマンフォードの疑似コード。

    引数: 
        G : Graph : 負のサイクルがないグラフ
        p : function(int, int) -> int : エッジ(u, v)の重みをO(1)で返す関数
        source : int : V内のソース頂点のインデックス
    
    戻り値:
        prev : List[int] : 各頂点の前の頂点を含む配列。
        weight : List[int] : 各頂点について、ソースからこの頂点までの最短経路の重み(重みの合計)を含む配列。
    '''

    # 初期化
    for v in G.vertices:
        weight[v] = inf
        prev[v] = '-'


    weight[source] = 0 # ソースからソースまでの重みは0

    for iter in [1, |V| - 1]: # Wikipediaでの停止条件の証明
        for (u, v) in G.edges:
            if weight[u] + p(u, v) < weight[v]:
                weight[v] = weight[u] + p(u, v)
                prev[v] = u

    return prev, weight

リラクゼーションステップ if weight[u] + p(u, v) < weight[v]: はアルゴリズムの重要なステップです。ここでアルゴリズムは頂点の重みを更新します。

修正された演習

以下の有向グラフにベルマンフォードアルゴリズムを適用して、ソース s から p までの最短経路を見つけてください。

Diagram

解決策

重みと前の頂点を追跡するために2つのテーブルを作成します。

  • 最初のテーブルは頂点の重みを含みます。これは、ソースから頂点までの現在の最短距離に相当します。
  • 2番目のテーブルは、最短経路で現在の頂点に到達するために取るべき前の頂点を含みます。

重みテーブル

イテレーションs1234p
Iter 00
Iter 1042
Iter 20419-24
Iter 30419-2017
Iter 40419-2017
Iter 50419-2017

非決定的な側面

ここに示されているものとは異なる結果が得られる場合があります!

エッジが緩和される順序によって結果が変わることがあります。 例えば、エッジ3 -> 4の重みがエッジ1 -> 4の重みの前に更新されると、4の重みは4ではなく0になります。

このアルゴリズムは、重みについては常に同じ結果に収束しますが、経路は変わることがあります。(2つの都市から、同じ長さの異なる道を見つけることができるかもしれません)

前のテーブル

イテレーションs1234p
Iter 0------
Iter 1-s-s--
Iter 2-s113-
Iter 3-s1134
Iter 4-s1134
Iter 5-s1134

経路を計算するには、前のテーブルの最後の行を取ります。

pから、4があります。4から、3があります。3から、1があります。1から、sがあります。

Diagram

sからpまでの最短経路の重みは、4 - 6 + 2 + 17 = 17です。