單源最短路徑--Dijkstra
阿新 • • 發佈:2018-11-10
Dijkstra的用途:
-
Dijkstra是一個求單源最短路徑的演算法。
-
"單源最短路徑",顧名思義,從一個源頭到其他結點的最短路徑。而這個演算法,可以求出單個點對其他所有點的最短路徑長度。(有些情況是Dijkstra不能處理的,比如負邊權,遇到這類情況可能就需要使用其他演算法了)
Dijkstra的思想:
-
主要思想:
-
從源結點開始,遍歷源結點的所有連線的邊,並進行排序,再從邊的值最小的一個邊開始遍歷,在每一次遍歷的過程中,都嘗試更新所儲存的某個結點對於源結點的最短路徑長度(假設我們要到點B,目前我們遍歷到了點A,如果點A到點B的距離加上點A到源點的距離比目前我們之前儲存的
-
例子:
-
首先,從源結點開始進行BFS(也就是遍歷到達的點所向外連著的點.比如A->B B->A A->C(A連B,B連A,A連C),那麼BFS A結點就是B C)
-
在BFS的過程中,每到一個點,就比較一下從 源頭到這個點的路徑長度 和 之前求的從源頭到這個點的最短路徑的長度 大小,如果現在這個路徑長度比較小,就存起來
Dijkstra的實現方法:
- 首先,存圖(使用鄰接表或鏈式前向星(如果不會存圖建議先學存圖,因為這裡不會講
- 演算法執行過程:
- 把資料(也就是dis陣列)初始化為INF(為什麼要這樣做?這是因為我們要使用較小的值不停更新它,這樣最終就是源點到這個點的最小值了。那怎麼知道某個點所對應的與源點的距離呢?一般是把源點的序號當做陣列的下標。下標是什麼意思呢?比如使用這個程式碼:int i=A[10],10就是陣列A的一個下標。源點的序號是什麼意思呢?一般使用源點讀入的順序當做源點的序號)
- 準備一個優先佇列,用於之後進行的BFS.
- 在BFS的過程中不斷更新最小值,並且,如果一個點被更新了,就再把被更新的點推入優先佇列,準備之後在這個點上進行BFS.
- 優化
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;
}