最短路算法

最短路算法

一、Dijkstra

简介

用于计算一个节点其他节点最短路径

它的主要特点是以起始点为中心向外层层扩展(广度优先遍历贪心思想,如果权重为1的话就是BFS寻找最短路了),直到扩展到终点为止。

长度递增的次序产生最短路径

适用于正权图,可以有环,不可以有负权边
image

代码

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)\)
image

代码

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谁更大】)

那么与这个点有关联的所有点(除了已经在队列的元素)都将入队,进行迭代更新
image

代码

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])

image

应用:判断负权回路

用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,每次将所有的边都进行更新,目前的作用是求有边数限制的最短路

暴力遍历,无脑松驰
image

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")
posted @ 2024-04-08 17:06  gebeng  阅读(84)  评论(0)    收藏  举报