最短路模板Dijkstra Bellman-Ford Floyd SPFA

(做了好久纔想起整理模板難過


基本最短路算法集錦


算法總結:

①Dijkstra算法用的是貪心策略,每次都找當前最短路徑的下一個最短距離點。所以不適合帶有負權的情況。至於時間效率通過各種優化可以到達不同的程度。但是樸素的Dijkstra算法永遠是最穩定的。

②Bellman-Ford算法是Dijkstra的一種變式,它摒棄了貪心的策略,但是每次都需要鬆弛所有的路徑,所以也適合負權的情況。但是時間效率較低。有資料顯示,Bellman-Ford算法也可以應用貪心策略,這屬於高級技巧,這裏不予考慮。

Floyd算法用的是動態規劃的思想,由於是不定源點,所以它需要對所有的情況進行鬆弛操作,最終獲得所有的最短路徑長度。由於需要遍歷,所以它的時間效率比較低。但是遍歷策略好的話,還是能很快得到想要的結果。

SPFA算法是用FIFO隊列優化的Bellman-Ford算法,所以支持負權的情況,由於已經將需要考慮的節點放入隊列中,所以避免了很多不必要的鬆弛,時間效率也相對較高。由於隊列中無法進行修改,所以時間效率相對不穩定。

⑤所有的隊列優化其實都能轉化爲hash優化,這裏不再考慮更進一步的優化。

 

 

一、Dijkstra算法:單源到所有節點的最短路算法。

算法要求:可以是無向圖或有向圖,所有邊權均爲正,最短路存在。如果圖是五環圖,則最長路也一定存在,但是如果是有環圖,則最長路不一定存在。

算法思想:每次比較所有的從源點可以到達的路的長度,然後選出最短的一條放入源點集合,然後更新最短路徑長度。這樣,當所有的點都在源點集合中時,得到的長度就是最短路長度。

1)使用鄰接矩陣的稠密圖普通Dijkstra算法

算法時間複雜度:O(n^2)

算法代碼:

#include <iostream>

#include <cstdio>

#include <cstring>

using namespace std;

#define INF (1<<31)-1   //int最大值

#define MAXN 100   //最大節點數

int dist[MAXN],c[MAXN][MAXN];  

//dist[i]存放從源點到i節點的最短距離,c數組用來表示圖

int n,line;   //n爲節點數,line爲邊數

 

//迪傑斯特拉算法主算法模塊

void Dijkstra(int v)   //v代表源點

{

       inti,j,temp,u;  //temp用於暫時存放最短路徑長度

       boolvis[MAXN];   //判定重複標記數組

       for(i=0;i<n;i++)   //dist數組與vis數組的初始化

{

       dist[i]=c[v][i];

       vis[i]=0;

}

dist[v]=0;  //源點到源點的距離設爲0

vis[v]=1;   //源點默認爲已經訪問

for(i=0;i<n;i++)  //總共應該遍歷n次

{

       temp=INF;  //初始化temp爲最大值

       u=v;   //初始化最短節點爲當前節點

for(j=0;j<n;j++)if((!vis[j])&&dist[j]<temp)

{

u=j;   //找出v距離最短的節點u

temp=dist[j];   //更新最短距離

}

if(temp==INF)return;   //不存在其它節點了,返回

vis[u]=1;   //標記該最短節點爲已經訪問

for(j=0;j<n;j++)if((!vis[j])&&dist[u]+c[u][v]<dist[j])  //鬆弛操作

       dist[j]=dist[u]+c[u][v];  //更新最短距離

}

}

int main()

