算法-图论-最短路径
0. 不同的最短路径算法
1. dijkstra 朴素(卡码网 47)
题目条件:
- 不存在负权值的边
图的存储:邻接矩阵
dijkstra的基本思路:
- 维护
minDist[]
,表示各顶点到起点的最短距离;维护visited[]
,访问过的顶点不会重复访问。 - 对未访问过的
visited[j] == False
且minDist[j]
最小的顶点 进行访问 - 更新 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])
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了