Bootstrap

代码随想录算法训练营第六十二天 | Floyd 算法、A star算法、最短路算法总结篇、图论总结

一、Floyd 算法

题目连接:97. 小明逛公园 (kamacoder.com)
文章讲解:代码随想录 (programmercarl.com)——Floyd 算法

Carl说:Floyd 算法代码很简单,但真正理解起原理 还是需要花点功夫,大家在看代码的时候,会发现 Floyd 的代码很简单,甚至看一眼就背下来了,但我为了讲清楚原理,本篇还是花了大篇幅来讲解。

思路

本题为多源最短路径问题,即 求多个起点到多个终点的多条最短路径。

Floyd算法

应用范围:对边的权重没有要求,正负均可。

核心思想:动态规划。

动归五部曲:

1. 确定 dp 数组及下标含义:grid[ i ][ j ][ k ] = m,表示节点 i 到 节点 j 以[1...k] 集合为中间节点的最短距离为 m 。
2. 确定递推公式:
case1. 节点 i 到 节点 j 的最短路径经过节点 k。grid[i][j][k] = grid[i][k][k - 1] + grid[k][j][k - 1],经过 k ,两段路都不包含节点 k ,所以中间节点集合为[1, k - 1]
case2. 节点 i 到 节点 k 的最短路径不经过节点 k 。grid[i][j][k] = grid[i][j][k - 1],不经过节点 k ,中间节点集合为 [1, k - 1]
求最短路径,两种情况取最小值,grid[i][j][k] = min(grid[i][k][k - 1] + grid[k][j][k - 1], grid[i][j][k - 1])
3. 确定dp数组如何初始化:
for _ in range(m):
        p1, p2, val = map(int, input().split())
        # 注意这里是双向图
        grid[p1][p2][0] = val
        grid[p2][p1][0] = val
4. 确定遍历顺序:把 i 和 j 看作三维左边的平层,k 看作垂直向上的,遍历时是从底向上一层一层遍历,所以 k 是最外层循环,i 和 j 的先后顺序无所谓。
5. 举例推导dp数组。

def floyd(n, edges):
    # 初始化三维数组
    inf = 10005
    grid = [[[inf] * (n + 1) for _ in range(n + 1)]for _ in range(n + 1)]
    
    # 读取边的信息,初始 dp 数组
    for p1, p2, val in edges:
        # 注意,为双向图
        grid[p1][p2][0] = val
        grid[p2][p1][0] = val
        
    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
    

if __name__ == '__main__':
    n, m = map(int, input().split())
    edges = []
    
    for _ in range(m):
        p1, p2, val = map(int, input().split())
        edges.append((p1, p2, val))
        
    grid = floyd(n, edges)
    
    # q 个观景计划
    q = int(input())
    for _ in range(q):
        # 读取每个计划的起点和终点
        start, end = map(int, input().split())
        if grid[start][end][n] == 10005:
            print(-1)
        else:
            print(grid[start][end][n])

二、A star算法

题目连接:127. 骑士的攻击 (kamacoder.com)
文章讲解:代码随想录 (programmercarl.com)——A star算法

Carl说:一般 笔试或者 面试的时候,不会考察A*, 都是会结合具体业务场景问 A*算法,例如:地图导航,游戏开发 等等。

import heapq
import sys

# 定义方向
dir = [(-2, -1), (-2, 1), (-1, 2), (1, 2), (2, 1), (2, -1), (1, -2), (-1, -2)]

# A* 格子
moves = [[0] * 1001 for _ in range(1001)]

# 计算启发式函数:欧氏距离的平方
def heuristic(x, y, target_x, target_y):
    return (x - target_x) ** 2 + (y - target_y) ** 2
    
# A* 算法
def a_star(start_x, start_y, target_x, target_y):
    # 将 start 定义为 (g, h, f, x, y)
    open_list = []
    heapq.heappush(open_list, (0, 0, heuristic(start_x, start_y, target_x, target_y), start_x, start_y))
    
    while open_list:
        g, _, h, x, y = heapq.heappop(open_list)
        
        # 达到目标
        if x == target_x and y == target_y:
            return moves[x][y]
            
        for dx, dy in dir:
            next_x = x + dx
            next_y = y + dy
            
            # 检查边界
            if next_x < 1 or next_x > 1000 or next_y < 1 or next_y > 1000:
                continue
            
            # 如果没访问过
            if moves[next_x][next_y] == 0:
                # 更新步数
                moves[next_x][next_y] = g + 1
                
                # 计算 F 值
                
                next_g = g + 5                   # 每次移动消耗 5
                next_h = heuristic(next_x, next_y, target_x, target_y)
                heapq.heappush(open_list, (next_g, next_h, next_h, next_x, next_y))
                
    return -1
    
if __name__ == '__main__':
    n = int(input())
    for _ in range(n):
        a1, a2, b1, b2 = map(int, input().split())
        
        # 初始化 moves 数组
        for i in range(1001):
            for j in range(1001):
                moves[i][j] = -1
                
        # 调用 A* 算法
        result = a_star(a1, a2, b1, b2)
        
        print(result)

三、最短路算法总结篇

文章连接:代码随想录 (programmercarl.com)——最短路算法总结篇​​​​​​

单源且边为正数,用 dijkstra,一般情况下可以直接用 堆优化版本。

单源且边可为负数,用 Bellman-Ford,一般情况下可以直接用 spfa。

有负权回路,优先 Bellman-Ford。

多源点求最短路径,用 floyd。

四、图论总结

文章链接:代码随想录 (programmercarl.com)——图论总结篇

;