1. 程式人生 > >單源最短路徑--Dijkstra

單源最短路徑--Dijkstra

Dijkstra的用途:

  • Dijkstra是一個求單源最短路徑的演算法。

  • "單源最短路徑",顧名思義,從一個源頭到其他結點的最短路徑。而這個演算法,可以求出單個點對其他所有點的最短路徑長度。(有些情況是Dijkstra不能處理的,比如負邊權,遇到這類情況可能就需要使用其他演算法了)

 
Dijkstra的思想:

  • 主要思想:

  • 從源結點開始,遍歷源結點的所有連線的邊,並進行排序,再從邊的值最小的一個邊開始遍歷,在每一次遍歷的過程中,都嘗試更新所儲存的某個結點對於源結點的最短路徑長度(假設我們要到點B,目前我們遍歷到了點A,如果點A到點B的距離加上點A到源點的距離比目前我們之前儲存的

    源點到點B的距離短的話,就更新源點到點B的最短路徑,並在之後遍歷連著點B的點(也就是進行BFS))。

  • 例子:

  • 首先,從源結點開始進行BFS(也就是遍歷到達的點所向外連著的點.比如A->B B->A A->C(A連B,B連A,A連C),那麼BFS A結點就是B C)

  • 在BFS的過程中,每到一個點,就比較一下從 源頭到這個點的路徑長度 和 之前求的從源頭到這個點的最短路徑的長度 大小,如果現在這個路徑長度比較小,就存起來

Dijkstra的實現方法:

  • 首先,存圖(使用鄰接表或鏈式前向星(如果不會存圖建議先學存圖,因為這裡不會講
    怎麼存圖和BFS)).
  • 演算法執行過程:
  1. 把資料(也就是dis陣列)初始化為INF(為什麼要這樣做?這是因為我們要使用較小的值不停更新它,這樣最終就是源點到這個點的最小值了。那怎麼知道某個點所對應的與源點的距離呢?一般是把源點的序號當做陣列的下標。下標是什麼意思呢?比如使用這個程式碼:int i=A[10],10就是陣列A的一個下標。源點的序號是什麼意思呢?一般使用源點讀入的順序當做源點的序號)
  2. 準備一個優先佇列,用於之後進行的BFS.
  3. 在BFS的過程中不斷更新最小值,並且,如果一個點被更新了,就再把被更新的點推入優先佇列,準備之後在這個點上進行BFS.
  4. 優化

Dijkstra的優化:

  • 優化方法:
  • 堆優化(這就是使用優先佇列(這裡我的程式碼使用了STL中的priority_queue)的原因)。因為我們是從源點開始遍歷,並不停用離源點儘量小的點來更新其他點的當前最短路徑,所以如果遍歷佇列中的結點時,發現這個結點中所儲存的 向佇列推入這個點時,這個點離源點的距離 與當前這個點到源點的最短路徑不同的話,這是因為這個點的最短路徑已經被別人更新過了,也就是說這個點的資料已經無法使用了。並且,在更新這個點的最短路徑(在我的實現中使用了dis陣列(這個括號內的內容並不重要,只是為了防止一些人不明白怎麼更新這個點的最短路徑))時,肯定已經把這個點推到佇列中了。因此可以得出:如果這個點中儲存的最短路徑(我們用Node結構體,儲存點的序號和將這個點推入佇列時源點到這個點的最短路徑)和目前的最短路徑不一樣,那麼必然這個點中儲存的最短路徑是較長的,並且這個點已經無用了,只會造成重複計算 所以,當我們判斷到這個條件時,就可以直接跳過這個結點。

Dijkstra的使用注意事項:

  • 如果圖是無向圖,需要雙向加邊(也就是呼叫兩次addEdge方法,比如A-B這條邊 就可以addEdge(a,b);addEdge(b,a)(這裡忽略了權值))

模板題目地址:https://www.luogu.org/problemnew/show/P4779

過載運算子版:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<queue>
using namespace std;
int s,cnt=0,dis[1000010],head[1000010];
//s:源結點 cnt:邊總數,用於存邊 dis:儲存當前的源結點到某個結點的最短路徑長度(下標是結點序號) head:鄰接表, 
struct Edge{
    int v,w,nxt;
}e[500010];
struct Node{
    int u,d;
    bool operator <(const Node& rhs)const{
        return d>rhs.d;//過載運算子,這個需要背過 
    }
};
void addEdge(int u,int v,int w){
    e[++cnt].v=v;
    e[cnt].w=w;
    e[cnt].nxt=head[u];
    //關於鄰接表: 
    //首先要明確,鄰接表是一個鏈式結構,也就是說,首先新增的邊在鄰接表的最後面
    //最後新增的在前面,在遍歷的時候也是從頭往後遍歷,也就是說最先新增的反而最後被遍歷到. 
    //當遍歷到最後一個的時候 由於最後一個的後面已經沒有結點了,那麼此時i的值就成為了0(具體看下面的for迴圈程式碼)
    //成為0了,也就代表著迴圈該結束了.這時候,迴圈條件不成立,迴圈就結束了. 
    head[u]=cnt;
}
void dijkstra(){
    dis[s]=0;
    priority_queue<Node>q;
    q.push((Node){s,0});
    while(!q.empty()){
        Node now=q.top();q.pop();
        int d=now.d,u=now.u;
        if(d!=dis[u])continue;//堆優化,等同於 if(d>dis[u])continue;,可以相對較好地減少時間複雜度
        for(int i=head[u];i;i=e[i].nxt){
            int v=e[i].v,w=e[i].w;
            if(dis[v]>dis[u]+w){
                dis[v]=dis[u]+w;
                q.push((Node){v,dis[v]});
            }
        }
    }
}
int main(){
    int n,m;
    scanf("%d%d%d",&n,&m,&s);
    for(int i=1;i<=n;i++)dis[i]=2147483647;//將源結點到其他結點的路徑長度初始化為儘可能大的值,也就是INF 
    for(int i=1;i<=m;i++){
        int x,y,z;
        scanf("%d%d%d",&x,&y,&z);
        addEdge(x,y,z);
    }
    dijkstra();
    for(int i=1;i<=n;i++)printf("%d ",dis[i]);
    return 0;
}

 

Pair版:

#include<bits/stdc++.h>
#include<iostream>
#define P pair<long long,int>
#define INF 2147483647
#define maxn 500005
using namespace std;
priority_queue<P,vector<P>,greater<P> >q;
int dis[maxn],vis[maxn],head[maxn];
int n,m,s;
int cnt;
struct Edge{
    int u,v,w,next;
}e[maxn*2];
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(){
    while(!q.empty()){
        int x=q.top().second;
        q.pop();
        if(!vis[x]){
            vis[x]=1;
            for(int i=head[x];i;i=e[i].next){
                int v=e[i].v;
                dis[v]=min(dis[v],dis[x]+e[i].w);
                q.push(make_pair(dis[v],v));
            }
        }
        
    }
    
}
int main(){
    cin>>n>>m>>s;
    for(int i=1;i<=m;i++){
        dis[i]=INF;
    }
    dis[s]=0;
    q.push(make_pair(0,s));
    for(int i=1;i<=m;i++){
        int a,b,c;
        cin>>a>>b>>c;
        add(a,b,c); 
    }
    dijkstra();
    for(int i=1;i<=n;i++){
        cout<<dis[i]<<" ";
    }
    cout<<endl;
    return 0;
        
}