算法-图论-最短路径

0. 不同的最短路径算法

img

1. dijkstra 朴素(卡码网 47)

题目条件

  • 不存在负权值的边

图的存储:邻接矩阵

dijkstra的基本思路

  1. 维护minDist[],表示各顶点到起点的最短距离;维护visited[],访问过的顶点不会重复访问。
  2. 对未访问过的visited[j] == FalseminDist[j]最小的顶点 进行访问
  3. 更新 j 加入后的minDist[]
# 起点为start,终点为end
# dijkstra能求单源到所有点的最短路
def dijkstra(num_node, grid, start, end):
    # 各点到起点1的距离
    minDist = [min(grid[start][i], float('inf')) for i in range(num_node+1)]
    visited = [False for _ in range(num_node+1)]
    
    # 初始化
    minDist[start] = 0
    visited[start] = True
    
    # 访问剩下的n-1个点
    for i in range(num_node-1):
        minVal = float('inf')
        cur = start
        
        for j in range(1, num_node+1):
            if visited[j] == False and minDist[j] < minVal:
                minVal = minDist[j]
                cur = j
        
        visited[cur] = True
        
        # 更新minDist[]
        for j in range(1, num_node+1):
            if visited[j] == False and minDist[cur] + grid[cur][j] < minDist[j]:
                minDist[j] = minDist[cur] + grid[cur][j]
    
    if minDist[end] == float('inf'):
        return -1
    else :
        return minDist[end]


def main():
    num_node, num_edge = map(int, input().split())
    grid = [[float('inf') for _ in range(num_node+1)] for _ in range(num_node+1)]
    for _ in range(num_edge):
        source, target, val = map(int, input().split())
        grid[source][target] = val

    result = dijkstra(num_node, grid, 1, num_node)
    print(result)

if __name__ == "__main__":
    main()

2. dijkstra 堆优化版 (卡码网 47)

图的存储:邻接表

import heapq

class Edge:
    def __init__(self, target, val):
        self.target = target
        self.val = val

# 堆优化版的dijkstra,适合稀疏图
def dijkstra(num_node, num_edge, grid):
    visited = [False for _ in range(num_node+1)]
    minDist = [float('inf') for _ in range(num_node+1)]

    # 使用小根堆维护的优先队列
    pq = []
    # 将item=(0,1)插入队列
    heapq.heappush(pq, (0, 1))
    minDist[1] = 0
    
    while pq:
        cur_dist, cur_node = heapq.heappop(pq)
        
        if visited[cur_node]:
            continue
    
        visited[cur_node] = True
        
        for edge in grid[cur_node]:
            if not visited[edge.target] and cur_dist + edge.val < minDist[edge.target]:
                minDist[edge.target] = cur_dist + edge.val
                heapq.heappush(pq, (minDist[edge.target], edge.target))


    if minDist[num_node] == float('inf'):
        return -1
    else :
        return minDist[num_node]


def main():
    num_node, num_edge = map(int, input().split())
    # 邻接表存储
    grid= [[] for _ in range(num_node+1)]
    
    for _ in range(num_edge):
        s, t, val = map(int, input().split())
        grid[s].append(Edge(t, val))
        
    
    result = dijkstra(num_node, num_edge, grid)
    print(result)

if __name__ == "__main__":
    main()

3. Bellman_ford 算法(卡码网 94)

题目条件

  • 存在负权值的边
  • 不存在负权回路

要求:求起点1到终点n的最短路径(可以为负值)。

图的存储:邻接表

算法思路

  • 因为不存在负回路,所以起点1到任何顶点的最短路径上的边数都小于等于n-1
  • 所以松弛n-1次必定收敛
  • 松弛就是更新minDist[],每次松弛遍历一次所有的边。
  • 时间复杂度为\(O(N*E)\)
if minDist[e.t] < minDist[e.s] + e.val:
	minDist[e.t] = minDist[e.s] + e.val
class Edge:
    def __init__(self, s, t, val):
        self.s = s
        self.t = t
        self.val = val

# 有负权边的单源最短路
def bellman_ford(num_node, num_edge, edges):
    # 各点到起点1的最短距离
    minDist = [float('inf') for _ in range(num_node+1)]
    minDist[1] = 0
    
    # 松弛 n-1 次,第i次循环得到的是与起点距离i条边的顶点的minDist
    for _ in range(num_node-1):
        updated = False
        for e in edges:
            if minDist[e.s] != float('inf') and minDist[e.s] + e.val < minDist[e.t]:
                minDist[e.t] = minDist[e.s] + e.val
                updated = True
        # 如果提前收敛,则跳出循环
        if updated == False:
            break
        

    if minDist[num_node] == float('inf'):
        print("unconnected")
    else :
        print(minDist[num_node])


def main():
    num_node, num_edge = map(int, input().split())
    edges = []
    for _ in range(num_edge):
        s, t, v = map(int, input().split())
        edges.append(Edge(s, t, v))
    
    bellman_ford(num_node, num_edge, edges)
    
if __name__ == "__main__":
    main()

4. 使用Bellman_Ford算法判断负回路

