Dijkstra:適用於權值為非負的圖的單源最短路徑,用斐波那契堆的複雜度O(E+VlgV)
BellmanFord:適用於權值有負值的圖的單源最短路徑,並且能夠檢測負圈,複雜度O(VE)SPFA:適用於權值有負值,且沒有負圈的圖的單源最短路徑,論文中的複雜度O(kE),k為每個節點進入Queue的次數,且k一般<=2,但此處的複雜度證明是有問題的,其實SPFA的最壞情況應該是O(VE).Floyd:每對節點之間的最短路徑。
先給出結論:(1)當權值為非負時,用Dijkstra。(2)當權值有負值,且沒有負圈,則用SPFA,SPFA能檢測負圈,但是不能輸出負圈。(3)當權值有負值,而且可能存在負圈,則用BellmanFord,能夠檢測並輸出負圈。(4)SPFA檢測負環:當存在一個點入隊大於等於V次,則有負環,後面有證明。
本文針對SPFA演算法進行分析。
本文解決問題有:(1)證明SPFA演算法最壞複雜度。(2)為什麼存在一個點進入隊列V次,就說圖有負環。
SPFA是
西安交通大學的段凡丁在1994年與《西安交通大學學報》中發表的“關於最短路徑的SPFA快速演算法”,他在裡面說SPFA速度比Dijkstra快,且運行V次的SPFA速度比Floyd速度快,當時我就產生了疑惑:為什麼他這麼快,在一些經典的書籍中都沒有出現過,也沒被提及過。事實證明SPFA演算法是有局限的,他不適用於稠密圖,對於特別情況的稠密圖,SPFA複雜度和BellmanFord時間一樣。
最優時間複雜度先不看。下面來證明SPFA
最壞時間複雜度:
思路:(1)找出SPFA的最最壞到不可能的情況的複雜度為O(VE)。(2)找出SPFA確實有圖,使得跑SPFA的複雜度為O(VE)。
我原本想舉一個例子來說明SPFA存在O(VE)的情況,但是確實,最壞情況複雜度是不能用舉例說明的,謝謝TianMingBu老師的指出。
證明如果有負環若且唯若存在一個點入隊列次數大於等於V次。
對於某個點v,我們已知s到v的鬆弛路徑的邊的數量最多為V-1。我這裡說的鬆弛路徑指的是:比如s直接鬆弛v,這樣就有一條鬆弛路徑:s->v 。s鬆弛a,a鬆弛v,則s->a->v就是一條鬆弛路徑。
對於所有s到v的鬆弛路徑來說,當鬆弛路徑邊的數量相等時,v只入隊一次。比如有鬆弛路徑:s->a->x->vs->b->x->vs->c->z->v,可以看出v只入隊一次。因為s到v的鬆弛路徑的長度最多可以有V-1種變化,所以v最多入隊V-1次。
舉個例子:
假設有一個圖,點集為{s,a,b,c,v},則最多可能的鬆弛路徑有:s->vs->a->vs->b->vs->c->vs->a->b->vs->a->c->vs->b->c->vs->a->b->c->v
則鬆弛路徑的邊數變化有1,2,3,4,所以v入隊為4次,即V-1次。
所以我們可以說每個點最多入隊V-1次,因此我們求最壞情況為每個點都入隊V-1次,所以此時:
這裡舉個最壞情況的例子。
當然我們可能考慮,當給定一個V的值,E的值,比如E=2V,怎麼給出一個圖,使得對此圖運行SPFA演算法的複雜度為O(VE).我們這裡假定圖是連通的,所以E>=V-1。
方法如下:(1)我們首先將圖組成一個鏈,即如所示:
這樣就用去了V-1條邊。(2)分別添加v0連向v2,v3,....vk的邊,我們要添加的這些邊的權值要滿足v0先更新vk,v0更新vk-1後vk-1還能更新vk,以此類推,如所示:
(3)以vk,vk-1,.....v1的順序添加權值為正無窮的自環,且不斷迴圈,這個步驟是為了保持v1到vk點的出度保持一致,所以這樣做,如:
這樣我們就構造了一個能夠讓SPFA跑出O(VE)的圖了,原因如下:
因為我們E的值和V的值是不確定的,所以很有可能不能夠完成上述的這些構造,我們會分析當沒有剩餘的邊構造上面的步驟(2)時的複雜度(也就是說E<=2V-3,因為第一步連成一個鏈需要V-1條邊,而第二步v0連出去的邊需要V-2),和有足夠的邊能構造上面的圖這兩種情況。
(1)如果E<=2V-3
因為E<=2V-3,所以E=O(V),所以只要能夠求出複雜度是O(V^2),即可說為O(VE).我們要計算所有點的入隊次數和訪問的邊數。
V0出度為 E-V+2,V0入隊1次。V1出度為1,V1入隊為1次。V2出度為1,V2入隊為2次(分別為v0鬆弛v2,v1鬆弛v2)。V3出度為1,V3入隊為3次。....V(e-v+2)出度為1,入隊次數為(E-V+2)次。後面V(e-v+3),V(e-v+4),.....V(k-1)的出度為1,入隊次數為E-V+2次。 這些點的個數為V-(E-V+3)-1 = 2V-E-4。vk出度為0,vk入隊為E-V+2次。
所以總共的訪問的邊數為:
(2)如果E>2V-3
此時構造圖的第二步已經完畢,所以後面剩餘的邊只需要不斷添加自環保持出度平衡即可。
V0出度為V-1,入隊1次。V1到Vk出度為(E-V+2)/(V-1) 或(E-V+2)/(V-1)+1。v1到vk的入隊次數分別為1,2,3,.....V-1。
所以總共訪問邊數為:
package C24;import java.util.Iterator;import java.util.LinkedList;import java.util.List;import C22.GraphFactory;import C22.Pair;import C22.Weighted_Adjacent_List;public class SPFA {public int[] spfa(Weighted_Adjacent_List G,String s){return spfa(G,G.getVertexIndex(s));}public int[] spfa(Weighted_Adjacent_List G,int s){//1.建立所要的資料結構int size = G.getSize();int d[] = new int[size];//距離估計for(int i=0;i<d.length;i++){d[i] = Integer.MAX_VALUE;}List<Integer> Q = new LinkedList<Integer>();boolean is_in_queue[] = new boolean[size];//是否在隊列中for(int i=0;i<is_in_queue.length;i++){is_in_queue[i] = false;}//2.初始化d[s] = 0;Q.add(s);is_in_queue[s] = true;//3.核心while(!Q.isEmpty()){int u = Q.remove(0);is_in_queue[u] = false;List<Pair> list = G.getListByVertexIndex(u);Iterator<Pair> iter = list.iterator();while(iter.hasNext()){Pair vstr = iter.next();int v = G.getVertexIndex(vstr.end);if(d[v]>d[u]+vstr.weight){d[v] = d[u] + vstr.weight;if(!is_in_queue[v]){//如果鬆弛的點不在隊列中,則排入佇列;如果在隊列中,則不動Q.add(v);is_in_queue[v] = true;}}}}return d;}public static void main(String[] args) throws Exception {SPFA spfa_alg = new SPFA();Weighted_Adjacent_List g = GraphFactory.getWeightedAdjacentListInstance("input\\weighted_graph.txt");int[] d = spfa_alg.spfa(g,"s");for(int i=0;i<d.length;i++){System.out.println(g.getVertexValue(i)+":"+d[i]);}}}