棋盘最短路径 python_Dijkstra 最短路径算法 Python 实现
Dijkstra 最短路徑算法 Python 實現(xiàn)
問題描述
使用 Dijkstra 算法求圖中的任意頂點到其它頂點的最短路徑(求出需要經(jīng)過那些點以及最短距離)。
以下圖為例:
算法思想
可以使用二維數(shù)組來存儲頂點之間邊的關(guān)系
首先需要用一個一維數(shù)組 dis 來存儲 初始頂點到其余各個頂點的初始路程,以求 1 頂點到其它各個頂點為例:
將此時 dis 數(shù)組中的值稱為最短路的“估計值”。
既然是求 1 號頂點到其余各個頂點的最短路程,那就先找一個離 1 號頂點最近的頂點。通過數(shù)組 dis 可知當(dāng)前離 1 號頂點最近是 2 號頂點。當(dāng)選擇了 2 號頂點后,dis[2] 的值就已經(jīng)從“估計值”變?yōu)榱恕按_定值”,即 1 號頂點到 2 號頂點的最短路程就是當(dāng)前 dis[2]值。為什么呢?因為目前離 1 號頂點最近的是 2 號頂點,并且這個圖所有的邊都是正數(shù),那么肯定不可能通過第三個頂點中轉(zhuǎn),使得 1 號頂點到 2 號頂點的路程進一步縮短了。
既然選了 2 號頂點,接下來再來看 2 號頂點有哪些出邊。有 2->3 和 2->4 這兩條邊。先討論通過 2->3 這條邊能否讓 1 號頂點到 3 號頂點的路程變短。也就是說現(xiàn)在比較 dis[3] 和 dis[2] + G[2][3]的大小。其中 dis[3] 表示 1 號頂點到 3 號頂點的路程。dis[2] + G[2][3] 中 dis[2] 表示 1 號頂點到 2 號頂點的路程,G[2][3] 表示 2->3 這條邊。所以 dis[2] + G[2][3] 就表示從 1 號頂點先到 2 號頂點,再通過 2->3 這條邊,到達 3 號頂點的路程。
在本例中 dis[3] = 12,dis[2] + G[2][3] = 1 + 9 = 10,dis[3] > dis[2] + G[2][3],所以 dis[3] 要更新為 10。這個過程有個專業(yè)術(shù)語叫做“松弛”。即 1 號頂點到 3 號頂點的路程即 dis[3],通過 2->3 這條邊松弛成功。這是 Dijkstra 算法的主要思想:通過“邊”來松弛初始頂點到其余各個頂點的路程。
同理通過 2->4(G[2][4]),可以將 dis[4]的值從 ∞ 松弛為 4(dis[4] 初始為 ∞,dis[2] + G[2][4] = 1 + 3 = 4,dis[4] > dis[2] + G[2][4],所以 dis[4] 要更新為 4)。
剛才對 2 號頂點所有的出邊進行了松弛。松弛完畢之后 dis 數(shù)組為:
接下來,繼續(xù)在剩下的 3、4、5 和 6 號頂點中,選出離 1 號頂點最近的頂點。通過上面更新過 dis 數(shù)組,當(dāng)前離 1 號頂點最近是 4 號頂點。此時,dis[4] 的值已經(jīng)從“估計值”變?yōu)榱恕按_定值”。下面繼續(xù)對 4 號頂點的所有出邊(4->3,4->5 和 4->6)用剛才的方法進行松弛。松弛完畢之后 dis 數(shù)組為:
繼續(xù)在剩下的 3、5 和 6 號頂點中,選出離 1 號頂點最近的頂點,這次選擇 3 號頂點。此時,dis[3] 的值已經(jīng)從“估計值”變?yōu)榱恕按_定值”。對 3 號頂點的所有出邊(3->5)進行松弛。松弛完畢之后 dis 數(shù)組為:
繼續(xù)在剩下的 5 和 6 號頂點中,選出離 1 號頂點最近的頂點,這次選擇 5 號頂點。此時,dis[5] 的值已經(jīng)從“估計值”變?yōu)榱恕按_定值”。對5號頂點的所有出邊(5->4)進行松弛。松弛完畢之后 dis 數(shù)組為:
最后對 6 號頂點所有點出邊進行松弛。因為這個例子中 6 號頂點沒有出邊,因此不用處理。到此,dis 數(shù)組中所有的值都已經(jīng)從“估計值”變?yōu)榱恕按_定值”。
最終 dis 數(shù)組如下,這便是 1 號頂點到其余各個頂點的最短路徑。
總結(jié)一下剛才的算法。算法的基本思想是:每次找到離源點(上面例子的源點就是 1 號頂點)最近的一個頂點,然后以該頂點為中心進行擴展,最終得到源點到其余所有點的最短路徑。基本步驟如下:將所有的頂點分為兩部分:已知最短路程的頂點集合 P 和未知最短路徑的頂點集合 Q。最開始,已知最短路徑的頂點集合 P 中只有源點一個頂點。這里用一個 visited[ i ]數(shù)組來記錄哪些點在集合 P 中。例如對于某個頂點 i,如果 visited[ i ]為 1 則表示這個頂點在集合 P 中,如果 visited[ i ]為 0 則表示這個頂點在集合 Q 中;
設(shè)置源點 s 到自己的最短路徑為 0 即 dis = 0。若存在源點有能直接到達的頂點 i,則把 dis[ i ]設(shè)為 G[s][ i ]。同時把所有其它(源點不能直接到達的)頂點的最短路徑為設(shè)為 ∞;
在集合 Q 的所有頂點中選擇一個離源點 s 最近的頂點 u(即 dis[u] 最小)加入到集合 P。并考察所有以點 u 為起點的邊,對每一條邊進行松弛操作。例如存在一條從 u 到 v 的邊,那么可以通過將邊 u->v 添加到尾部來拓展一條從 s 到 v 的路徑,這條路徑的長度是 dis[u] + G[u][v]。如果這個值比目前已知的 dis[v] 的值要小,我們可以用新值來替代當(dāng)前 dis[v] 中的值;
重復(fù)第 3 步,如果集合 Q 為空,算法結(jié)束。最終 dis 數(shù)組中的值就是源點到所有頂點的最短路徑
Dijkstra 算法不能應(yīng)用于有負權(quán)重的圖
Dijkstra 時間復(fù)雜度為 O(N2)
Python 實現(xiàn)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50def (G, start):
start = start - 1
inf = float('inf')
node_num = len(G)
visited = [0] * node_num
dis = {node: G[start][node] for node in range(node_num)}
parents = {node: -1 for node in range(node_num)}
# 起始點加入進 visited 數(shù)組
visited[start] = 1
# 最開始的上一個頂點為初始頂點
last_point = start
for i in range(node_num - 1):
# 求出 dis 中未加入 visited 數(shù)組的最短距離和頂點
min_dis = inf
for j in range(node_num):
if visited[j] == 0 and dis[j] < min_dis:
min_dis = dis[j]
# 把該頂點做為下次遍歷的上一個頂點
last_point = j
# 最短頂點假加入 visited 數(shù)組
visited[last_point] = 1
# 對首次循環(huán)做特殊處理,不然在首次循環(huán)時會沒法求出該點的上一個頂點
if i == 0:
parents[last_point] = start + 1
for k in range(node_num):
if G[last_point][k] < inf and dis[k] > dis[last_point] + G[last_point][k]:
# 如果有更短的路徑,更新 dis 和 記錄 parents
dis[k] = dis[last_point] + G[last_point][k]
parents[k] = last_point + 1
# 因為從 0 開始,最后把頂點都加 1
return {key + 1: values for key, values in dis.items()}, {key + 1: values for key, values in parents.items()}
if __name__ == '__main__':
inf = float('inf')
G = [[0, 1, 12, inf, inf, inf],
[inf, 0, 9, 3, inf, inf],
[inf, inf, 0, inf, 5, inf],
[inf, inf, 4, 0, 13, 15],
[inf, inf, inf, inf, 0, 4],
[inf, inf, inf, inf, inf, 0]]
dis, parents = Dijkstra(G, 1)
print("dis: ", dis)
print("parents: ", parents)
輸出為1
2dis: {1: 0, 2: 1, 3: 8, 4: 4, 5: 13, 6: 17}
parents: {1: -1, 2: 1, 3: 4, 4: 2, 5: 3, 6: 5}
如果求 1 號頂點到 6 號頂點的最短距離,dis[6] = 17,所以最短距離為 17。
再看 parents[6] = 5,說明 6 號頂點的上一個頂點為 5,parents[5] = 3,說明 5 號頂點的上一個頂點為 3,以此類推,最終 1 號頂點到 6 號頂點的路徑為 1->2->4->3->5->6。
優(yōu)化思路其中每次找到離 1 號頂點最近的頂點的時間復(fù)雜度是 O(N),可以用“堆”來優(yōu)化,使得這一部分的時間復(fù)雜度降低到 O(logN);
另外對于邊數(shù) M 少于 N2 的稀疏圖來說(把 M 遠小于 N2 的圖稱為稀疏圖,而 M 相對較大的圖稱為稠密圖),可以用鄰接表來代替鄰接矩陣,使得整個時間復(fù)雜度優(yōu)化到 O((M+N)logN)。注意,在最壞的情況下 M 就是 N2,這樣的話 MlogN 要比 N2 還要大。但是大多數(shù)情況下并不會有那么多邊,所以 (M+N)logN 要比 N2 小很多
總結(jié)
以上是生活随笔為你收集整理的棋盘最短路径 python_Dijkstra 最短路径算法 Python 实现的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 什么是独家银行贷款
- 下一篇: eclipse配置mysql教程_在Ec