最短路算法
最短路算法
一、Dijkstra
简介
用于计算一个节点到其他节点的最短路径。
它的主要特点是以起始点为中心向外层层扩展(广度优先遍历与贪心思想,如果权重为1的话就是BFS寻找最短路了),直到扩展到终点为止。
按长度递增的次序产生最短路径
适用于正权图,可以有环,不可以有负权边

代码
from heapq import heappop,heappush
from math import inf
n,m = map(int,input().split())
g = [[]for _ in range(n + 1)]
for _ in range(m):
u,v,w = map(int,input().split())
g[u].append([v,w])
dis = [inf] * (n + 1)
start = 1
dis[start] = 0
heap = [(0,start)]
while heap:
d,u = heappop(heap)
# 说明start->u的最短路已经更新了,因此就不必再更新
if d > dis[u]:
continue
for v,w in g[u]:
if dis[v] > d + w:
dis[v] = d + w
heappush(heap,(d + w,v))
if dis[n] == inf:
print(-1)
else:
print(dis[n])
时间复杂度:(n + m)log n
变式1
如何存储最短路径?
关键点就在每次松弛操作那里,如果v可以由u更新,那么令pre[v] = u,表示v的前驱节点是u
变式2
如果增加一维度量标准,该怎么做?首先要求最短路,在此基础上要求花费最少?
if dis[v] == d + w:
if cost[v] > cost[u] + c:
cost[v] = cost[u] + c
pre[v] = u
二、Floyd
简介
求多源最短路,本质上是动态规划,边权可为负数,但不能存在负权回路,时间复杂度为 \(O(n^3)\)

代码
from math import inf
n,m,q = map(int,input().split())
# dp[k][i][j]表示考虑从1-k的节点作为中间节点,从i到j的最短路径
# dp[k][i][j] = min(dp[k - 1][i][j],dp[k - 1][i][k] + dp[k - 1][k][j])
# 由于k只与k - 1有关,因此可以优化k这一维
dp = [[inf]*(n + 1) for _ in range(n + 1)]
for _ in range(m):
u,v,w = map(int,input().split())
dp[u][v] = min(dp[u][v],w)
for i in range(1,n + 1):
dp[i][i] = 0
for k in range(1,n + 1):
for u in range(1,n + 1):
for v in range(1,n + 1):
dp[u][v] = min(dp[u][v],dp[u][k] + dp[k][v])
for _ in range(q):
u,v = map(int,input().split())
if dp[u][v] == inf:
print("impossible")
else:
print(dp[u][v])
三、SPFA
简介
边权可为负数,可用于检验负权回路,时间复杂度为O(kn),k为每个点进入队列的平均次数,一般小于等于2,但如果专门卡的话,会很大!
大致思路:如果有一个点的最短距离被更新了(松弛操作【看dis[v]与dis[u] + w谁更大】)
那么与这个点有关联的所有点(除了已经在队列的元素)都将入队,进行迭代更新

代码
from math import inf
from collections import deque
n,m = map(int,input().split())
g = [[]for _ in range(n + 1)]
dis = [inf] * (n + 1)
vis = [False] * (n + 1)
for _ in range(m):
u,v,w = map(int,input().split())
g[u].append([v,w])
dis[1] = 0
vis[1] = True
q = deque([1])
while q:
u = q.popleft()
vis[u] = False
for v,w in g[u]:
if dis[v] > dis[u] + w:
dis[v] = dis[u] + w
if not vis[v]:
q.append(v)
vis[v] = True
if dis[n] == inf:
print("impossible")
else:
print(dis[n])

应用:判断负权回路
用cnt数组记录最短路径的边数,如果大于等于n,那么就代表出现负权回路死循环了!
小tips,将队列变成栈,可以加快判环的速度。
dis数组的初始化可以随意定值,只要都一样,那么就相当于0,出现负边就满足dis[v] > dis[u] + w,那么就可以进行迭代更新了!
from math import inf
from collections import deque
n,m = map(int,input().split())
g = [[]for _ in range(n + 1)]
dis = [0x3f3f3f3f] * (n + 1)
vis = [True] * (n + 1)
cnt = [0] * (n + 1)
for _ in range(m):
u,v,w = map(int,input().split())
g[u].append([v,w])
def judge():
st = list(range(1,n + 1))
while st:
u = st.pop()
vis[u] = False
for v,w in g[u]:
if dis[v] > dis[u] + w:
dis[v] = dis[u] + w
cnt[v] = cnt[u] + 1
if not vis[v]:
st.append(v)
vis[v] = True
# 用来存放最短路的边数,如果边数大于等于n,那么必然出现负权回路死循环
if cnt[v] >= n:
return True
return False
print("Yes") if judge() else print("No")
四、Bellman-Ford
简介
相当于未进行队列优化的SPFA,每次将所有的边都进行更新,目前的作用是求有边数限制的最短路
暴力遍历,无脑松驰

from collections import defaultdict as df
from math import inf
import sys
input = sys.stdin.readline
read = lambda:map(int,input().split())
n,m,k = read()
g = df(lambda:df(lambda:inf))
dis = [inf] * (n + 1)
dis[1] = 0
for _ in range(m):
u,v,w = read()
g[u][v] = min(g[u][v],w)
for _ in range(k):
pre = dis + []
for u in g:
for v in g[u]:
dis[v] = min(dis[v],pre[u] + g[u][v])
print(dis[-1]) if dis[-1] != inf else print("impossible")

浙公网安备 33010602011771号