by:
潘雲登
對於商業目的下對本文的任何行為需經作者同意。
寫在前面
1.本文內容對應《演算法導論》(第
2版)》第
24章。 2.
主要介紹了兩種求解單源最短路徑的演算法:
bellman-ford
演算法和dijkstra
演算法。 3.
最短路徑問題
在最短路徑問題中,給出的是乙個帶權有向圖
g=(v, e)
,加權函式w:
e->r
為從邊到實型權值的對映。路徑
p=0, v1, …, vk>
的權是指其組成邊的所有權值之和
w(p)
。可以定義從u到
v間的最短路徑的權為δ
(u, v)=min
。如果從u到
v不可達,則δ
(u, v)
為∞。一條最短路徑既不能包含負權迴路,也不會包含正權迴路。由於最短路徑問題具有最優子結構性質,即最短路徑的子路徑是最短路徑,因此可以利用
動態規劃或貪心演算法
的思想進行求解。單源最短路徑問題,在給定圖
g=(v, e)
的情況下,希望找出從某個給定源頂點
s∈v到每個頂點v∈v的最短路徑。
通常不僅希望算出最短路徑的權,而且希望得到最短路徑上的頂點。因此,可以對每個頂點v,設定其前趨頂點π[v],以便使源於頂點v的前趨鍊錶沿著從s到v的最短路徑的相反方向排列。由π的值匯出的前趨子圖gπ
=(vπ
, eπ
)定義為,vπ
=∪,即頂點集vπ
為g中所有具有非空前趨的頂點集合加上源點s;且eπ=
},即有向邊集eπ是v
π中的頂點的π值匯出的邊集。在單源最短路徑問題得解後,gπ
就是最短路徑樹,包含了從源點s到
s可達的每個頂點之間的一條最短路徑。
鬆弛操作
對每個頂點v∈
v,可以設定乙個屬性
d[v]
,用來描述從源點s到
v的最短路徑上權值的上界,稱為最短路徑估計。在初始化時,對所有v∈
v,有π[v]=null,對v∈
v-,有
d[s]=0
以及d[v]=
∞。在鬆弛一條邊
(u, v)
的過程中,要測試是否可以通過
u,對迄今找到的道
v的最短路徑進行改進;如果可以改進的話,則更新
d[v]
和前趨π[v]。鬆弛操作是單源最短路徑演算法中改變最短路徑和前趨的唯一方式。
void initalize_single_source(adjlist *graphic, int s)
graphic->distance[s] = 0;
}void relax(adjlist *graphic, int u, int v, int weight)
}
bellman-ford
演算法
bellman-ford
演算法能在存在負權邊的情況下,解決單源最短路徑問題,並且可以返回乙個布林值,表明圖中是否存在乙個從源點可達的負權迴路,這是它優於
dijkstra
演算法的地方。
bellman-ford
演算法:首先,對各個頂點的最短路徑估計
d和前趨
π進行初始化;然後,執行|v|-1次迴圈,每次迴圈中,利用鬆弛操作對所有邊的端點的
最短路徑估計
d和前趨
π進行更新;最後,通過判斷各頂點的
最短路徑估計是否收斂,表明是否存在負權迴路。由於第二步的迴圈需要
(|v|-1)|e|
次鬆弛操作,
bellman-ford
演算法的總執行時間為
o(ve)。
在演算法開始前,最短路徑樹中僅包含源點
s。第一趟迴圈過後,與
s相鄰的結點被加入到最短路徑樹中。在所有頂點可達的情況下,至多需要
|v|-1
次迴圈,便可將所有頂點加入到樹中。由於後加入的頂點與其前趨結點之間可能存在負權邊,因此,已經存在於樹中的路徑仍然可能被更新。換句話說,在不存在負權迴路的情況下,路徑
(s, v)
至多需要
|v|-1
次鬆弛操作,即可收斂到最短路徑的權
d[v]=
δ(s, v)
。這是因為在
|v|-1
次迴圈後,不會再有新結點引入負權邊,導致路徑
(s, v)
被更新。這也是檢查負權迴路是否存在的依據。
int bellman_ford(adjlist *graphic, int s)}}
for(j=0; jvertex_number; j++)
}return 1;
}bellman-ford
演算法的不足在於,每次迴圈過程中存在許多冗餘的鬆弛操作。一種改進的
spfa(shortest path faster algorithm)
演算法基於以下觀察:只有那些在前一趟迴圈中更新了最短路徑估計的頂點,才可能引起它們的鄰接點的最短路徑估計的改變。因此,
spfa
演算法用乙個佇列來存放被成功鬆弛的頂點。初始時,源點
s入隊。當佇列不為空時,取出隊首頂點,
對它的鄰接點進行鬆弛。如果某個鄰接點鬆弛成功,且該鄰接點不在佇列中,則將其入隊。經過有限次的鬆弛操作後,佇列將為空,演算法結束。但是,只有在不存在負權迴路的時候,
spfa
演算法才能正常工作。
dijkstra
演算法
dijkstra
演算法比bellman-ford
演算法的執行時間要低,但它要求所有邊的權值非負。
dijkstra
演算法中設定了乙個頂點集合
s,從源點
s到集合中的頂點的最終最短路徑的權值均已確定。演算法反覆選擇具有最短路徑估計的頂點
u∈v-s,並將u加入s中,對u的所有出邊進行鬆弛。由於總是在v-s中選擇「最近」的頂點插入集合s中,可以說
dijkstra
演算法使用了貪心策略。可以想象,在
dijkstra
演算法的執行過程中,最短路徑估計沿著以
s為根的最短路徑樹向下傳播。由於不存在負權邊,最短路徑樹中的邊一旦確定就不再改變。可以使用最小堆構建的最小優先佇列(同
prim
演算法),儲存集合
v-s中的頂點。
dijkstra
演算法需要
|v|次執行時間為
o(lg v)
的heap_extract_min
操作和|e|
次執行時間為
o(lg v)
的heap_decrease_key
操作,因此,總的執行時間為
o((v+e) lg v)
,如果所有頂點都從源點可達的話,則為
o(e lg v)。
void dijkstra(adjlist *graphic, int s)
build_min_heap(&h);
while(h.heap_size > 0)
*/temp_lnode = graphic->vlist[temp_vhnode->index].link;
while(temp_lnode != null)
}free_heap(&h);
}
單源最短路徑演算法小結
這裡就不寫具體演算法了,只將他們的時間複雜度 適用範圍 複雜程度簡單做個比較 待搜尋的圖都指有向圖 無向圖類似 儲存方式均為鄰接表 一 廣度優先搜尋 bfs 時間複雜度 o v e 效率很高 適用範圍 很窄 僅適於無權邊的圖。即每條邊長度都為1的情況 複雜程度 一般,需佇列 二 bellman fo...
演算法導論筆記 單源最短路徑
本文所貼示的偽 均 演算法導論 本文只是對其中 單源最短路徑 章節的簡單總結,許多數學證明過程已忽略。最短路徑的定義 給定乙個圖g v,e 希望找到從給定源節點s v 到每個結點v v 的最短路徑。單源最短路徑可以用來解決許多其他問題,包括 1 單目的地最短路徑問題 找到從每個結點v到給定目的結點t...
單源最短路徑演算法
簡單介紹 最短路徑演算法是圖演算法中的經典演算法,是用於解決圖中某個頂點到另外乙個頂點所經過路徑的花銷最小 這裡的花銷可能是時間也可能指費用等 dijkstra是用於解決單源最短路徑的經典演算法。圖的儲存方式 鄰接矩陣 在鄰接矩陣中,要獲取某個結點的出度和入讀,是通過掃瞄結點所在臨界矩陣中的行列完成...