1. 程式人生 > >單源最短路徑:Dijkstra演算法(堆優化)

單源最短路徑:Dijkstra演算法(堆優化)

前言:趁著對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