1. 程式人生 > >圖的五種最短路徑演算法

圖的五種最短路徑演算法

本文總結了圖的幾種最短路徑演算法的實現:深度或廣度優先搜尋演算法,費羅伊德演算法,迪傑斯特拉演算法,Bellman-Ford 演算法。

1)深度或廣度優先搜尋演算法(解決單源最短路徑)

從起點開始訪問所有深度遍歷路徑或廣度優先路徑,則到達終點節點的路徑有多條,取其中路徑權值最短的一條則為最短路徑。

下面是核心程式碼:

void dfs(int cur,int dst){
    if(minpath<dst) return;//當前走過的路徑大雨之前的最短路徑,沒有必要再走下去了
    if(cur==en){//臨界條件,當走到終點n
       if(minpath>dst){
        minpath=dst;
        return;
       }
    }
     for(int i=1;i<=n;i++){
        if(mark[i]==0&&edge[cur][i]!=inf&&edge[cur][i]!=0){
            mark[i]=1;
            dfs(i,dst+edge[cur][i]);
            mark[i]=0;//需要在深度遍歷返回時將訪問標誌置0
        }
     }
     return;
}

例:先輸入n個節點,m條邊,之後輸入有向圖的m條邊,邊的前兩個元素表示起點和終點,第三個值表示權值,輸出1號城市到n號城市的最短距離。

#include<bits/stdc++.h>
using namespace std;
#define nmax 110
#define inf 999999999
int minpath,n,m,en,edge[nmax][nmax],mark[nmax];//最短路徑,節點數,邊數,終點,鄰接矩陣,節點訪問標記
void dfs(int cur,int dst){
    if(minpath<dst) return;//當前走過的路徑大雨之前的最短路徑,沒有必要再走下去了
    if(cur==en){//臨界條件,當走到終點n
       if(minpath>dst){
        minpath=dst;
        return;
       }
    }
     for(int i=1;i<=n;i++){
        if(mark[i]==0&&edge[cur][i]!=inf&&edge[cur][i]!=0){
            mark[i]=1;
            dfs(i,dst+edge[cur][i]);
            mark[i]=0;//需要在深度遍歷返回時將訪問標誌置0
        }
     }
     return;
}
int main ()
{
      while(cin>>n>>m&&n!=0){
        //初始化鄰接矩陣
        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++){
                edge[i][j]=inf;
            }
            edge[i][i]=0;
        }
      int a,b;
      while(m--){
        cin>>a>>b;
        cin>>edge[a][b];
      }
      minpath=inf;
      memset(mark,0,sizeof(mark));
      mark[1]=1;
      en=n;
      dfs(1,0);
      cout<<minpath<<endl;
      }
}

程式執行結果如下:


2)弗洛伊德演算法(解決多源最短路徑):時間複雜度o(n^3),空間複雜度o(n^2)

基本思想:最開始只允許經過1號頂點進行中轉,接下來只允許經過1號和2號頂點進行中轉.......允許經過1~n號所有頂點進行中轉,來不斷動態更新任意兩點之間的最短距離。即求從i號頂點到j頂點只經過前k號點的最短距離。

分析如下:1,首先構建鄰接矩陣edge[n+1][n+1],假如現在只允許經過1號節點,求任意兩點間的最短距離,很顯然edge[i][j]=min(edge[i][j],edge[i][1]+edge[1][j]),程式碼如下:

for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            if(edge[i][j]>edge[i][1]+edge[1][j]){
               edge[i][j]=edge[i][1]+edge[1][j];
            }
        }
     }

2.接下來繼續求在只允許經過1和2號兩個頂點的情況下任意兩點之間的最短距離,在已經實現了從i號頂點到j號頂點只經過前1號點的最短路程的前提下,現在插入第2號節點,來看看能不能更新最短路徑,因此只需在步驟一求得的基礎上,進行edge[i][j]=min(edge[i][j],edge[i][2]+edge[2][j]);.......

3.很顯然,需要n次這樣的更新,表示依次插入了1號2號.......n號節點,最後求得的edge[i][j]是從i號頂點到j號頂點只經過前n號點的最短路程。因此核心程式碼如下:

#include<bits/stdc++.h>
using namespace std;
#define inf 999999999
int main ()
{
    for(int k=1;k<=n;k++){
     for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            if(edge[k][j]<inf&&edge[i][k]<inf&&edge[i][j]>edge[i][k]+edge[k][j]){
               edge[i][j]=edge[i][k]+edge[k][j];
            }
        }
     }
     }
}
例1:尋找最短的從商場到賽場的路線。其中商店在1號節點處,賽場在n號節點處,1~n節點中有m條線路雙向連線。

/***先輸入n,m,在輸入m個三元組,n為路口數,m表示有幾條路,其中1為商店,n為賽場,三元組分別表示起點終點,和該路徑長,輸出1到n的最短距離***/

