[OI學習筆記]DAG最短路的四種算法整理-floyd,Dijkstra,Bellman-Ford,SPFA


背景

  開學了,好開心啊!

    周末好不容易寫篇博客,搞長一點把。。。

最短路概念

 

    這周花了點時間研究最短路問題,那么什么是最短路呢?

    摘自百度百科:

    最短路問題(short-path problem)是網絡理論解決的典型問題之一,可用來解決管路鋪設、線路安裝、廠區布局和設備更新等實際問題。基本內容是:若網絡中的每條邊都有一個數值(長度、成本、時間等),則找出兩節點(通常是源節點和阱節點)之間總權和最小的路徑就是最短路問題。 [1] 

    我了解的最短路算法有四種——floyd,Dijkstra,Bellman-Ford,SPFA,四種算法各有各的特點,適用的圖也有不同。

松弛操作

 

    在學習最短路算法前,首先要了解松弛操作,這是所有最短路算法的核心,即是對於一條邊(u,v,w),比較以中間點k或不以k當中間點時的最短路那個最小,選擇小的那條來更新最短路:

if(dist[u]+w<dist[v])dist[v]=dist[u]+w;

 

(A)Floyd算法  時間復雜度O(n3)

        1)基於動態規划,本質上是一個三維的DP

        2)與其他算法不同,floyd是一個多源最短路徑算法,即經過一次floyd后能求出任意兩點間的最短路

        3)基本思想:dist[i][j][k]是i到j的只以1-k為中間路點的最短路,則dist[i][j][k]=min(dist[i][j][k-1],dist[i][k][k-1]+dist[k][j][k-1]);即選擇不以k或以k當中間點時的最小的dist為dist[i][j][k]

        4)具體實現:

            ▶初始化:dist[s][v]=l(s,v)【如果存在邊(s,v)】

            ▶進行松弛操作

        3)核心代碼

for(int k=1;k<=n;k++)
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            if(dist[i][k]+dist[k][j]<dist[i][j])
                dist[i][j]=dist[i][k]+dist[k][j];

 

 

 

(B)Dijkstra算法  時間復雜度:堆優化:O((n+m)*log m)  無優化:O(nm)

    1)只適用於無負邊權的圖(有負邊要么TLE要么WA)

    2)基本思想:不斷用已更新的邊去更新他所連接的邊。

    3)具體實現:

        ▶初始化:dist[s]=0;其余dist[i]=INF;然后把s點加入待處理隊列

        ▶更新:每次以隊首節點為基礎更新

    4)

       Q :如果要求具體的最短路徑怎么辦?

      A :在更新時如果可以更新就用一個path數組存儲這個點是從那個點更新來的,輸出時帶着輸出就可以了

    5)

        Q:怎么寫堆優化?

       A:用一個優先隊列(原理是小根堆)存儲每次更新了的點,依次用這些點去更新這個點所連的邊,更新完后就讓他出隊

    6)代碼:

#include <bits/stdc++.h>
#define re register
using namespace std;

inline int read() {
    int X=0,w=1; char c=getchar();
    while (c<'0'||c>'9') { if (c=='-') w=-1; c=getchar(); }
    while (c>='0'&&c<='9') X=(X<<3)+(X<<1)+c-'0',c=getchar();
    return X*w;
}

struct Edge { int v,w,nxt; };
Edge e[500010];
int head[100010],cnt=0;

inline void addEdge(int u,int v,int w) {
    e[++cnt].v=v;
    e[cnt].w=w;
    e[cnt].nxt=head[u];
    head[u]=cnt;
}

int n,m,s;
int dis[100010];

struct node { //堆節點
    int u,d;//存儲每次更新到的點和它的dist
    //重載運算
    bool operator <(const node& rhs) const {
        return d>rhs.d;
    }
};

inline void Dijkstra() {
    for (re int i=1;i<=n;i++) dis[i]=2147483647;
    dis[s]=0;
    priority_queue<node> Q; //
    Q.push((node){s,0});
    while (!Q.empty()) {
        node fr=Q.top(); Q.pop();
        int u=fr.u,d=fr.d;
        if (d!=dis[u]) continue;
        for (re int i=head[u];i;i=e[i].nxt) {
            int v=e[i].v,w=e[i].w;
            if (dis[u]+w<dis[v]) {
                dis[v]=dis[u]+w;
                Q.push((node){v,dis[v]});
            }
        }
    }
}

int main() {
    n=read(),m=read(),s=read();
    for (re int i=1;i<=m;i++) {
        int X=read(),Y=read(),Z=read();
        addEdge(X,Y,Z);
    }
    Dijkstra();
    for (re int i=1;i<=n;i++) printf("%d ",dis[i]);
    return 0;
}

 

