1. 程式人生 > >【模板】最短路徑(迪傑斯特拉、SPFA、弗洛伊德)

【模板】最短路徑(迪傑斯特拉、SPFA、弗洛伊德)

迪傑斯特拉演算法(Dijkstra's Algorithm)

解決單源最短路問題的優秀演算法,堆優化後時間複雜度降到O((m+n)logn)。

#include<iostream>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<queue>
#include<vector>
#define FOR(i,x,y) for(int i=(x);i<=(y);i++)
#define DOR(i,x,y) for(int i=(x);i>=(y);i--)
#define N 10003
#define M 500003
#define INF 2147483647
using namespace std;
int n,m;
int dis[N];

struct edge
{
    int to,cost;
};
vector<edge>E[N];   //這裡以鄰接表為例

struct node
{          //走到at節點的最短路為path
    int at,path;
    bool operator<(const node &_)const
    {
        return path>_.path;   //path小的先出
    }
};

void dijkstra(int s)
{
    FOR(i,1,n)dis[i]=INF;
    dis[s]=0;
    priority_queue<node>q;
    while(!q.empty())q.pop();    //使用前測試STL是好習慣
    q.push((node){s,0});        //把起點先加入佇列
    while(!q.empty())
    {
    	node now=q.top();q.pop();   //取距離最小的節點
    	int u=now.at;
    	if(dis[u]<now.path)continue;   //剪枝,如果已經找到更小的dis[u]那麼只需要鬆弛那一次就夠了
    	FOR(i,0,(int)E[u].size()-1)
    	{
    		int v=E[u][i].to,w=E[u][i].cost;
    		if(dis[v]>dis[u]+w)
    		{
    			dis[v]=dis[u]+w;
    			q.push((node){v,dis[v]});
                }
        }
    }
    return;
}

int main()
{
    int s;
    scanf("%d%d%d",&n,&m,&s);
    FOR(i,1,m)
    {
        int u,v,w;
        scanf("%d%d%d",&u,&v,&w);
        E[u].push_back((edge){v,w});
    }
    dijkstra(s);
    FOR(i,1,n)printf("%d ",dis[i]);
    printf("\n");
    return 0;
}


Dj演算法的流程如上,其實不難理解。從源點出發開始鬆弛,然後尋找沒有作過起點的節點中距離最小的,作為新的起點進行下一輪操作。特別要提示的是,Dj演算法只適用於正權圖,對於有負邊出現的圖不好使,這也能解釋為什麼凡是標紅的點一定是找到最短路的。

而堆優化其實是優化了尋找距離最小節點這步,從程式碼中不難看出,如果出現負邊,就會出現死迴圈。因為已經作過起點的節點還會作無數次起點。

SPFA演算法(Shortest Path Faster Algorithm)

實際上是Bellman-Ford演算法的優化,時間複雜度為O(kn),k一般小於等於2n.

#include<iostream>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<queue>
#define FOR(i,x,y) for(int i=(x);i<=(y);i++)
#define DOR(i,x,y) for(int i=(x);i>=(y);i--)
#define N 10003
#define M 500003
#define INF 2147483647
using namespace std;
int n,m,tot;
int head[N],dis[N];

struct edge
{
    int next,to,w;   //E[i].next是邊i的“下一條”邊的序號
}E[M];

void add(int u,int v,int w)  //這裡以前向星為例
{
    E[++tot].next=head[u];  //head[u]是以u為起點的“第一條”邊的序號
    E[tot].to=v;
    E[tot].w=w;
    head[u]=tot;
    return;
}

void spfa(int s)
{
    bool vis[N]={0};
    vis[s]=1;    //vis[i]表示i是否在佇列中
    queue<int>q;
    while(!q.empty())q.pop();
    q.push(s);
    FOR(i,1,n)dis[i]=INF;
    dis[s]=0;
    while(!q.empty())
    {
        int u=q.front();q.pop();
        vis[u]=0;
        for(int i=head[u];i!=0;i=E[i].next) //前向星遍歷
        {
        	int v=E[i].to,w=E[i].w;
        	if(dis[v]>dis[u]+w)
        	{
        		dis[v]=dis[u]+w;
        		if(!vis[v])
        		{
        		    vis[v]=1;
        		    q.push(v);
                }
            }
        }
    }
    return;
}

int main()
{
    int s;
    scanf("%d%d%d",&n,&m,&s);
    for(int i=1;i<=m;i++)
    {
    	int u,v,w;
    	scanf("%d%d%d",&u,&v,&w);
    	add(u,v,w);
    }
    spfa(s);
    FOR(i,1,n)printf("%d ",dis[i]);
    printf("\n");
    return 0;
}

spfa比較淺顯,這裡重點介紹前向星。

前向星是一種存圖方式,它將起點相同的邊互相用next鏈起(而鄰接表是直接將它們存在同一個vector中),每次加入新的邊(u,v)都會鏈上上一條起點為u的邊,然後更新起點為u的“第一條”邊。

這種儲存方式,使得訪問邊的順序與輸入順序相反,不過這也沒關係。

弗洛伊德演算法(Floyd's Algorithm)

弗洛伊德演算法是解決多源最短路問題的優秀演算法,基於動態規劃,此演算法的精妙之處在於核心程式碼只有4-5行

#include<iostream>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#define FOR(i,x,y) for(int i=(x);i<=(y);i++)
#define DOR(i,x,y) for(int i=(x);i>=(y);i--)
#define N 1003
#define M 500003
#define INF 2147483647
using namespace std;
int n,m;
int dis[N][N];  //此演算法用鄰接矩陣存邊

int main()
{
    int s;
    scanf("%d%d%d",&n,&m,&s);
    FOR(i,1,n)FOR(j,1,n)dis[i][j]=INF;
    FOR(i,1,n)dis[i][i]=0;
    FOR(i,1,m)
    {
        int u,v,w;
        scanf("%d%d%d",&u,&v,&w);
        dis[u][v]=min(dis[u][v],w);
    }
    FOR(k,1,n)          //Floyd
        FOR(i,1,n)
            FOR(j,1,n)
            	if(dis[i][k]!=INF && dis[k][j]!=INF)   //這裡因為題目需要,事實上如果2INF<MaxInt,這行程式碼可以省去
            	    dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
    FOR(i,1,n)printf("%d ",dis[s][i]);
    printf("\n"); 
    return 0;
}
弗洛伊德時間複雜度達到O(n³),當n在100以內時求多源最短路問題可用它很方便的解決。