#include<bits/stdc++.h>
using namespace std;
#define inf 999999999
#define nmax 110
int n,m,edge[nmax][nmax];
int main ()
{
    int a,b;
    while(cin>>n>>m&&n!=0){
        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++){
                edge[i][j]=inf;
            }
            edge[i][i]=0;
        }
        while(m--){
           cin>>a>>b;
           cin>>edge[a][b];
           edge[b][a]=edge[a][b];
        }
        for(int k=1;k<=n;k++){
     for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            if(edge[k][j]<inf&&edge[i][k]<inf&&edge[i][j]>edge[i][k]+edge[k][j]){
               edge[i][j]=edge[i][k]+edge[k][j];
            }
        }
     }
     }
     cout<<edge[1][n]<<endl;
    }
}

程式執行結果如下:


3)迪傑斯特拉演算法(解決單源最短路徑)

基本思想:每次找到離源點(如1號節點)最近的一個頂點,然後以該頂點為中心進行擴充套件,最終得到源點到其餘所有點的最短路徑。

基本步驟:1,設定標記陣列book[]:將所有的頂點分為兩部分,已知最短路徑的頂點集合P和未知最短路徑的頂點集合Q,很顯然最開始集合P只有源點一個頂點。book[i]為1表示在集合P中;

2,設定最短路徑陣列dst[]並不斷更新:初始狀態下,dst[i]=edge[s][i](s為源點,edge為鄰接矩陣),很顯然此時dst[s]=0,book[s]=1.此時,在集合Q中可選擇一個離源點s最近的頂點u加入到P中。並依據以u為新的中心點,對每一條邊進行鬆弛操作(鬆弛是指由頂點s-->j的途中可以經過點u,並令dst[j]=min(dst[j],dst[u]+edge[u][j])),並令book[u]=1;

3,在集合Q中再次選擇一個離源點s最近的頂點v加入到P中。並依據v為新的中心點,對每一條邊進行鬆弛操作(即dst[j]=min(dst[j],dst[v]+edge[v][j])),並令book[v]=1;

4,重複3,直至集合Q為空。

核心程式碼如下:

#include<bits/stdc++.h>
using namespace std;
#define nmax 110
#define inf 999999999
/***構建所有點最短路徑陣列dst[],且1為源點***/
int u;/***離源點最近的點***/
int minx;
for(int i=1;i<=n;i++) dst[i]=edge[1][i];
for(int i=1;i<=n;i++) book[i]=0;
book[1]=1;
for(int i=1;i<=n-1;i++){
        minx=inf;
    for(int j=1;j<=n;j++){
        if(book[j]==0&&dst[j]<minx){
            minx=dst[j];
            u=j;
        }
    }
    book[u]=1;
    /***更新最短路徑陣列***/
    for(int k=1;k<=n;k++){
        if(book[k]==0&&dst[k]>dst[u]+edge[u][k]&&edge[u][k]<inf){
            dst[k]=dst[u]+edge[u][k];
        }
    }
}

例1:給你n個點,m條無向邊,每條邊都有長度d和花費p,給你起點s,終點t,要求輸出起點到終點的最短距離及其花費,如果最短距離是有多條路線,則輸出花費最少的。

輸入:輸入n,m,點的編號是1~n,然後是m行,每行4個數a,b,d,p,表示a和b之間有一條邊,且長度為d,花費為p。最後一行是兩個數s,t,起點s,終點t。n和m為0時輸入結束。(1<n<=1000,0<m<100000,s!=t)

輸出:輸出一行,有兩個數,最短距離及其花費。

分析:由於每條邊有長度d和花費p,最好構建變結構體存放。

使用鄰接矩陣求解:

#include<bits/stdc++.h>
using namespace std;
#define nmax 110
#define inf 999999999
struct Edge{
    int len;
    int cost;
}edge[nmax][nmax];
int u,n,m,book[nmax],s,t,dst[nmax],spend[nmax];
int minx;
int main (){
    while(cin>>n>>m&&n!=0&&m!=0){
        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++){
                edge[i][j].len=inf;
                edge[i][j].cost=0;
            }
            edge[i][i].len=0;
        }
        int a,b;
        while(m--){
            cin>>a>>b;
            cin>>edge[a][b].len>>edge[a][b].cost;
            edge[b][a].len=edge[a][b].len;
            edge[b][a].cost=edge[a][b].cost;
        }
        cin>>s>>t;
       for(int i=1;i<=n;i++) {dst[i]=edge[s][i].len;spend[i]=edge[s][i].cost;}
       for(int i=1;i<=n;i++) book[i]=0;
       book[s]=1;
