程式碼隨想錄演算法訓練營第64天 | 圖論:Floyd 演算法+A * 演算法

哆啦**發表於2024-08-08

97.小明逛公園
https://kamacoder.com/problempage.php?pid=1155
Floyd 演算法精講
https://www.programmercarl.com/kamacoder/0097.小明逛公園.html#floyd-演算法精講

Floyd 演算法精講

  • 問題總結:雙向道路;路徑規劃;多個起點到多個終點

  • 核心思想:動態規劃

    • 確定dp陣列和下標含義:grid[i][j][k] = m 含義:節點i到節點j以【1...k】集合為中間節點的最短距離為m k是集合
    • 確定遞推公式:
      1. 節點i->節點j最短路徑經過節點k:
        grid[i][j][k] = grid[i][k][k-1]+grid[k][j][k-1]
      2. 節點i->節點j最短路徑不經過節點k
        grid[i][j][k] = grid[i][j][k-1]
      3. 最終遞推式為:
        grid[i][j][k] = min(grid[i][k][k-1]+grid[k][j][k-1],grid[i][j][k-1])
    • dp陣列初始化
      grid[i][j][0]:節點0無意義;從節點0開始初始化;

    image

    • 確定遍歷順序:從底層一層一層遍歷上去;
    • 推導dp陣列
    點選檢視程式碼
    def floyd(n,grid):
    	for k in range(1,n+1):
    		for i in range(1,n+1):
    			for j in range(1,n+1):
    				grid[i][j][k] = min(grid[i][k][k-1]+grid[k][j][k-1],grid[i][j][k-1])
    	return grid
    def main():
    	max_int = 10005
    
    	n,m = map(int,input().split())
    	grid = [[[max_int]*(n+1) for _ in range(n+1)] for _ in range(n+1)]
    
    	for _ in range(m):
    		p1,p2,w = map(int,input().split())
    		grid[p1][p2][0] = w
    		grid[p2][p1][0] = w
    
    	grid = floyd(n,grid)
    
    	q = int(input())
    	for _ in range(q):
    		start,end = map(int,input().split())
    		if grid[start][end][n]==max_int:
    			print(-1)
    		else:
    			print(grid[start][end][n])
    
    if __name__ == '__main__':
    	main()
    

Astar演算法

  • 其實是BFS的變形;

  • BFS和Astar演算法的動畫如圖:https://kamacoder.com/tools/knight.html

  • Astar遍歷:
    image

  • BFS遍歷:
    image

  • BFS 是沒有目的性的 一圈一圈去搜尋, 而 A * 是有方向性的去搜尋。

  • 其關鍵在於 啟發式函式。

  • 每個節點的權值為F,給出公式為:F = G + H

    • G:起點達到目前遍歷節點的距離
    • F:目前遍歷的節點到達終點的距離
  • 本題的圖是無權網格狀,在計算兩點距離通常有如下三種計算方式:

    • 曼哈頓距離,計算方式: d = abs(x1-x2)+abs(y1-y2)
    • 歐氏距離(尤拉距離) ,計算方式:d = sqrt( (x1-x2)^2 + (y1-y2)^2
    • 切比雪夫距離,計算方式:d = max(abs(x1 - x2), abs(y1 - y2))
  • 計算出來 F 之後,按照 F 的 大小,來選去出佇列的節點。可以使用 優先順序佇列 幫我們排好序,每次出佇列,就是F最小的節點。

  • 多個目標找最近目標(特別是潛在目標數量很多的時候),可以考慮 Dijkstra ,BFS 或者 Floyd

    點選檢視程式碼
    import heapq
    import math
    
    def heuristic(x1, y1, x2, y2):
    	# 歐式距離作為啟發式函式
    	return math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)
    
    def a_star(a1,a2,b1,b2):
    	# 定義騎士的8個移動方向
    	directions = [(-2, -1), (-1, -2), (1, -2), (2, -1),
    			  (2, 1), (1, 2), (-1, 2), (-2, 1)]
    	pq = []
    	heapq.heappush(pq,(0,0,a1,a2))
    
    	visited = set()
    	while pq:
    		total_cost,curr_cost,x,y = heapq.heappop(pq)
    
    		if (x,y) == (b1,b2):
    			return curr_cost
    		if (x,y) in visited:
    			continue
    		visited.add((x,y))
    
    		for dx,dy in directions:
    			nx,ny = x+dx,y+dy
    			if 1<=nx<=1000 and 1<=ny<=1000 and (nx,ny) not in visited:
    				new_cost  = curr_cost+1
    				total_cost = new_cost+heuristic(nx,ny,b1,b2)
    				heapq.heappush(pq,(total_cost,new_cost,nx,ny))
    	return -1
    
    def main():
    	# 定義騎士的8個移動方向
    	directions = [(-2, -1), (-1, -2), (1, -2), (2, -1),
    			  (2, 1), (1, 2), (-1, 2), (-2, 1)]
    
    	n = int(input())
    	res = []
    	for _ in range(n):
    		a1,a2,b1,b2 = map(int,input().split())
    		res_i = a_star(a1,a2,b1,b2)
    		res.append(res_i)
    	for res_i in res:
    		print(res_i)
    if __name__ == '__main__':
    	main()
    

路徑規劃演算法總結

演算法 源點數 邊的權值是否為負數 檢測負權迴路 有限節點最短路徑 思路
dijkstra樸素版 單源 不可以 不可以 不可以 遍歷未被訪問的節點更新mindist
dijkstra堆最佳化版 單源 不可以 不可以 不可以 用堆疊最佳化
Bellman_ford 單源 可以 可以 可以 遍歷邊更新mindist
SPFA 單源 可以 可以 可以 只最佳化當前節點相連的點
Floyd 多源 可以 可以 不可以 三維動態最佳化

圖演算法總結

  1. 深搜與廣搜:遍歷;
  2. 並查集:判斷是否相連;
  3. 最小生成樹;
  4. 拓撲排序;
  5. 最短路徑演算法;

相關文章