1. 程式人生 > >單源最短路徑算法總結

單源最短路徑算法總結

算法總結 使用 距離 組合 () 結構體 spa 最短路 etc

這裏給大家介紹三種最短路常用算法:

floyd(O(n^3))、dijkstra(O(nlogn))、SPFA(O(KE))(k是進隊列次數,在沒有負環的情況下2)

其實還有一個Bellman-Ford(O(nm))算法,但由於不常用而且SPFA是這個算法的改進版本,在這裏就不列舉了

floyd:效率較低,只有70分

具體思路:將所有節點的距離都存在一個數組裏,由於要枚舉所有的兩兩組合以及每一個組合的“中轉點”,再進行松弛操作

在求單源最短路徑的時候就會浪費許多空間,但在求多源最短路時,復雜度仍是O(n^3)使用很廣

#include<bits/stdc++.h>
using namespace std;
#define inf 1234567890
#define maxn 10005
inline int read()
{
    int x=0,k=1; char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')k=-1;c=getchar();}
    while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+(c^48),c=getchar();
    return x*k;
}//快讀
int a[maxn][maxn],n,m,s;
inline void floyd()
{
    for(int k=1;k<=n;k++)
    //這裏要先枚舉k(可以理解為中轉點)
    {
        for(int i=1;i<=n;i++)
        {
            if(i==k||a[i][k]==inf)
            {
                continue;
            }
            for(int j=1;j<=n;j++)
            {
                a[i][j]=min(a[i][j],a[i][k]+a[k][j]);
                //松弛操作,即更新每兩個點之間的距離
                //松弛操作有三角形的三邊關系推出
                //即兩邊之和大於第三邊
            }
        }
    }
}
int main()
{
    n=read(),m=read(),s=read();
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=n;j++)
        {
            a[i][j]=inf;
        }
    }
    //初始化,相當於memset(a,inf,sizeof(a))
    for(int i=1,u,v,w;i<=m;i++)
    {
        u=read(),v=read(),w=read();
        a[u][v]=min(a[u][v],w);
        //取min可以對付重邊
    }
    floyd();
    a[s][s]=0;
    for(int i=1;i<=n;i++)
    {
        printf("%d ",a[s][i]);
    }
    return 0;
}

dijkstra:對於無負邊的情況下可以達到O(nlogn)且很難被卡

具體思路:Dijkstra是基於一種貪心的策略,首先用數組dis記錄起點到每個結點的最短路徑,再用一個數組保存已經找到最短路徑的點

然後,從dis數組選擇最小值,則該值就是源點s到該值對應的頂點的最短路徑,並且把該點記為已經找到最短路

此時完成一個頂點,再看這個點能否到達其它點(記為v),將dis[v]的值進行更新

不斷重復上述動作,將所有的點都更新到最短路徑

這種算法實際上是O(n^2)的時間復雜度,但我們發現在dis數組中選擇最小值時,我們可以用一些數據結構來進行優化。線段樹?平衡樹?

其實我們可以用STL裏的堆來進行優化,堆相對於線段樹以及平衡樹有著常數小,碼量小等優點,並且堆的一個妙妙的性質就是可以在nlogn的時限內滿足堆頂是堆內元素的最大(小)值,之不正是我們要的嘛?