{

int p,q,len,i,j,origin;

while(scanf(“%d%d”,&n,&line))

{

       if(!n&&!line) break;  //如果圖爲空白圖,則結束

       for(i=0;i<n;i++)

       for(j=0;j<n;j++)

              c[i][j]=INF;   //初始化節點間距離,默認爲節點間全部不連通

while(line--)   //輸入這line條邊的內容

{

       scanf(“%d%d%d”,&p,&q,&len);  

//p爲邊的起點,q爲邊的終點,len爲邊的長度

       //下面是無向圖的輸入方式

       if(len<c[p][q])

{

       c[p][q]=len;

       c[q][p]=len;

}

/*下面是有向圖的輸入方式

if(len<c[p][q])

       c[p][q]=lem;

以上是有向圖的輸入方式*/

}

for(i=0;i<n;i++)

       dist[i]=INF;   //初始化最短距離數組爲最大長度

scanf(“%d”,&origin);

Dijkstra(origin);  //最短路算法調用

printf(“%d\n”,dist[n]);

}

}

 

2)使用鄰接表的稀疏圖的普通Dijkstra算法

算法時間複雜度:O(m*log(n))

算法代碼:(代碼以無向圖爲例,有向圖部分以註釋形式寫在代碼中)

###頭文件以及宏定義部分省略###

int first[MAXN];  //first[u]保存節點u的第一條邊的編號

int u[MAXN],v[MAXN],w[MAXN];

//u[i]表示第i條邊的一端端點,v[i]表示第i條邊的另一端端點,w[i]表示第i條邊的長度

int next[2*MAXN];  //next[i]表示第i條邊的下一條邊的編號【無向圖】

//int next[MAXN]; next[i]表示第i條邊的下一條邊的編號。【有向圖】

#########省略部分#########

scanf(“%d%d”,&n,&m);

for(i=0;i<n;i++) first[i]=-1;   //初始化鏈表表頭

for(i=0;i<2*line;i++)  //輸入2*line條邊,每次將邊首插法插入鏈表表頭(避免遍歷鏈表)。

//如果是有向圖,那麼只有line條邊,即i<line。

{

       scanf(“%d%d%d”,&u[i],&v[i],&w[i]);

       next[i]=first[u[i]];

       first[u[i]]=i;

       //以下只有無向圖纔有

       i++;

       u[i]=v[i-1];

       v[i]=u[i-1];

next[i]=first[u[i]];

       first[u[i]]=i;

       //以上只有無向圖纔有

}

//對應的Dijkstra算法

void Dijkstra(int x)   //v代表源點

{

       inti,j,temp,minu;  //temp用於暫時存放最短路徑長度

       boolvis[MAXN];   //判定重複標記數組

       for(i=first[x];i!=-1;i=next[i])  //dist數組與vis數組的初始化

{

       dist[v[i]]=w[i];

       vis[v[i]]=0;

}

dist[x]=0;  //源點到源點的距離設爲0

vis[x]=1;   //源點默認爲已經訪問

for(i=0;i<n;i++)  //總共應該遍歷n次

{

       temp=INF;  //初始化temp爲最大值

       minu=x;   //初始化最短節點爲當前節點

for(j=first[x];j!=-1;j=next[j])

{

if((!vis[v[j]])&&dist[v[j]]<temp)

{

minu=j;   //找出v距離最短的節點uu

temp=dist[v[j]];   //更新最短距離

}

}

if(temp==INF)return;   //不存在其它節點了,返回

vis[minu]=1;   //標記該最短節點爲已經訪問

for(j=first[x];j!=-1;j=next[j])if((!vis[v[j]])&&dist[minu]+w[j]<dist[v[j]])  //鬆弛操作

       dist[v[j]]=dist[minu]+w[j];  //更新最短距離

}

}

 

3)使用優先隊列的Dijkstra優化算法

算法時間複雜度:O(n*lgn+ m)。一般情況下最快。

算法代碼:

#include <queue>   //需要優先隊列

#include <utility>   //需要pair類型

######其他頭文件已經宏定義省略######

int first[MAXN];  //first[u]保存節點u的第一條邊的編號

int u[MAXN],v[MAXN],w[MAXN];

//u[i]表示第i條邊的一端端點,v[i]表示第i條邊的另一端端點,w[i]表示第i條邊的長度

int next[2*MAXN];  //next[i]表示第i條邊的下一條邊的編號【無向圖】

//int next[MAXN]; next[i]表示第i條邊的下一條邊的編號。【有向圖】

#########省略部分#########

