Bootstrap

代码随想录算法训练营第六十天 | dijkstra(堆优化版)、Bellman_ford 算法精讲

一、dijkstra(堆优化版)

题目连接:47. 参加科学大会(第六期模拟笔试) (kamacoder.com)
文章讲解:代码随想录 (programmercarl.com)——dijkstra(堆优化版)

思路依旧是dijkstra算法三部曲:
step1. 选择距离源节点近且未被访问过的节点;
           区别:直接遍历边,且通过堆对边进行排序,达到直接选择距离源节点最近的节点。
step2. 将该节点标记为访问过;
step3. 更新非访问节点到远点的距离,即更新 minDist 数组。

import heapq    # 用于实现优先队列
from collections import defaultdict

# 定义一个表示带权重的边的类
class Edge:
    def __init__(self, to, val):
        self.to = to        # 邻接顶点
        self.val = val      # 边的权重

def dijkstra(n, m, edges, start, end):
    # 创建邻接表,使用 defaultdict 存储边
    grid = defaultdict(list)
    
    
    # 填充邻接表,存储每条边的信息
    for p1, p2, val in edges:
        grid[p1].append(Edge(p2, val))
        
    # 存储从源点到每个节点的最短距离,初始化为无穷大
    minDist = [float('inf')] * (n + 1)
    minDist[start] = 0
    
    # 记录节点是否被访问过
    visited = [False] * (n + 1)
    
    # 优先队列,存储(节点,源点到该节点的距离)的元组
    pq = []
    # pq 是一个列表,作为优先队列使用
    # (0, start) 表示从 start 起始节点到源节点的距离为0
    heapq.heappush(pq, (0, start))
    
    while pq:
        # step1. 选择距离源节点近且未被访问过的节点
        # cur_node -> 当前节点
        # cur_dist -> 到当前节点的距离
        cur_dist, cur_node = heapq.heappop(pq)
        
        # 如果当前节点访问过,跳过
        if visited[cur_node]:
            continue
        
        # step2. 将该节点标记为访问过
        visited[cur_node] = True
        
        # step3. 更新非访问节点到远点的距离
        # 遍历当前节点所有的边
        for edge in grid[cur_node]:
            # 如果相邻节点没访问过
            if not visited[edge.to]:
                # 且 通过当前节点 cur_node 到达相邻节点 edge.to 的路径(当前到达源节点距离+道道相邻节点距离)是否比已知到达相邻节点距离(minDist[edge.to])最短路径更短
                if cur_dist + edge.val < minDist[edge.to]:
                    # 更新最短距离
                    minDist[edge.to] = cur_dist + edge.val
                    # 把(节点,源点到该节点的距离)
                    heapq.heappush(pq, (minDist[edge.to], edge.to))
    
    # 检查目标路径是否可以到到
    if minDist[end] == float('inf'):
        return -1
    else:
        return minDist[end]
        

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))
        
    start = 1
    end = n
    
    result = dijkstra(n, m, edges, start, end)
    print(result)

二、Bellman_ford 算法精讲

题目连接:94. 城市间货物运输 I (kamacoder.com)
文章讲解:代码随想录 (programmercarl.com)——Bellman_ford 算法

应用场景:解决带负权值的单源最短路径问题。

核心思想:对 所有边 进行松弛 n - 1 次操作(n 为节点数量),从而求得目标最短路。

松弛定义:如果 通过 A 到 B 这条边可以获得更短的到达B节点的路径,即如果 minDist[B] > minDist[A] + value,那么我们就更新 minDist[B] = minDist[A] + value ,这个过程就叫做 “松弛” 。代码:minDist[B] = min(minDist[A] + value, minDist[B])

Note:松弛 n - 1 次因为对所有边每松弛一次,相当于计算 起点 到达与起点用一条边相连的节点的最短距离,无论图什么样,松弛 n - 1 次一定可以得到 起点 到 所有点 的最短距离。

def bellman(n, m, edges, start, end):
    # 初始化最短距离数组
    minDist = [float('inf')] * (n + 1)
    minDist[start] = 0
    
    # 对所有边松弛 n - 1 次
    for _ in range (1, n):
        # 遍历所有边
        for from_node, to_node, val in edges:
            # 松弛操作
            if minDist[from_node] != float('inf'):
                minDist[to_node] = min(minDist[from_node] + val, minDist[to_node])
            
    # 检查是否可以到达终点
    if minDist[end] == float('inf'):
        return "unconnected"
    else:
        return minDist[end]
        

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))
        
    start = 1
    end = n
    
    result = bellman(n, m, edges, start, end)
    
    print(result)
;