1. 程式人生 > >最短路徑問題 Floyd SPFA Dijkstra 效率比較

最短路徑問題 Floyd SPFA Dijkstra 效率比較

雖然時間複雜度都清楚,不過實際執行起來如何心裡還是沒底,實踐才是檢驗真理的標準啊。

稀疏圖中對單源問題來說SPFA 的效率略高於 Heap+Dijkstra ;對於無向圖上的 APSP (All Pairs ShortestPaths)問題,SPFA演算法加上優化後效率更是遠高於 Heap+Dijkstra。今次再來看一看稠密圖上的實驗結果。

SPFA優化方法:對於APSP問題如果用單源最短路徑演算法的話,曾考慮過用先求出的最短路徑優化,使得在後面求新源點的最短路徑時對已求過的點之間可以一次優化到最終結果避免多次進入佇列更新,這個優化對SPFA演算法尤其明顯。

(注:以下提到的Dijkstra,如無特別說明,均指加入二叉堆(Binary-Heap)優化的Dijkstra演算法,程式中由STL的優先佇列(piority_queue)實現) 程式隨機生成一個 N頂點的無向完全圖,之後分別用三種演算法求所有頂點對之間的最短路徑並統計各演算法耗費的時間。程式程式碼附後 (1)不加優化
試驗一:
Number of vertexes: 100 Floydconsumed:    0.070 secs
SPFAconsumed:     0.240 secs
Dijkstra consumed:  0.040secs 試驗二:
Number of vertexes: 300 Floydconsumed:    0.911 secs
SPFAconsumed:     2.183 secs
Dijkstra consumed:  0.891secs 試驗三:
Number of vertexes: 600 Floydconsumed:    6.719 secs
SPFAconsumed:     19.709 secs
Dijkstra consumed:  6.589secs 可以看到完全圖果然是Floyd演算法的老本行,寫起來複雜得多的Heap+Dijkstra比起區區三行的Floyd並不具有太多優勢;雖然沒有寫程序序,不過可以預見不加Heap的Dijkstra是肯定要敗給Floyd的。
比起前兩個,SPFA就不那麼好過了,基本耗費了2-3倍的時間才完成計算,可見在稠密圖的單源最短路徑問題上SPFA比起Dijkstra確實有很大劣勢。 (2)SPFA針對無向圖的APSP問題加優化,優化方法見前面文章所述
試驗四:
Number of vertexes: 100 Floydconsumed:    0.070 secs
SPFAconsumed:     0.070 secs
Dijkstra consumed:  0.080secs 試驗五:
Number of vertexes: 300 Floydconsumed:    0.981 secs
SPFAconsumed:     0.641 secs
Dijkstra consumed:  0.902secs 試驗六:
Number of vertexes: 600 Floydconsumed:    6.820 secs
SPFAconsumed:     5.077 secs
Dijkstra consumed:  6.590secs SPFA加上優化後效率有了大幅提高,不但超過了Floyd,比起Dijkstra還略高20%左右。我不打算繼續針對完全圖的情況做任何優化,因為這裡要比較的應該是對一般圖都適用的普通演算法。 總結一下:
(1)對於稀疏圖,當然是SPFA的天下,不論是單源問題還是APSP問題,SPFA的效率都是最高的,寫起來也比Dijkstra簡單。對於無向圖的APSP問題還可以加入優化使效率提高2倍以上。
(2)對於稠密圖,就得分情況討論了。單源問題明顯還是Dijkstra的勢力範圍,效率比SPFA要高2-3倍。APSP問題,如果對時間要求不是那麼嚴苛的話簡簡單單的Floyd即可滿足要求,又快又不容易寫錯;否則就得使用Dijkstra或其他更高階的演算法了。如果是無向圖,則可以把Dijkstra扔掉了,加上優化的SPFA絕對是必然的選擇。
稠密圖 稀疏圖 有負權邊
單源問題 Dijkstra+heap SPFA (或Dijkstra+heap,根據稀疏程度) SPFA
APSP(無向圖) SPFA(優化)/Floyd SPFA(優化) SPFA(優化)
APSP(有向圖) Floyd SPFA (或Dijkstra+heap,根據稀疏程度) SPFA
OK,今天總算徹底弄清這仨的恩怨情仇,至少一段時間內不用再為選擇哪個而頭大了…… 最後附上實驗程式的程式碼:
#include<iostream>
#include<fstream>
#include<vector>
#include<cstdlib>
#include<ctime>
#include<queue>
using namespace std;
 
const int infinity=1000000000;
int N;
vector< vector<int> > vAdjMatrix;
vector< vector<int> > dijkstra;
vector< vector<int> > spfa;
vector< vector<int> > floyd;
 
inline bool relax(int u, int v, vector<int> &d){
    int nlen=d[u]+vAdjMatrix[u][v];
    if(nlen<d[v]){
        d[v]=nlen;
        return true;
    }
    return false;
}
 
void DoFloyd(){
    for(int i=0;i<N;++i)for(int j=0;j<N;++j)
        floyd[i][j]=vAdjMatrix[i][j];
 
    int newlen;
    for(int k=0;k<N;++k)
        for(int u=0;u<N;++u)
            for(int v=0;v<N;++v)
                if((newlen=floyd[u][k]+floyd[k][v])<floyd[u][v])
                    floyd[u][v]=floyd[v][u]=newlen;
}
 
