floyd算法用於求圖中各個點到其它點的最短路徑,無論其中經過多少個中間點。該算法的核心理念是基於動態規划,
不斷更新最短距離,遍歷所有的點。
知識基礎:圖的鄰接矩陣表示:

如圖是一個簡單圖,從A開始,按照ABCDEFG的順序來制定一個方陣,該方陣每一行代表一個點到所有點的直達距離,
到它本身的距離是0,如果兩點之間沒有直接相連(非鄰接)的,那么這兩點的距離就定位無窮或者-1,例如圖中的A點到
其它所有點的距離為 0 7 ∞ 5 ∞ ∞ ∞ 按照ABCDEFG的順序排列,方陣的每一行從上到下按照ABCDEFG的順
序排列出各點到各點的距離,這樣的方陣就叫做圖的鄰接矩陣,例如該圖的鄰接矩陣data為:

由於該圖是無向圖,所以它的鄰接矩陣是先對角線對稱的。如果是有向圖的話則要根據邊的方向來確定點與點間的距離。
編程中,我們一般用二維數組表示鄰接矩陣。
算法核心:遍歷圖中的每一個點,通過該點的入讀和出度來計算以該點作為中間點連接另外兩點的距離,來與原來的距
離作比較,存最小的值,不斷刷新。例如以k為中間點,計算i到j的最短距離,則比較data[i][j] 和data[i][k]+data[k][j]的
大小,如果后者更小,則刷新數組,令data[i][j] = data[i][k]+data[k][j]。由於k值是不斷變化的,所以遍歷完整個數
組,data[i][j]所存的值就是i到j的最小值。
需要注意的是,由i到j中間可能會經過多個點,所以我們要理解data[i][k]也並非表示i到k的直達距離,一開始data[i][k]
確實是i到k的直達距離,但是隨着數組data的不斷刷新,點到點的距離不再單是直達距離而是經過0個或多個點的最短距
離。所以data[i][k]中也可能經過多個點。而data[k][j]表示從k到j的直達距離因為后面的距離還沒刷新(遍歷數組是從上到
下,從左到右)。
記錄路徑 :定義一個二維數組path來記錄各點到各點所經過的中間點,如果兩點之間沒有中間點的話就以它的起點作為
中間點,這樣做的好處是能夠通過反推找到完整的路徑
代碼:
f = float('inf') # float('inf')表示無窮大
# 准備數據
data = [
[0, 7, f, 5, f, f, f],
[7, 0, 8, 9, 7, f, f],
[f, 8, 0, f, 5, f, f],
[5, 9, f, 0, 15, 6, f],
[f, 7, 5, 15, 0, 8, 9],
[f, f, f, 6, 8, 0, 11],
[f, f, f, f, 9, 11, 0],
]
path = [[i] * 7 for i in range(7)]
for k in range(7):
for i in range(7):
for j in range(7):
if data[i][j] > data[i][k] + data[k][j]: # 比較距離的大小
data[i][j] = data[i][k] + data[k][j] # 每次都存最小的值
path[i][j] = k # 記錄中間點
# 定義函數找出x到y的具體路徑
def show_trace(x,y):
trace = []
def add_trace(x, y):
global mm
if x != y:
add_trace(x, path[x][y])
return trace.append(y)
add_trace(x,y)
trace_str = str(trace)
trace_str = trace_str.replace(',','-->')
print(f"從 {x} 到 {y} 的最短路徑為: {trace_str}")
for i in data:
print(i)
show_trace(0,4) # 求A到E的最短路徑
show_trace(0,6) # 求A到G的最短路徑
#[0, 7, 15, 5, 14, 11, 22]
#[7, 0, 8, 9, 7, 15, 16]
#[15, 8, 0, 17, 5, 13, 14]
#[5, 9, 17, 0, 14, 6, 17]
#[14, 7, 5, 14, 0, 8, 9]
#[11, 15, 13, 6, 8, 0, 11]
#[22, 16, 14, 17, 9, 11, 0]
#從 0 到 4 的最短路徑為: [0--> 1--> 4]
#從 0 到 6 的最短路徑為: [0--> 3--> 5--> 6]
接下再用2021藍橋杯pythonA組的題目來深入理解
【問題描述】
小藍學習了最短路徑之后特別高興,他定義了一個特別的圖,希望找到圖中的最短路徑。
小藍的圖由2021個結點組成,依次編號1至2021
對於兩個不同的結點a,b,如果a和b的差的絕對值大於21,則兩個結點之間沒有邊相連;如果a和b的差的絕對值小於等於21,則兩個點之間有一條長度為a和b的最小公倍數的無向邊相連。
例如:結點1和結點23之間沒有邊相連;結點3和結點24之間有一條無向邊,長度為24;結點15和結點25之間有一條無向邊,長度為75.
請計算,結點1和結點2021之間的最短路徑長度是多少。
題目分析:該題點與點之間是否直連受到二者差值的約束,線段的距離也是通過計算才能得出,因為是求1到2021的最短距離,所以
只需要1行的矩陣來記錄1點到其它所有點的最短距離,同樣的,1到2021的通過的中間點也只需要一行矩陣來存儲。因此上面代碼的
循環在這里可以減少一層。
解題代碼:
def fn(x, y):
x1, y1 = x, y
while y1 != 0:
x1, y1 = y1, x1 % y1
return x*y//x1
n = 2021
# 由於一開始不知道1到各點的距離所以全設為無窮大,反正后面會計算求出
data = [float('inf')]*(n+1) # 這里數組向前進一位是為了后面計算最小公倍數更方便
path = [1]*(n+1) #相應地路徑數組也要進一位
data[1] = 0 # 1到1的距離是0
for i in range(1, n+1):
for j in range(i+1, i+22):
if j > 2021:
break
if data[j] > fn(i,j) + data[i]:
data[j] = fn(i,j) + data[i]
path[j] = i # 記錄中間點
# 顯示路徑
def show_trace(y):
trace = []
def add_trace(y):
global mm
if 1 != y:
add_trace(path[y])
return trace.append(y)
add_trace(y)
trace_str = str(trace)
trace_str = trace_str.replace(',','-->')
print(f"從 1 到 {y} 的最短路徑為: {trace_str}")
print(data[n])
# 10266837
show_trace(25)# 路徑太長了這里就不展示了
