【最短経路アルゴリズム】SPFA

導入

コンピューター サイエンスの世界では、アルゴリズムは星空の星のようなもので、それぞれが知恵の光で輝いています。彼らは、沈黙の哲学者のグループのように、世界の問題に黙って答えます。

アルゴリズムのステップは美しい詩の行のようなもので、複雑な問題を流れるような文字で解き放つことができます。それらは山の中の清らかな泉のように、ある峰から別の峰へと流れ、問題の塵を洗い流し、本当の顔を明らかにします。

それらはコンピューター サイエンスへの扉を開く鍵のようなものです。私たちは問題を解決するためにそれらを使用し、奇跡を起こすためにそれらを使用します。それらは私たちの知恵の結晶であり、世界についての理解であり、未来へのビジョンです。

アルゴリズムに満ちたこの世界に足を踏み入れ、知恵の光と詩的なリズムを感じてください。最も美しい景色と最も貴重な宝物を探して、一緒に未知の領域を探索しましょう。

アルゴリズムはコンピューター サイエンスの基礎であるだけでなく、私たちの生活の詩でもあります。未来への希望とテクノロジーの魅力を感じさせてくれます。ですから、一緒にアルゴリズムを活用し、私たちの生活に彩りを加え、世界にさらなる可能性をもたらしましょう。

アルゴリズムはコンピューター サイエンスにおけるごちそうのようなもので、それぞれに独自の味と風味があります。アルゴリズムの中には、繊細なフランスのデザートのような、複雑で繊細で、一つ一つ辛抱強く味わう必要があるものもあれば、単純な田舎のパンのような、シンプルで実用的で、人々を心から温かい気持ちにさせるアルゴリズムもあります。

基本的な紹介

そして、この広大なアルゴリズムの海の中で、ひときわ輝くアルゴリズム、それが最短経路アルゴリズムです。

最短パス アルゴリズムは、重み付きグラフ内の 2 つのノード間の最短パスを見つけるためのグラフ理論アルゴリズムです。このアルゴリズムは、実際の生活において次のような幅広い用途に使用できます。

  1. 交通計画: 最短経路アルゴリズムを都市交通計画に使用すると、最短ルートを決定して交通渋滞を軽減し、交通効率を向上させることができます。
  2. 物流物流: 物流物流では、最短経路アルゴリズムを使用して最短経路を決定し、輸送コストと時間を最小限に抑えることができます。
  3. ネットワーク設計: コンピュータ ネットワークでは、最短パス アルゴリズムを使用して最適なルートを決定し、データ パケットができるだけ早く送信されるようにすることができます。
  4. 地理情報システム: 地理情報システムでは、地図上の 2 点間の最適なルートを見つけるなど、最短経路アルゴリズムを使用して 2 点間の最短経路を決定できます。

その中でも、SPFA はグラフ理論の扉を開く鍵です。その中に入ってみましょう。

一連の考え

SPFA アルゴリズムの正式名は「Shortest Path Faster Algorithm」です。これは、 1994 年に西南交通大学のDuan Fandingが発表した論文での名前です。しかし、Duan Fanding の証明は間違っており、Bellman-Ford アルゴリズムが提案された直後 (1957 年)、キュー最適化の内容が存在したため、SPFA アルゴリズムは国際的には Duan Fanding のものとして認識されていません。

最悪のケースを回避するには、より効率的なダイクストラ アルゴリズムを正の重みグラフで使用する必要があります

特定のグラフに負の重みエッジがある場合、ダイクストラ アルゴリズムなどのアルゴリズムは役に立たず、SPFA アルゴリズムが役立ちます。簡潔にするために、重み付き有向グラフ G には負の重みループが存在しない、つまり最短経路が存在する必要があることに同意します。配列 d は各ノードの最短経路の推定値を記録するために使用され、隣接リストはグラフ G を保存するために使用されます。採用する方法は動的近似法です。最適化するノードを保存する先入れ先出しキューを設定し、最適化中に毎回ヘッド ノード u を取り出し、点の現在の最短パス推定値を使用します。 u はポイント u を離れます ノード v が指すノードは緩和操作を実行します ポイント v の最短経路の推定値が調整され、ポイント v が現在のキューにない場合、ポイント v はキューの最後に配置されますこのようにして、キューが空になるまでキューからノードが取り出され続けて緩和操作が実行されます。

定理: 最短パスが存在する限り、上記の SPFA アルゴリズムは最小値を見つけることができなければなりません。証明: ポイントがキューの最後尾に置かれるたびに、緩和操作を通じてポイントが達成されます。つまり、各最適化では、ある点 v の最短経路推定値 d[v] が小さくなります。したがって、アルゴリズムを実行すると、d はどんどん小さくなります。グラフ内に負の重み付けされたサイクルが存在しないと仮定しているため、各ノードは最短パス値を持ちます。したがって、アルゴリズムは無限に実行されるわけではなく、dの値が徐々に減少していき、最短経路の値に達した時点でアルゴリズムは終了し、そのときの最短経路の推定値が対応するノードの最短経路の値となります。

実際、ポイントが最大 n 回キューに入る場合、グラフに負のサイクルが存在し、最短パスが存在しないことを示します。

Duan Fanding の論文の複雑性の証明 (O(kE)、k は小さな定数)は間違っているため、ここでは省略します。このアルゴリズムの最悪の時間計算量はO(VE) です。

