單源最短路徑:Dijkstra演算法(堆優化)
阿新 • • 發佈:2020-05-03
前言:趁著對Dijkstra還有點印象,趕快寫一篇筆記。
注意:本文章面向已有Dijkstra演算法基礎的童鞋。
### 簡介
單源最短路徑,在我的理解裡就是求從一個源點(起點)到其它點的最短路徑的長度。
當然,也可以輸出這條路徑,都不是難事。
但是,Dijkstra不能處理有負權邊的圖。
---
### 解析
---
**注:接下來,我們的源點均預設為1。**
先上程式碼(**注意,是堆優化過的!!**):
```cpp
struct node{
int id;
int total;
node(){};
node(int Id,int Total){
id=Id;
total=Total;
}
bool operator < (const node& x) const{
return total>x.total;
}
};
void dijkstra(int start){
memset(dis,inf,sizeof(dis));
memset(conf,false,sizeof(conf));
memset(pre,0,sizeof(pre));
dis[start]=0;
priority_queue Q;
Q.push(node(1,0));
while(Q.size()){
int u=Q.top().id;
Q.pop();
if(conf[u])
continue;
conf[u]=true;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v;
int cost=dis[u]+e[i].w;
if(cost < dis[v]){
dis[v]=cost;
pre[v]=u;
Q.push(node(v,dis[v]));
}
}
}
}
```
接下來,一步一步解析程式碼:
---
首先是結構體```node```
```cpp
struct node{
int id;
int total;
node(){};
node(int Id,int Total){
id=Id;
total=Total;
}
bool operator < (const node& x) const{
return total>x.total;
}
};
```
這裡的id就是這個結點的編號,total就是走到當前節點的最小花費。
建構函式就不用我多說了吧。
因為在原始的Dijkstra中,每次都要選出當前花費最小的那個點,如果採用堆優化,使得堆頭永遠都是花費最小的那個,這樣每次選出花費最小的那個點的時間複雜度從$O(n)$驟降到$O(logn)$。
如果要用到堆,就可以使用STL的優先佇列(```priority_queue```)。
因為優先佇列預設是優先順序最高的放在最前面,在Dijkstra中,優先順序就是這個node的total,total越小優先順序就越高。
因為total越大,優先順序越低,所以這裡的小於運算子就可以定義為```total>x.total```。
---
接下來是初始化
```cpp
memset(dis,inf,sizeof(dis));
memset(conf,false,sizeof(conf));
memset(pre,0,sizeof(pre));
dis[start]=0;
Q.push(node(1,0));
```
陣列```dis[i]```表示的是從源點到點i的最短路的長度,初始時不知道能不能到達,設為```inf```(無窮大)。
陣列```conf[i]```表示的是點i的最短路徑是否確認,若是,則為```true```,否則為```false```。
陣列```pre[i]```表示的是點i的前驅,即到點i的前一個點的編號。
例如有一條最短路徑是這樣的:```1->3->8->5->2```,那麼```pre[2]=5;pre[5]=8;pre[8]=3;```。
這樣一來,輸出路徑就好辦了:
```cpp
//假設要輸出到2的路徑
int i=2;
while(pre[i]!=1){
ans.push(i);
i=pre[i];
}
printf("1");
while(!ans.empty()){
printf("->%d",ans.top());
ans.pop();
}
```
此外,一開始從結點1出發,到結點1的距離為0,知道這些資訊後,將源點入堆。
```cpp
Q.push(node(1/*節點編號*/,0/*到該節點距離*/));
```
---
接下來是重點了,我們再次一步步地拆分:
```cpp
int u=Q.top().id;
Q.pop();
if(conf[u])
continue;
conf[u]=true;
```
這個應該不難理解,首先拿出一個源點u,u的編號自然是```Q.top().id```。接下來```Q.pop()```必不可少。
這時候,如果```conf[u]==true```,即結點u的最短路長度已經確定過了,那就沒必要再走了,因為之前肯定走過了。直接```continue```看下一個結點。
如果沒有,按照Dijkstra的特性,當前結點u的總路徑長度肯定是最短了,那麼就被確定了,```conf[u]=true```。
然後是下一段:
```cpp
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v;
int cost=dis[u]+e[i].w;
if(cost < dis[v]){
dis[v]=cost;
pre[v]=u;
Q.push(node(v,dis[v]));
}
}
```
這段其實好理解,不過我用的是鏈式前向星存圖,如果你用的是vector做的鄰接表,其實大體上是相同的。
如果你用的是鄰接表或鄰接矩陣,這裡的```v```其實就是當前找的這條路的終點(```e[i].v```表示的是這條邊的終點。
而```cost```,則是```dis[u]```的值加上這條邊的權值(沒錯,```e[i].w```表示的是這條邊的權值),也就是到點v的總花費。
如果```cost