for(int i=1;i<=n-1;i++){
        minx=inf;
   for(int j=1;j<=n;j++){
        if(book[j]==0&&dst[j]<minx){
            minx=dst[j];
            u=j;
        }
    }
    book[u]=1;
    for(int k=1;k<=n;k++){
        if(book[k]==0&&(dst[k]>dst[u]+edge[u][k].len||(dst[k]==dst[u]+edge[u][k].len&&spend[k]>spend[u]+edge[u][k].cost))&&edge[u][k].len<inf){
            dst[k]=dst[u]+edge[u][k].len;
            spend[k]=spend[u]+edge[u][k].cost;
        }
    }
}
       cout<<dst[t]<<' '<<spend[t]<<endl;
    }
 }

程式執行結果如下:


4)Bellman-Ford演算法(解決負權邊,解決單源最短路徑,前幾種方法不能解決負權邊)

主要思想:所有的邊進行n-1輪鬆弛,因為在一個含有n個頂點的圖中,任意兩點之間的最短路徑最多包含n-1條邊。換句話說,第1輪在對所有的邊進行鬆弛操作後,得到從1號頂點只能經過一條邊到達其餘各定點的最短路徑長度,第2輪在對所有的邊進行鬆弛操作後,得到從1號頂點只能經過兩條邊到達其餘各定點的最短路徑長度,........

此外,Bellman-Ford演算法可以檢測一個圖是否含有負權迴路:如果經過n-1輪鬆弛後任然存在dst[e[i]]>dst[s[i]]+w[i].

例1:對圖中含有負權的有向圖,輸出從1號節點到各節點的最短距離,並判斷有無負權迴路。

#include<bits/stdc++.h>
using namespace std;
#define nmax 1001
#define inf 999999999
int n,m,s[nmax],e[nmax],w[nmax],dst[nmax];
int main ()
{
       while(cin>>n>>m&&n!=0&&m!=0){
        for(int i=1;i<=m;i++){
            cin>>s[i]>>e[i]>>w[i];
        }
        for(int i=1;i<=n;i++)
            dst[i]=inf;
        dst[1]=0;
        for(int i=1;i<=n-1;i++){
            for(int j=1;j<=m;j++){
                if(dst[e[j]]>dst[s[j]]+w[j]){
                   dst[e[j]]=dst[s[j]]+w[j];
                }
            }
        }
        int flag=0;
        for(int i=1;i<=m;i++){
           if(dst[e[i]]>dst[s[i]]+w[i]){
                flag=1;
        }
       }
       if(flag) cout<<"此圖有負權迴路"<<endl;
       else{
        for(int i=1;i<=n;i++){
            if(i==1) cout<<dst[i];
            else cout<<' '<<dst[i];
        }
        cout<<endl;
       }
}
}

程式執行結果如下:


5)SPFA(Shortest Path Faster Algorithm)演算法是求單源最短路徑的一種演算法,它是Bellman-ford佇列優化,它是一種十分高效的最短路演算法。

實現方法:建立一個佇列,初始時佇列裡只有起始點s,在建立一個數組記錄起始點s到所有點的最短路徑(初始值都要賦為極大值,該點到他本身的路徑賦為0)。然後執行鬆弛操作,用佇列裡的點去重新整理起始點s到所有點的距離的距離,如果重新整理成功且重新整理的點不在佇列中,則把該點加入到佇列,重複執行直到佇列為空。

此外,SPFA演算法可以判斷圖中是否有負權歡=環,即一個點的入隊次數超過N。

#include<bits/stdc++.h>
using namespace std;
int n,m,len;
struct egde{
     int to,val,next;
}e[200100];
int head[200100],vis[200100],dis[200100];
void add(int from,int to,int val){
     e[len].to=to;
     e[len].val=val;
     e[len].next=head[from];
     head[from]=len;
     len++;
}
void spfa()
{
    queue<int>q;
    q.push(1);
    vis[1]=1;
    while(!q.empty())
    {
        int t=q.front();
        q.pop();
        vis[t]=0;
        for(int i=head[t];i!=-1;i=e[i].next){
            int s=e[i].to;
            if(dis[s]>dis[t]+e[i].val){
                dis[s]=dis[t]+e[i].val;
                if(vis[s]==0){
                    q.push(s);
                    vis[s]=1;
                }
            }
        }
    }

}
int main(){
    int from,to,val;
    while(cin>>n>>m){
        memset(head,-1,sizeof(head));
        memset(vis,0,sizeof(vis));
       /* for(int i=1;i<=n;i++){
            dis[i]=99999999;
        }*/
        memset(dis,0x3f,sizeof(dis));
        dis[1]=0;len=1;
        for(int i=0;i<m;i++){
            cin>>from>>to>>val;
            add(from,to,val);
        }
        spfa();
        for(int i=1;i<=n;i++){
            cout<<dis[i]<<" ";
        }
        cout<<endl;
    }
}