scanf(“%d%d”,&n,&m);

for(i=0;i<n;i++) first[i]=-1;   //初始化鏈表表頭

for(i=0;i<2*line;i++)  //輸入2*line條邊,每次將邊首插法插入鏈表表頭(避免遍歷鏈表)。

//如果是有向圖,那麼只有line條邊,即i<line。

{

       scanf(“%d%d%d”,&u[i],&v[i],&w[i]);

       next[i]=first[u[i]];

       first[u[i]]=i;

       //以下只有無向圖纔有

       i++;

       u[i]=v[i-1];

       v[i]=u[i-1];

next[i]=first[u[i]];

       first[u[i]]=i;

       //以上只有無向圖纔有

}

typedef pair<int,int> pii;  //定義雙對象類型

priority_queue<pii,vector<pii>,greater<pii>> pq;   //聲明最小出隊的優先隊列

//對應的Dijkstra算法

int Dijkstra(int x)

{

       inti,j;

       pq.push(make_pair(d[x],x);

       while(!pq.empty())

       {

              piiminu=pq.top();pq.pop();

              intx=minu.second;

              if(minu.first!=dist[x])continue;

              for(inti=first[x];i!=-1;i=next[i]) if(d[v[i]]>dist[x]+w[i])

              {

                     dist[v[i]]=dist[x]+w[i];

                     pq.push(make_pair(d[v[i]],v[i]));

              }

       }

}

二、Bellman-Ford算法:單源到所有節點的最短最長路算法。

算法要求:可以使無向圖或者有向圖。邊權可以存在負值。當然最短路不一定存在,當最短路存在時,可以求出最短路長度。如果圖爲有環圖,則最短路不一定存在,最長路也不一定存在。如有負權則輸出錯誤提示。也適合求解約束拆分系統的不等式問題。

算法思想:如果最短路存在,一定存在一個不含環的最短路。在邊權可正可負的圖中,環有零環、正環、負環3種。如果包含零環或正環,去掉以後路徑不會變長;如果包含負環,則最短路不存在。可以通過n-1輪鬆弛操作得到。

1)樸素的BF算法。

算法時間複雜度:O(mn)

算法代碼:

const int N =205;

const int M =20005;

const int MAXN = 1000000000

int dist[N];

 

//自定義邊的結構體

struct edge{int u,v,w;}e[M];  //u,v分別是邊的兩端點,w爲邊長度

//初始化dist數組

void init(int vs,int s)

{

       inti;

       for(i=0;i<vs;i++)

              dist[i]=MAXN;

       dist[s]=0;

       return;

}

//鬆弛操作,成功返回true,失敗返回false

bool relax(int u,int v,int w)

{

       if(dist[v]>dist[u]+w)

       {

              dist[v]=dist[u]+w;

              returntrue;

}

return false;

}

//BF主算法模塊,返回false表示算法失敗,圖中存在負環。

bool bellmanFord(int es,int vs,int s)   //es表示邊數,vs表示點數,s表示起點

{

       inti,j;

       init(vs,s);

       boolflag;

       for(i=0;i<vs-1;i++)   //應進行vs-1次鬆弛操作

       {

              flag=false;

              for(j=0;j<es;j++)

       if(relax(e[j].u,e[j].v,e[j].w))

              flag=true;

return flag;

}

}

 

 

int main()

{

       intn,m,i;

       while(scanf(“%d%d”,&n,&m)!=EOF)

{

       for(i=0;i<m;i++)

       {

              scanf(“%d%d%d”,&e[i].u,&e[i].v,&e[i].w);

              e[m+i].u=e[i].v;

              e[m+i].v=e[i].u;

              e[m+i].w=e[i].w;

}

if(bellmanFord(m<<1,n,1))

printf(“%d\n”,dist[n]);

else printf(“No\n”);

}

return 0;

}

 

(2)使用FIFO隊列的優化BF算法(使用鄰接表)

算法時間複雜度:O(mn)

算法代碼:

#include <queue>

#define INF (1<<31)-1

######其他頭文件以及宏定義省略######

