單源最短路問題(SSSP)常用的算法有Dijkstra,Bellman-Ford,這兩個算法進行優化,就有了Dijkstra+heap、SPFA(Shortest Path Faster Algorithm)算法。這兩個算法寫起來非常相似。下面就從他們的算法思路、寫法和適用場景上進行對比分析。如果對最短路算法不太了解,可先看一下相關ppt:最短路
為了解釋得簡單點,以及讓對比更加明顯,我就省略了部分細節。
我們先看優化前的:
\(O(V^2 + E)\)的Dijkstra
n-1次循環
-->找到未標記的d最小的點
-->標記,松弛它的邊
\(O(VE)\)的Bellman-Ford
n-1次循環
-->對所有邊松弛
還能再松弛則有負環
- Dijkstra是每次確定了到一個點的最短距離,再用該點更新到其它點的距離。不能處理有負邊的圖。
- Bellman-Ford是每次對所有邊松弛。可以計算出有負邊無負環的最短路,可以判斷是否存在負環。
接下來再看優化后的:
\(O((V + E)lgV)\)的Dijkstra+heap優化
用STL中的優先隊列實現堆:
while(優先隊列非空)
-->隊頭出隊,松弛它的邊
-->松弛了的<新距離,點>入隊
刪了部分定義和初始化的代碼:
typedef pair<int,int> PII;
priority_queue<PII,vector<PII>,greater<PII> > q;
...
while(!q.empty()){ // O(V) 加上count<n可以優化一點點
int w=q.top().first, u=q.top().second;
q.pop(); // O(lgV)
if(b[u])continue; b[u]=true;
//++count;
for(int i=head[u];i;i=e[i].next){ // Sum -> O(E)
int v=e[i].to;
if(d[u]+e[i].w<d[v]){
d[v]=d[u]+e[i].w;
q.push(PII(d[v],v)); // O(lgV)
}
}
}
\(O(kE)\)\(O(VE)\)的SPFA
while(隊非空)
-->隊頭出隊,松弛它的邊
-->松弛了且不在隊內的點入隊
while(!q.empty()){
int u=q.front(); q.pop();
b[u]=false;
for(int i=head[u];i;i=e[i].next){
int v=e[i].to;
if(d[u]+e[i].w<d[v]){
d[v]=d[u]+e[i].w;
if(!b[v])b[v]=true,q.push(v);
}
}
}
算法思路對比
- Dijkstra+heap是用小根堆,每次取出d最小的點,來更新距離,那么這個點來說,最小距離就是當前的d。
- SPFA是用雙端隊列,每次取出隊頭,來更新距離,它之后可能還會入隊。它是一種動態逼近法,因為每次松弛距離都會減小,所以松弛一定會有結束的。如果一個點入隊超過n次就是存在負環。
復雜度分析對比
Dijkstra+heap
- 因為是堆,取隊頭需要O(lgV)。
- 松弛邊時,因為點的d改變了,所以點v需要以新距離重新入堆,O(lgV),總共O(ElgV)。
- 因此總的是\(O((V + E)lgV)\)
SPFA
- 論文證明也不嚴格。復雜度不太好分析。
總的是O(kE)。k大概為2。- 復雜度應該是 \(O(VE)\)。
適用場景
如果是稠密圖,Dijkstra+heap比SPFA快。稀疏圖則SPFA更快。SPFA可以有SLF和LLL兩種優化,SLF就是d比隊頭小就插入隊頭,否則插入隊尾。
另外,Dijkstra和Prim也很相似,它們的區別主要是d的含義,前者是到s的臨時最短距離,后者是到樹的臨時最短距離,相同點是,每次找d最小的更新其它點的距離。