SPFA の非常に直観的な理解は、重み付けされていないグラフのBFSから変換されます。重み付けされていないグラフでは、BFS が最初に到達した頂点が経験するパスは最短パス (つまり、通過した頂点の最小数) でなければなりません。そのため、現時点では、配列を使用してノード アクセスを記録すると、各頂点がキューは 1 回だけですが、バンド内にあります。 右のグラフでは、最初に到達した頂点によって計算されたパスが必ずしも最短パスであるとは限りません。解決策の 1 つは、配列を放棄することです。この時点で必要な時間は当然指数関数的であるため、配列を放棄することはできませんが、既にキュー内にある頂点を処理する場合に最適なパスを直接更新し、現在のパスが元のパスよりも優れている場合は、最適なパスを直接更新します。解決_

SPFA アルゴリズムには、ヒープ最適化、スタック最適化、SLF、LLL の 4 つの最適化戦略があります。

  • ヒープの最適化: キューをヒープに置き換えます。ダイクストラとの違いは、ポイントが複数回ヒープに入ることができることです。負の重み付けされたエッジを持つグラフは、指数関数的な複雑さに陥る可能性があります。

  • スタックの最適化: キューをスタックに置き換えます (つまり、元の BFS プロセスを DFSに変更します)。ネガティブ リングを探す場合はより効率的になる可能性がありますが、最悪の時間計算量は依然として指数関数的です。

  • SLF: Small Label First 戦略。追加するノードが j で、チームの先頭要素が i であると仮定し、dist(j)<dist(i) の場合は j をチームの先頭に挿入し、それ以外の場合はチームの先頭に j を挿入します。それをチームの最後尾に入れる。

  • LLL: Large Label Last 戦略、キューの最初の要素を i に設定、キュー内のすべての dist 値の平均値が x、dist(i)>x の場合、i をキューの最後に挿入、検索特定の i が見つかるまで次の要素を調べます。 dist(i)<=x の場合、i は緩和操作のためにデキューされます。

SLF および LLL の最適化はランダム データでは良好に実行されますが、最悪のケースは正の重みグラフでは O(VE)、負の重みグラフでは指数関数的な複雑さになります。

結局のところ、SPFA は Kuansou です。見てください。

#include <bits/stdc++.h>
using namespace std;
struct edge{int x, y, c, pre;} a[410000];int alen, last[11100];
void ins(int x, int y, int c)//ins函数的功能是建立一条从x出发到y且长度为c的边
{
    a[++alen] = edge{x, y, c, last[x]}; 全局增加一条有向边,并赋值
    last[x] = alen;                     //建立边与边的联系(都是从x出发)
}

int n, m, d[11100]; //d[i]表示目前i和出发点的最短距离
bool v[11100];      //v[i]等于true表示点i在更新队列中,等于false表示点i不在更新队列中
void spfa()
{
    memset(d, 63, sizeof(d));d[1] = 0; //初始化d数组,出发点为1,出发点到自己的距离为0
    memset(v, 0, sizeof(v)); v[1] = 1; //初始化v数组,v[1]等于1,表示出发点1进入队列
    deque<int> Q; Q.push_back(1);   //定义更新队列,出发点进入更新队列
    while (!Q.empty()) //只要队列不为空,就表示还有点等着更新别的点
    {
        int x = Q.front();                     //从队列中取出准备好更新别的点的点x
        for (int k = last[x]; k; k = a[k].pre) //重点理解!k首相等于和x相连的最后一条边的编号。那么倒数第二条和x相连的边的编号是多少呢?在a[k].next可以找到
        {
            int y = a[k].y;
            if (d[y] > d[x] + a[k].c) //尝试用x的最短距离更新y的最短距离
            {
                d[y] = d[x] + a[k].c; //如果点被更新了,那么它马上有冲动(想进更新队列)要去更新它的"亲朋好友"(与之直接相连的点)
                if (v[y] == 0)        //如果点y不在队列,则进入队列;如果已经在队列了,则不用再进入。
                {
                    Q.push_back(y);
                    v[y] = 1;
                }
            }
        }
        Q.pop_front(); //此时x已经更新完它的"亲朋好友",完成使命,退出队列Q
        v[x] = 0;      //标记x已经不再队列,以后x有可能会再次进入队列
    }
    //队列没有点等着更新别的点了,那么意味着所有点的d值都是最优的
    if (d[n] == d[0]) printf("-1\n"); //d[0]为初始的最大值,d[n]等于d[0]表示点n没有被更新,即点n无法到达。
    else              printf("%d", d[n]);
}
int main()
{
    scanf("%d%d", &n, &m);
    alen = 0;
    memset(last, 0, sizeof(last)); //注意构图之前一定要初始化,不然后果很严重!
    for (int i = 1; i <= m; i++)
    {
        int x, y, c;
        scanf("%d%d%d", &x, &y, &c); //题目给出的是无向边,而我们的边目录是有向边
        ins(x, y, c);
        ins(y, x, c); //建立正向边、反向边
    }
    spfa();
    return 0;
}

詳しいコメントはコード内に書いてありますので見てください。

例 

放っておかないで、自分で羅谷に行って確認してください。

 殴られるに値する行為には、それを慰めるための写真が必要だと言われています。

おすすめ

転載: blog.csdn.net/aliyonghang/article/details/131152177