void DoSPFA(){
    for(int iSource=0;iSource<N;++iSource){
        queue<int> Q;
        vector<bool> IsInQ(N,false);
 
        //optimizing begin:
        if(iSource>0){
            int iShort=0;
            for(int k=1;k<iSource;++k)if(spfa[k][iSource]<spfa[iShort][iSource])
                iShort=k;
            for(int iDes=0;iDes<N;++iDes)
                spfa[iSource][iDes]=spfa[iShort][iSource]+spfa[iShort][iDes];
        }
        //optimizing end.
 
        Q.push(iSource);
        IsInQ[iSource]=true;
        spfa[iSource][iSource]=0;
        while(!Q.empty()){
            int u=Q.front();
            Q.pop();
            IsInQ[u]=false;
            for(int v=0;v<N;++v)
                if(relax(u,v,spfa[iSource]) && !IsInQ[v]){
                    Q.push(v);
                    IsInQ[v]=true;
                }
        }
    }
}
 
void DoDijkstra(){
    struct ver_weight{
        int vertex;
        int weight;
        ver_weight(int Vertex, int Weight):vertex(Vertex),weight(Weight){}
        bool operator>(const ver_weight &comp)const{
            return weight>comp.weight;
        }
    };
 
    for(int iSource=0;iSource<N;++iSource){
        vector<bool> IsInset(N,false);
        int setcount=0;
        priority_queue< ver_weight,vector<ver_weight>,greater<ver_weight> > pq;
 
        IsInset[iSource]=true;
        ++setcount;
        for(int v=0;v<N;++v)if(vAdjMatrix[iSource][v]<infinity){
            dijkstra[iSource][v]=vAdjMatrix[iSource][v];
            pq.push(ver_weight(v,dijkstra[iSource][v]));
        }

        while(setcount<N){
            ver_weight uw=pq.top();
            pq.pop();
            if(IsInset[uw.vertex])continue;
            IsInset[uw.vertex]=true;
            ++setcount;
            for(int v=0;v<N;++v)if(relax(uw.vertex,v,dijkstra[iSource]))
                pq.push(ver_weight(v,dijkstra[iSource][v]));
        }
    }
}
 
int main(){
    ofstream fout("out.txt");
    cout<<"Input the number of vertexes: ";
    cin>>N;
    vAdjMatrix.resize(N,vector<int>(N,infinity));
    floyd.resize(N,vector<int>(N,infinity));
    spfa.resize(N,vector<int>(N,infinity));
    dijkstra.resize(N,vector<int>(N,infinity));

    fout<<"Number of vertexes: "<<N<<'\n'<<endl;
 
    //create the graph
    srand(unsigned(time(NULL)));
    for(int i=0;i<N;++i)for(int j=0;j<i;++j)
        vAdjMatrix[i][j]=vAdjMatrix[j][i]=rand();
    fout.precision(3);
    fout.setf(ios::fixed);
    clock_t start,finish;
 
    //floyd
    cout<<"Floyd start ... \n";
    start=clock();
    DoFloyd();
    finish=clock();
    cout<<"Floyd end.\n\n";
    fout<<"Floyd consumed:     "<<(double)(finish-start)/CLOCKS_PER_SEC<<" secs\n";
 
    //SPFA
    cout<<"SPFA start ... \n";
    start=clock();
    DoSPFA();
    finish=clock();
    cout<<"SPFA end.\n\n";
    fout<<"SPFA consumed:      "<<(double)(finish-start)/CLOCKS_PER_SEC<<" secs\n";
 
    for(int i=0;i<N;++i)for(int j=0;j<i;++j)
        if(floyd[i][j]!=spfa[i][j]){
            fout<<"SPFA worked wrong!\n";
            goto SPFA_end;   // try...catch are much better than goto...
        }
 
SPFA_end:
 
    //Dijkstra
    cout<<"Dijkstra start ... \n";
    start=clock();
    DoDijkstra();
    finish=clock();
    cout<<"Dijkstra end.\n";
    fout<<"Dijkstra consumed:  "<<(double)(finish-start)/CLOCKS_PER_SEC<<" secs\n";
 
    for(int i=0;i<N;++i)for(int j=0;j<i;++j)
        if(floyd[i][j]!=dijkstra[i][j]){
            fout<<"Dijkstra worked wrong!\n";
            goto Dijk_end;
        }
 
Dijk_end:
 
    //system("pause");
    return 0;
}

----------------PS.-------------------------------------- 再補充,關於 SPFA 的兩個優化 SLF 和LLL:  懶得再寫一篇了... 仍然拿隨機生成的圖做了實驗,對單源問題,SLF 可使速度提高 15 ~ 20 %; SLF + LLL 可提高約 50 %(都是與純SPFA相比)。 不過 LLL 寫起來加的東西比較多一點,SLF 簡單能加就隨便加吧反正多一行而已 即便如此在稠密圖上與 Heap + Dijkstra 相比仍有差距 對無向圖 APSP 如果加上前述優化的話再加 SLF 或 LLL 反正略有提高,但不明顯,最多好像也就 7~8 %的樣子,忘了。加不加無所謂。

轉載自:
蓬萊山輝夜