int first[MAXN];  //first[u]保存節點u的第一條邊的編號

int u[MAXN],v[MAXN],w[MAXN];

//u[i]表示第i條邊的一端端點,v[i]表示第i條邊的另一端端點,w[i]表示第i條邊的長度

int next[2*MAXN];  //next[i]表示第i條邊的下一條邊的編號【無向圖】

//int next[MAXN]; next[i]表示第i條邊的下一條邊的編號。【有向圖】

#########省略部分#########

queue<int> q;

bool inq[MAXN];

bool bellmanFord(int x)

{

int i,j;

bool ans;

       for(i=0;i<n;i++)d[i]=!i?0:INF;

       memset(inq,0,sizeof(inq));   //在隊列中的標誌

       q.push(x);

       ans=false;

       while(!q.empty())

{

       int x=q.front();q.pop();

       inq[x]=false;

       for(i=first[x];i!=-1;i=next[i]) if(dist[v[e]]>dist[x]+w[e])

{

       dist[v[e]]=dist[x]+w[e];

       ans=true;

       if(!inq[v[e]])

{

       inq[v[e]]=true;

       q.push(v[e]);

}

}

}

return ans;

}

 

三、Floyd-Warshall算法:任意兩點之間的最短路

算法要求:無特殊要求。

算法思想:動態規劃。

時間複雜度:O(n^3)

算法代碼:

#define MAXN 100

#define INF (1<<31)-1

int n,m,p,q;

int f[MAXN+10][MAXN+10];

void Floyd()

{

       inti,j,k;

       for(k=0;k<n;k++)

{

       for(i=0;i<n;i++)

       {

              for(j=0;j<n;j++)

{

       if(f[i][k]+f[k][j]<f[i][j])

              f[i][j]=f[i][k]+f[k][j];

}

}

}

if(f[0][n-1]==INF) printf(“0\n”);

else printf(“%d\n”,f[0][n-1]);

}

 

int main()

{

       inta,b,c,i;

       while(~scanf(“%d%d”,&n,&m))

{

       if(!n&&!m) break;

       for(p=0;p<n;p++)

              for(q=0;q<n;q++)

                     f[p][q]=INF;

       for(i=0;i<m;i++)

{

       scanf(“%d%d%d”,&a,&b,&c);

       f[a][b]=c;

       f[b][a]=c;

}

floyd();

}

return 0;

}

 

四、SPFA算法:單源點最短路的高效實用算法

算法要求:無特殊要求

算法思想:用FIFO隊列優化的BF算法。

算法時間複雜度:O(k|E|),k爲常數,一般k<=2

算法代碼:

#include <queue>

#######省略部分######

#define INF (1<<31)-1

#define N 1010

int dist[N],n,m;

int edge[N][N];

bool vis[N];

 

void spfa(int s)

{

       inti,u;

       memset(vis,false,sizeof(vis));

       for(i=0;i<n;i++)dist[i]=INF;

       queue<int>q;

       q.push(s);

vis[s]=true;

dist[s]=0;

while(!q.empty())

{

       u=q.front();

       q.pop();

       vis[u]=false;

       for(i=0;i<n;i++)

{

       if(dist[i]>dist[u]+edge[u][i])

              dist[i]=dist[u]+edge[u][i];

       if(!vis[i])

{

       vis[i]=true;

       q.push(i);

}

}

}

}

 

int main()

{

       inti,j,a,b,c,origin;

while(scanf(“%d%d”,&n,&m)!=EOF&&(n||m))

{

       for(i=0;i<N;i++)

              for(j=0;j<i;j++)

{

       edge[i][j]=INF;

       edge[j][i]=INF;

}

},

for(i=0;i<m;i++)

{

       scanf(“%d%d%d”,&a,&b,&c);

       if(edge[a][b]>c)

       {

              edge[a][b]=c;

              edge[b][a]=c;

}

scanf(“%d”,&origin);

spfa(origin);

printf(“%d\n”,dist[n]);

}

return 0;

}

發佈了35 篇原創文章 · 獲贊 28 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章