思路

  • 松弛次数为n次。若不存在负回路,则n-1次就收敛了。若第n次仍然发生了更新,说明存在负回路。

python

  • 使用本代码中的读取输入的方式比原先的读取方式更快。
import sys

def bellman_ford(num_node, num_edge, edges):
    # 城市1到各节点的最短距离
    minDist = [float('inf')] * (num_node+1)
    minDist[1] = 0
 
    flag = False
    
    # 松弛n次
    for i in range(1, num_node+1):
        if i < num_node:
            for e in edges:
                start = e[0]
                to = e[1]
                val = e[2]
                if minDist[start] != float('inf') and minDist[start] + val < minDist[to]:
                    minDist[to] = minDist[start] + val
        else :
            # 第n次回路权值仍变小,说明存在负回路
            for e in edges:
                start = e[0]
                to = e[1]
                val = e[2]
                if minDist[start] != float('inf') and minDist[start] + val < minDist[to]:
                    minDist[to] = minDist[start] + val
                    flag = True
        
    if flag :
        print("circle")
    elif minDist[num_node] == float('inf'):
        print("unconnected")
    else :
        print(minDist[num_node])

 
def main():
    # 这种方式的读取输入更快
    input = sys.stdin.read
    data = input().split()
    index = 0
    
    num_node = int(data[index])
    index += 1
    num_edge = int(data[index])
    index += 1
    
    edges = []
    for i in range(num_edge):
        s = int(data[index])
        index += 1
        t = int(data[index])
        index += 1
        val = int(data[index])
        index += 1
        edges.append([s, t, val])
     
    bellman_ford(num_node, num_edge, edges)
    

if __name__ == "__main__":
    main()

5. bellman_ford之单源有限最短路(卡码网 96)

题目:求src到dest最多经过k个点的最短路径;若存在,则输出路径长度,否则输出"unreachable"

import sys
import copy

# 单源有限最短路
def bellman_ford(num_node, num_edge, edges, src, dest, k):
    # src到各节点的最短距离
    minDist = [float('inf')] * (num_node+1)
    minDist[src] = 0
 

    # 松弛k+1次
    for i in range(k+1):
        minDistCopy = copy.deepcopy(minDist)
        for e in edges:
            start = e[0]
            to = e[1]
            val = e[2]
            # 第二个判断条件中不全是Copy
            if minDistCopy[start] != float('inf') and minDistCopy[start] + val < minDist[to]:
                minDist[to] = minDistCopy[start] + val
                
    if minDist[dest] == float('inf'):
        print("unreachable")
    else :
        print(minDist[dest])

 
def main():
    # 这种方式的读取输入更快
    input = sys.stdin.read
    data = input().split()
    index = 0
    
    num_node = int(data[index])
    index += 1
    num_edge = int(data[index])
    index += 1
    
    edges = []
    for i in range(num_edge):
        s = int(data[index])
        index += 1
        t = int(data[index])
        index += 1
        val = int(data[index])
        index += 1
        edges.append([s, t, val])
    
    src = int(data[-3])
    dest = int(data[-2])
    k = int(data[-1])
    
    bellman_ford(num_node, num_edge, edges, src, dest, k)
    

if __name__ == "__main__":
    main()

6. Floyd 算法(卡码网 97)

Floyd算法解决的是多源最短路问题。
思路

  • 采用动态规划:dp[i][j][k]
  • 递推公式:dp[i][j][k] = min(dp[i][j][k-1], dp[i][k][k-1] + dp[k][j][k-1])
  • 因为此动态规划中,只依赖于前一次的状态,可以压缩为二维数组
import sys

def main():
    max_int = 10001
    input = sys.stdin.read
    data = input().split()
    index = 0
    num_node, num_edge = int(data[index]), int(data[index+1])
    index += 2

    # dp[i][j][k] 表示经过 1...k 的 i 到 j的最短路径长度
    grid = [[[max_int for _ in range(num_node+1)] for _ in range(num_node+1)] for _ in range(num_node+1)]
    
    for _ in range(num_edge):
        s, t, val = int(data[index]), int(data[index+1]), int(data[index+2])
        index += 3
        # 双向边
        grid[s][t][0] = val
        grid[t][s][0] = val
    
    # floyd
    for k in range(1, num_node+1):
        for i in range(1, num_node+1):
            for j in range(1, num_node+1):
                grid[i][j][k] = min(grid[i][j][k-1], grid[i][k][k-1] + grid[k][j][k-1])
    

    q = int(data[index])
    index += 1
    
    for _ in range(q):
        s, t = int(data[index]), int(data[index+1])
        index += 2
        if grid[s][t][num_node] == 10001:
            print(-1)
        else :
            print(grid[s][t][num_node])
    
    
if __name__ == "__main__":
    main()
# floyd 二维 dp 版本
for k in range(1, num_node+1):
    for i in range(1, num_node+1):
        for j in range(1, num_node+1):
            grid[i][j] = min(grid[i][j], grid[i][k] + grid[k][j])
posted @   Frank23  阅读(24)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
点击右上角即可分享
微信分享提示