以下是用堆優化dijkstra代碼:
#include<bits/stdc++.h>
using namespace std;
#define maxn 10005
#define maxm 500005
#define INF  1234567890
inline int read()
{
    int x=0,k=1; char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')k=-1;c=getchar();}
    while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+(c^48),c=getchar();
    return x*k;
}
struct Edge
{
    int u,v,w,next;
}e[maxm];
int head[maxn],cnt,n,m,s,vis[maxn],dis[maxn];
struct node
{
    int w,now;
    inline bool operator <(const node &x)const
    //重載運算符把最小的元素放在堆頂(大根堆)
    {
        return w>x.w;//這裏註意符號要為'>'
    }
};
priority_queue<node>q;
//優先隊列,其實這裏一般使用一個pair,但為了方便理解所以用的結構體
inline void add(int u,int v,int w)
{
    e[++cnt].u=u;
    //這句話對於此題不需要,但在縮點之類的問題還是有用的
    e[cnt].v=v;
    e[cnt].w=w;
    e[cnt].next=head[u];
    //存儲該點的下一條邊
    head[u]=cnt;
    //更新目前該點的最後一條邊(就是這一條邊)
}
//鏈式前向星加邊
void dijkstra()
{
    for(int i=1;i<=n;i++)
    {
        dis[i]=INF;
    }
    dis[s]=0;
    //賦初值
    q.push((node){0,s});
    while(!q.empty())
    //堆為空即為所有點都更新
    {
        node x=q.top();
        q.pop();
        int u=x.now;
        //記錄堆頂(堆內最小的邊)並將其彈出
        if(vis[u]) continue; 
        //沒有遍歷過才需要遍歷
        vis[u]=1;
        for(int i=head[u];i;i=e[i].next)
        //搜索堆頂所有連邊
        {
            int v=e[i].v;
            if(dis[v]>dis[u]+e[i].w)
            {
                dis[v]=dis[u]+e[i].w;
                //松弛操作
                q.push((node){dis[v],v});
                //把新遍歷到的點加入堆中
            }
        }
    }
}
int main()
{
    n=read(),m=read(),s=read();
    for(int i=1,x,y,z;i<=m;i++)
    {
        x=read(),y=read(),z=read();
        add(x,y,z);
    }
    dijkstra();
    for(int i=1;i<=n;i++)
    {
        printf("%d ",dis[i]);
    }
    return 0;
}

SPFA:考場慎用,在毒瘤數據面前可能退化到O(nm)

具體思路:這裏用的是STL隊列,首先用數組dis記錄起點到每個結點的最短路徑,用鄰接表來存儲圖,用vis數組記錄當前節點是否在隊列中

具體操作為:用隊列來保存待優化的結點(類似於BFS),優化時每次取出隊首結點,並且用隊手節點來對最短路徑進行更新並進行松弛操作

如果要對所連點的最短路徑需要更新,且改點不在當前的隊列中,就將改點加入隊列

然後不斷進行松弛操作,直至隊列空為止。


#include<bits/stdc++.h>
using namespace std;
inline int read()
{
    int x=0,k=1; char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')k=-1;c=getchar();}
    while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+(c^48),c=getchar();
    return x*k;
}
#define maxn 10005
#define maxm 500005
#define inf 1234567890
int n,m,s,tot,dis[maxn],head[maxn];
bool vis[maxn];
struct Edge
{
      int next,to,w;
}h[maxm];
void add(int u,int v,int w)
{
    h[++tot].next=head[u];
    h[tot].to=v;
    h[tot].w=w;
    head[u]=tot;
}
//上面和dijkstra算法基本上一樣
queue<int> q;
//隊列優化
inline void spfa()
{
    for(int i=1; i<=n; i++)
    {
        dis[i]=inf;
        //賦初值
    }
    int u,v;
    q.push(s);
    dis[s]=0;
    //將起點的值負為0
    vis[s]=1;//這句話可加可不加,因為循環的時候vis[s]又會被賦為0
    while(!q.empty())
    //當隊列裏沒有元素的時候,那就已經更新了所有的單源最短路徑
    {
        u=q.front();
        //將隊手節點記錄並彈出隊首節點
        q.pop();
        vis[u]=0;
        for(int i=head[u];i;i=h[i].next)
        //尋找與u相連的邊
        {
            v=h[i].to;
            if(dis[v]>dis[u]+h[i].w)
            {
                dis[v]=dis[u]+h[i].w;
                //松弛操作,和floyd比較相似
                if(!vis[v])
                {
                //已經在隊列裏的點就不用再進入了
                      vis[v]=1;
                      q.push(v);
                }
            }
        }
    }
}
int main(){
    n=read(),m=read(),s=read();
    for(int i=1,u,v,w;i<=m;i++)
    {
        u=read(),v=read(),w=read();
        add(u,v,w);
    }
    spfa();
    for(int i=1; i<=n; i++)
    {
        printf("%d ",dis[i]);
    }
    return 0;
}

單源最短路徑算法總結