(C)Bellman-ford算法  時間復雜度:O(mn)

    1)基於動態規划

    2)可以用來判負環

    3)如果沒有負環,至多更新n-1輪或者某輪沒有更新即可出解

    4)判負環:如果更新第n-1環之后還有邊能更新,就有負環

    5)具體實現:

        1)dist[s]=0;其余=INF

        2)更新n-1輪,其中每輪:

            1)對於每一條邊,如果if(dist[u]+w<dist[v])dist[v]=dist[u]+w;並且把updated標記改為有更新

            2)更完每條邊后,如果updateed標記是沒更新,就break,因為這時已經更新結束,並且這樣肯定沒負環

            3)最后把updated改為0,進行下一輪

        4)再單獨更新一輪,如果這單獨一輪中有更新,就判為有負環

    6)生動形象的演示:

    

#include<cstdio>
#define INF 2147483647
#define MAX 10010
struct Edge{
    int u,v,w;
}edge[MAX];
int n,m,s,t,dist[MAX];
void BellmanFord(){
    int updated=0;
    for(int j=1;j<=n-1;j++){
        for(int i=1;i<=m;i++)
            if(dist[edge[i].u]+edge[i].w<dist[edge[i].v]){
                dist[edge[i].v]=dist[edge[i].u]+edge[i].w;
                updated=1;
            }
        if(!updated)break;
        updated=0;
    }
    for(int i=1;i<=m;i++)
        if(dist[edge[i].u]+edge[i].w<dist[edge[i].v]){
            printf("Orz");
            return;
        }
    printf("%d",dist[t]);    
}
int main(){
    scanf("%d%d%d%d",&n,&m,&s,&t);
    int X,Y,Z;
    for(int i=1;i<=m;i++){
        scanf("%d%d%d",&X,&Y,&Z);
        edge[i].u=X;
        edge[i].v=Y;
        edge[i].w=Z;
    }
    BellmanFord();
    return 0;
}

 

 

 

(D)SPFA 常被卡 時間復雜度:最壞:O(nm)  一般:不知道

    1)關於SPFA,他死了

    2)原因是在NOI中Dijkstra和SPFA這對難兄難弟經常其中一個被卡數據導致不能通過,而SPFA最經常

    3)和Dijkstra有點像,我有時候都分不清

    3)SPFA也是用一個隊列(不是優先隊列),第一步從s點開始,s入隊,對於隊中的每一個元素,廣度遍歷其所有出邊(u,v,w),並對其進行松弛,如果松弛可以進行,就讓v入隊,搜算完成后,讓u出隊,進行下一輪搜索,直至隊列空

    4)是bellmanford的優化

    5)具體實現:

        1)初始化:dist[s]=0;其余=INF;

        2)從s開始搜索,進行松弛,直至隊列空

    6)代碼:

#include <bits/stdc++.h>
#define ll long long
#define MAXM 500005
#define MAXN 10005
using namespace std;
    int n,m,s,sz,st,en;
    int Q[4000005],f[MAXN],to[MAXM<<1],nex[MAXM<<1],v[MAXM<<1],las[MAXN];
    bool inq[MAXN];
int inline read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
inline void ins(int x,int y,int z)
{
    sz++;to[sz]=y;v[sz]=z;nex[sz]=las[x];las[x]=sz;
}
void spfa()
{
    memset(f,127/2,sizeof(f));
    f[s]=0;Q[st=en=1]=s;
    while (st<=en)
    {
        int x=Q[st++];
        inq[x]=false;
        for (int i=las[x];i;i=nex[i])
        if (f[x]+v[i]<f[to[i]])
        {
            f[to[i]]=f[x]+v[i];
            if (!inq[to[i]])
            {
                inq[to[i]]=true;
                Q[++en]=to[i];
            }
        }
    }
}
int main()
{
    n=read(),m=read(),s=read();
    for (int i=1;i<=m;i++)
    {
        int x=read(),y=read(),z=read();
        ins(x,y,z);
    }
    spfa(); 
    for (int i=1;i<=n;i++)
        printf("%d%c",f[i]>1e9?2147483647:f[i],i==n?'\n':' ');
    return 0;
}

 

 

 

    以上就是關於DAG最短路的算法,各種算法各有各的優缺點,主要體現在時空復雜度上,要謹慎使用

    一道模板題(普及-難度)

 

    關於圖論的內容的話再寫一個拓補排序就不寫了(表示圖論從暑假看到現在已經看吐了)

    求推薦.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM