一周小结——图的最短路算法总结篇
文章目录
链表模拟树和图
领接表数据结构讲解
#N为节点个数
#M为边的个数
h = [-1] * N #存储第i个节点所连接的节点的地址idx
e = [-1] * M #存储节点的值
w = [-1] * M #存储连到该节点的路径长度
ne = [-1] * M #发挥next指针的作用,用于指向链表的下一一个节点
idx = 0 #相当于地址,每次创建一个节点都对idx加一,且用作指针
#节点间都通过索引统一e,w,ne的
def add(a, b, c) : # 构建节点a到节点b的权值为c的边
global idx
e[idx] = b
w[idx] = c
ne[idx] = h[a]
h[a] = idx
idx += 1
DFS and BFS
DFS模板
def dfs(u) :
do something on u
st[u] = True
i = h[u]
while i != -1
j = e[i]
if st[j] :
i = ne[i]
continue
dfs(j)
i = ne[i]
BFS模板
def bfs(u) :
que = collections.deque()
que.appendleft(u)
do something on u
st[u] = True
while len(que) != 0 :
t = que.pop()
i = h[t]
while i != -1 :
j = e[i]
if not st[j] :
que.appendleft(j)
do something on j
st[j] = True
i = ne[i]
有向图的拓扑序列
-
定义:图中所有点构成的序列 A 满足:对于图中的每条边 (x,y),x 在 A 中都出现在 y 之前,则称 A 是该图的一个拓扑序列。
-
性质:
- 拓扑序列存在的充要条件是图是一个有向无环图(DAG)
- DAG一定至少存在一个入度为0的点
-
例题 :
给定一个 n 个点 m 条边的有向图,点的编号是 1 到 n,图中可能存在重边和自环。请输出任意一个该有向图的拓扑序列,如果拓扑序列不存在,则输出 −1。
若一个由图中所有点构成的序列 A 满足:对于图中的每条边 (x,y),x 在 A 中都出现在 y 之前,则称 A 是该图的一个拓扑序列。
输入格式
第一行包含两个整数 n 和 m。接下来 m 行,每行包含两个整数 x 和 y,表示存在一条从点 x 到点 y 的有向边 (x,y)。
输出格式
共一行,如果存在拓扑序列,则输出任意一个合法的拓扑序列即 可。否则输出 −1。
数据范围
1≤n,m≤10^5
输入样例:
3 3
1 2
2 3
1 3
输出样例:
1 2 3import collections.deque() N = M = 100010 h = [-1] * N e = [-1] * M ne = [-1] * M idx = 0 d = [0] * N def add(a, b) : global idx e[idx] = b ne[idx] = h[a] h[a] = idx idx += 1 res = [] def topsort() : que = collections.deque() for i in range(1, n + 1) : if not d[i] : que.appendleft(i) while len(que) != 0 : t = que.pop() res.append(t) i = h[t] while i != -1 : j = e[i] d[j] -= 1 #### if not d[j] : que.appendleft(j) i = ne[i] return len(res) == n n, m = map(int, input().split()) for i in range(m) : x, y = map(int, input().split()) add(x, y) d[y] += 1 if topsort() : for i in res : print(i, end = " ") else : print(-1)
Dijkstra算法
朴素版Dijkstra(稠密图)
用于权值为正的稠密图最短路算法,用邻接矩阵来存,矩阵的值是权值
流程分析
- 将起始点到起始点的距离设为0,其他节点到起始结点的距离设为无穷大
- 遍历n - 1遍,为选取剩下n - 1个节点,每次选取一个到起始点最近节点的节点
- 选取过程是,先判断是否被选过,再比较每个点到起始点的距离
- 纳入节点后,通过纳入的节点更新其他点到起始点的距离,并且最后将纳入节点状态设为已纳入
例题
给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环,所有边权均为正值。
请你求出 1 号点到 n 号点的最短距离,如果无法从 1 号点走到 n 号点,则输出 −1。
输入格式
第一行包含整数 n 和 m。
接下来 m 行每行包含三个整数 x,y,z,表示存在一条从点 x 到点 y 的有向边,边长为 z。
输出格式
输出一个整数,表示 1 号点到 n 号点的最短距离。
如果路径不存在,则输出 −1。
数据范围
1≤n≤500,
1≤m≤10^5,
图中涉及边长均不超过10000。
输入样例:
3 3
1 2 2
2 3 1
1 3 4
输出样例:
3
N = 510
INF = 100000010
g = [[INF] * N for _ in range(N)]
dist = [INF] * N
st = [False] * N
def dijkstra() :
dist[1] = 0###
for i in range(n - 1) :
t = -1
for j in range(1, n + 1) :
if not st[j] and (t == -1 or dist[t] > dist[j]) :
t = j
st[t] = True###
for j in range(1, n + 1) :
if dist[j] > dist[t] + g[t][j] :
dist[j] = dist[t] + g[t][j]
if dist[n] >= INF :
return -1
else :
return dist[n]
n, m = map(int, input().split())
for i in range(m) :
x, y, z = map(int, input().split())
g[x][y] = min(g[x][y], z)
print(dijkstra())
堆优化版Dijkstra(稀疏图)
用于稀疏图的存储优化,使用邻接表存储图,用优先队列存储每个节点
- Python中的堆讲解
from queue import PriorityQueue as PQ
pq = PQ()
pq.put((1, 'a'))
pq.put((2, 'c'))
pq.put((2, 'b'))
pq.put((2, 'b'))
print(pq.queue) # [(1, 'a'), (2, 'b'), (2, 'b'), (2, 'c')]
item0 = pq.get() # (1, 'a')
print(pq.queue) # [(2, 'b'), (2, 'b'), (2, 'c')]
print(pq.qsize()) # 优先队列的尺寸
while not pq.empty():
print(pq.get())
例题
给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环,所有边权均为非负值。
请你求出 1 号点到 n 号点的最短距离,如果无法从 1 号点走到 n 号点,则输出 −1。
输入格式
第一行包含整数 n 和 m。
接下来 m 行每行包含三个整数 x,y,z,表示存在一条从点 x 到点 y 的有向边,边长为 z。
输出格式
输出一个整数,表示 1 号点到 n 号点的最短距离。
如果路径不存在,则输出 −1。
数据范围
1≤n,m≤1.5×10^5,
图中涉及边长均不小于 0,且不超过 10000。
数据保证:如果最短路存在,则最短路的长度不超过 10^9。
输入样例:
3 3
1 2 2
2 3 1
1 3 4
输出样例:
3
from queue import PriorityQueue as PQ
M = N = 150010
INF = 100000010
h = [-1] * N
e = [-1] * M
w = [-1] * M
ne = [-1] * M
idx = 1
dist = [INF] * N
st = [False] * N
def add(a, b, c) :
global idx
e[idx] = b
w[idx] = c
ne[idx] = h[a]
h[a] = idx
idx += 1
def dijkstra() :
pq = PQ()
dist[1] = 0
pq.put([0, 1])
while not pq.empty() :
t = pq.pop()
distance, ver = t[0], t[1]
if st[ver] :
continue
st[ver] = True####
i = h[ver]
while i != -1 :
j = e[i]
if dist[j] > dist[ver] + w[i] :
dist[j] = dist[ver] + w[i]
pq.put([dist[j], j])
i = ne[i]
if dist[n] >= INF :
return -1
else :
return dist[n]
n, m = map(int, input().split())
for i in range(m) :
x, y, z = map(int, input().split())
add(x, y, z)
print(dijkstra())
Bellman-Ford
-
原理
松弛操作:在松弛一条边(u,v)的过程中,要测试是否可以通过u,对迄今找到的v的最短路径进行改进;如果可以改进的话,则更新d[v]和π[v]。一次松弛操作可以减小最短路径估计的值d[v],并更新v的前趋域πv。下面的伪代码对边(u,v)进行了一步松弛操作。 -
Bellma-Ford算法,模板
for i in range(k) : for 所有边 : 松弛操作
-
模板讲解
首先循环k次,则对图的所有边进行k次松弛操作,意思也是从起点开始到每个节点最多经过k条边的最短距离
松弛操作就是判断到某个节点,通过某个边更短还是已有的边更短 -
例题:
给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环, 边权可能为负数。
请你求出从 1 号点到 n 号点的最多经过 k 条边的最短距离,如果无法从 1 号点走到 n 号点,输出 impossible。注意:图中可能 存在负权回路 。
输入格式
第一行包含三个整数 n,m,k。接下来 m 行,每行包含三个整数 x,y,z,表示存在一条从点 x 到点 y 的有向边,边长为 z。
点的编号为 1∼n。
输出格式
输出一个整数,表示从 1 号点到 n 号点的最多经过 k 条边的最短距离。如果不存在满足条件的路径,则输出 impossible。
数据范围
1≤n,k≤500,
1≤m≤10000,
1≤x,y≤n,
任意边长的绝对值不超过 10000。输入样例:
3 3 1
1 2 1
2 3 1
1 3 3
输出样例:
3import copy M = 10010 INF = 10000010 edge = [] dist = [INF] * M def bellman_ford() : dist[1] = 0 for i in range(k) : backup = copy.deepcopy(dist) for i in edge : st, end, distance = i[0], i[1], i[2] if dist[end] > backup[st] + distance : dist[end] = backup[st] + distance if dist[n] >= INF // 2 : return False else : return True n, m, k = map(int, input().split()) for i in range(m) : x, y, z = map(int, input().split()) edge.append([x, y, z]) if bellman_ford() : print(dist(n)) else : print("impossible")
SPFA
概述
- SPFA算法的全称是:Shortest Path Faster Algorithm
为了避免最坏情况的出现,在正权图上应使用效率更高的Dijkstra算法。若给定的图存在负权边,类似Dijkstra算法等算法便没有了用武之地,SPFA算法便派上用场了。 - 定理:只要最短路径存在,上述SPFA算法必定能求出最小值。证明:每次将点放入队尾,都是经过松弛操作达到的。换言之,每次的优化将会有某个点v的最短路径估计值d[v]变小。所以算法的执行会使d越来越小。由于我们假定图中不存在负权回路,所以每个结点都有最短路径值。因此,算法不会无限执行下去,随着d值的逐渐变小,直到到达最短路径值时,算法结束,这时的最短路径估计值就是对应结点的最短路径值。
- 原理解析
简洁起见,我们约定加权有向图G不存在负权回路,即最短路径一定存在。用数组d记录每个结点的最短路径估计值,而且用邻接表来存储图G。我们采取的方法是动态逼近法:设立一个先进先出的队列用来保存待优化的结点,优化时每次取出队首结点u,并且用u点当前的最短路径估计值对离开u点所指向的结点v进行松弛操作,如果v点的最短路径估计值有所调整,且v点不在当前的队列中,就将v点放入队尾。这样不断从队列中取出结点来进行松弛操作,直至队列空为止。
例题
给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环, 边权可能为负数。
请你求出 1 号点到 n 号点的最短距离,如果无法从 1 号点走到 n 号点,则输出 impossible。
数据保证不存在负权回路。
输入格式
第一行包含整数 n 和 m。
接下来 m 行每行包含三个整数 x,y,z,表示存在一条从点 x 到点 y 的有向边,边长为 z。
输出格式
输出一个整数,表示 1 号点到 n 号点的最短距离。
如果路径不存在,则输出 impossible。
数据范围
1≤n,m≤10^5,
图中涉及边长绝对值均不超过 10000。
输入样例:
3 3
1 2 5
2 3 -3
1 3 4
输出样例:
2
import collections
N = M = 100010
INF = 100000010
h = [-1] * N
e = [-1] * M
w = [INF] * M
ne = [-1] * M
idx = 0
dist = [INF] * N
st = [False] * N
def add(a, b, c) :
global idx
e[idx] = b
w[idx] = c
ne[idx] = h[a]
h[a] = idx
idx += 1
def spfa() :
que = collections.deque()
dist[1] = 0
que.appendleft(1)
st[1] = True
while len(que) != 0 :
t = que.pop()
st[t] = False
i = h[t]
while i != -1 :
j = e[i]
if dist[j] > dist[t] + w[i] :
dist[j] = dist[t] + w[i]
if not st[j] :
st[j] = True
que.appendleft(j)
i = ne[i]
if dist[n] >= INF // 2 :
return False
else :
return True
n, m = map(int, input().split())
for i in range(m) :
x, y, z = map(int, input().split())
add(x, y, z)
if spfa() :
print(dist[n])
else :
print("impossible")
给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环, 边权可能为负数。
请你判断图中是否存在负权回路。
输入格式
第一行包含整数 n 和 m。
接下来 m 行每行包含三个整数 x,y,z,表示存在一条从点 x 到点 y 的有向边,边长为 z。
输出格式
如果图中存在负权回路,则输出 Yes,否则输出 No。
数据范围
1≤n≤2000,
1≤m≤10000,
图中涉及边长绝对值均不超过 10000。
输入样例:
3 3
1 2 -1
2 3 4
3 1 -4
输出样例:
Yes
难度:简单
时/空限制:1s / 64MB
import collections
N = M = 100010
INF = 100000010
h = [-1] * N
e = [-1] * M
w = [INF] * M
ne = [-1] * M
idx = 0
dist = [INF] * N
st = [False] * N
cnt = [0] * N
def add(a, b, c) :
global idx
e[idx] = b
w[idx] = c
ne[idx] = h[a]
h[a] = idx
idx += 1
def spfa() :
que = collections.deque()
for i in range(1, n + 1) :#####
que.appendleft(i)
st[i] = True
while len(que) != 0 :
t = que.pop()
st[t] = False
i = h[t]
while i != -1 :
j = e[i]
if dist[j] > dist[t] + w[i] :
dist[j] = dist[t] + w[i]
cnt[j] = cnt[t] + 1
if cnt[j] >= n :
return True
if not st[j] :
st[j] = True
que.appendleft(j)
i = ne[i]
return False
n, m = map(int, input().split())
for i in range(m) :
x, y, z = map(int, input().split())
add(x, y, z)
if spfa() :
print("Yes")
else :
print("No")
Floyd
概述
简介:
Floyd算法又称为插点法,是一种利用动态规划的思想寻找给定的加权图中多源点之间最短路径的算法,与Dijkstra算法类似。
算法过程:
-
从任意一条单边路径开始。所有两点之间的距离是边的权,如果两点之间没有边相连,则权为无穷大。
-
对于每一对顶点 u 和 v,看看是否存在一个顶点 w 使得从 u 到 w 再到 v 比已知的路径更短。如果是更新它。
把图用邻接矩阵G表示出来,如果从Vi到Vj有路可达,则G[i][j]=d,d表示该路的长度;否则G[i][j]=无穷大。定义一个矩阵D用来记录所插入点的信息,D[i][j]表示从Vi到Vj需要经过的点,初始化D[i][j]=j。把各个顶点插入图中,比较插点后的距离与原来的距离,G[i][j] = min( G[i][j], G[i][k]+G[k][j] ),如果G[i][j]的值变小,则D[i][j]=k。在G中包含有两点之间最短道路的信息,而在D中则包含了最短通路径的信息。
比如,要寻找从V5到V1的路径。根据D,假如D(5,1)=3则说明从V5到V1经过V3,路径为{V5,V3,V1},如果D(5,3)=3,说明V5与V3直接相连,如果D(3,1)=1,说明V3与V1直接相连。 -
状态转移方程
其状态转移方程如下: map[i,j]:=min{map[i,k]+map[k,j],map[i,j]};
map[i,j]表示i到j的最短距离,K是穷举i,j的断点,map[n,n]初值应该为0,或者按照题目意思来做。
当然,如果这条路没有通的话,还必须特殊处理,比如没有map[i,k]这条路。
例题
给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环,边权可能为负数。
再给定 k 个询问,每个询问包含两个整数 x 和 y,表示查询从点 x 到点 y 的最短距离,如果路径不存在,则输出 impossible。
数据保证图中不存在负权回路。
输入格式
第一行包含三个整数 n,m,k。
接下来 m 行,每行包含三个整数 x,y,z,表示存在一条从点 x 到点 y 的有向边,边长为 z。
接下来 k 行,每行包含两个整数 x,y,表示询问点 x 到点 y 的最短距离。
输出格式
共 k 行,每行输出一个整数,表示询问的结果,若询问两点间不存在路径,则输出 impossible。
数据范围
1≤n≤200,
1≤k≤n2
1≤m≤20000,
图中涉及边长绝对值均不超过 10000。
输入样例:
3 3 2
1 2 1
2 3 2
1 3 1
2 1
1 3
输出样例:
impossible
1
难度:简单
时/空限制:1s / 64MB
N = 210
M = 200010
INF = 10000010
g = [[INF] * N for _ in range(N)]
def floyd() :
for k in range(1, n + 1) :
for i in range(1, n + 1) :
for j in range(1, n + 1) :
g[i][j] = min(g[i][j], g[i][k] + g[k][j])
n, m, k = map(int, input().split())
for i in range(1, n + 1) :########
g[i][i] = 0
for i in range(m) :
x, y, z = map(int, input().split())
g[x][y] = min(g[x][y], z)
floyd()
for i in range(k) :
x, y = map(int, input().split())
if g[x][y] >= INF // 2 :
print("impossible")
else :
print(g[x][y])
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
· 凌晨三点救火实录:Java内存泄漏的七个神坑,你至少踩过三个!