算法-图论-最短路径

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()
posted @ 2024-11-03 17:57  Frank23  阅读(4)  评论(0编辑  收藏  举报