文章目录
有向图
一般g存图、dis存边权、u起点、v终点、w边权、s是包含定点的集合
无向图
无向图就是特殊的有向图,一遍存图存 " 存两遍 "
邻接表
存出边和边权的二维列表或者字典(一般是列表,因为大部分题目以从1开始的编号作为结点,如果用字典存要浪费一部分空间,而列表这部分空间用索引实现即可),如果是无向图一对就要添加两次
用二维列表存code
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))
g[v].append((u, w)) # 有向图这句不要
特点
优:节省空间,在只要遍历某点的出边时很方便
缺:无法快速查找两个点之间的边权
邻接矩阵
用二维列表存边权
code
dis = [[float('inf')]*(n+1) for _ in range(n+1)]
for _ in range(m):
u, v, w = map(int, input().split())
dis[u][v] = w
dis[v][u] = w # 有向图这句不要
特点
优:可以很方便的查找两个顶点间的边权,可以O(1)时间添加和删除边
缺:会浪费空间(要提前占满二维空间,即提前把空间开好)
拓扑排序
拓扑排序主要用来解决有向图中的依赖解析(dependency resolution)问题。
也可以用来找出环(先拓扑排序,因为环上的入度都比较大,拓扑排序在后面,dfs或bfs剩下来的就是环)
实现方法
- 把入度为0的挑出来,把他们的出边的入度-1
- 不断把入度为0的挑出来,就是拓扑排序的结果
题目传送门
P4017 最大食物链计数 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
code
n, m = map(int, input().split())
d = dict(zip(range(1, n+1), ([0, []] for _ in range(n))))
for _ in range(m):
a, b = map(int, input().split())
d[b][0] += 1 # 入度+1
d[a][1].append(b) # 出边+1
in1 = [(i, j) for i, j in d.items() if j[0] == 0]
out = [i for i, j in d.items() if j[1] == []]
c = []
dp = [0]*(n+1)
for i, j in in1:
dp[i] = 1
for k in j[1]:
d[k][0] -= 1 # 入度-1
dp[k] = (dp[k] + dp[i]) % 80112002
if not d[k][0]:
c.append((k, d[k]))
for i, j in c:
for k in j[1]:
d[k][0] -= 1 # 入度-1
dp[k] = (dp[k]+dp[i]) % 80112002
if not d[k][0]:
c.append((k, d[k]))
ans = 0
for i in out:
ans = (ans+dp[i]) % 80112002
print(ans)
最短路
Floyd算法
适用于多源最短路问题
初始化
code
f = [[0 if i == j else float('inf') for j in range(n)] for i in range(n)]
主体
标准code
for k in range(1, n+1):
for i in range(1, n+1):
for j in range(1, n+1):
if f[i][j] > f[i][k]+f[k][j]: # 避免多次调用函数
f[i][j] = f[i][k]+f[k][j]
细节
-
k必须在最外层:一是因为基本的逻辑是先只考虑直接连通问题,再把k一个一个添加进去算子图,所以为了保证每个k都是在遍历全图,k得在外循环;二是因为得确保
f[i][k]和f[k][j]都是已经和前面的k更新过的,如果k在内循环会导致它们可能还没被前面的k松弛过
可以说在计算时都是以k为基点的,把一个一个"中转站"加入去更新
一句话,Floyd算法的本质是DP,而k是DP的阶段,因此要写最外面。
-
i 和 j 的顺序无所谓,随意调换
标准优化code
for k in range(1, n+1):
for i in range(1, n+1):
if k != i: # 中转站是自己是毫无意义的
if f[i][k] == float('inf'): # 跳过不可能的中转站
continue
for j in range(1, n+1):
if f[i][j] > f[i][k]+f[k][j]: # 避免多次调用函数
f[i][j] = f[i][k]+f[k][j]
无向图的优化code
因为无向图是对称的,所以可以优化。注意有向图千万不要只计算上三角或下三角
for k in range(1, n+1):
for i in range(2, n+1): # 给j留空间(实际f[1][1]为0已经初始化过了)
if k != i: # 中转站是自己是毫无意义的
if f[i][k] == float('inf'): # 跳过不可能的中转站
continue
for j in range(1, i): # 到i就是对角线了,f[i][i]是0,初始化的时候就算过了
if f[i][j] > f[i][k]+f[k][j]: # 避免多次调用函数
f[i][j] = f[j][i] = f[i][k]+f[k][j] # 初始就是相等的,如果有替换就一起换
题目传送门
B3647 【模板】Floyd 算法 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
code
n, m = map(int, input().split())
f = [[0 if i == j else float('inf') for j in range(n+1)] for i in range(n+1)]
for _ in range(m):
u, v, w = map(int, input().split())
f[u][v] = f[v][u] = w
for k in range(1, n+1):
for i in range(2, n+1):
if k != i:
if f[i][k] != float('inf'):
for j in range(1, i):
if f[i][j] > f[i][k]+f[k][j]:
f[i][j] = f[j][i] = f[i][k]+f[k][j]
for i in range(1, n+1):
for j in range(1, n+1):
print(f[i][j], end=' ')
print()
迪杰斯特拉算法(Dijkstra)
适用于单源最短路问题,即一个点到所有点
优点:高效稳定。缺点:边权不能为负
思想:贪心+优先队列
如图,原本考虑s到u最短直连通路径就是8,但是因为负边权的存在最短路径变成了5,所以迪杰斯特拉算法(Dijkstra)算法不支持负边权
思维过程(类似bfs,也就是为什么不能出现负边权)
- 从源点u1开始下一个一定是源点的最短直联通点u2
- 再下一个一定是u2的最短直联通点或u1剩下的最短直联通点
- 每次加入的点将自己的直连通点加入排序,每次弹出最小的那个作为下一个源点
实现过程
- 从源点开始,将所有直联通点压入最小堆hp中
- 弹出里面路程最短的作为下一个源点
- 遍历下一个源点的还没加入路径的直联通点
- 如果能产生更优方案就压入最小堆,更高的优先级让最优的方案优先被弹出(原本的方案还在,不会消失)
- 因为一个点只要一个最优方案,所以加入路径后就直接用done标记,这样下次遍历时就会跳过这个
标准code
题目传送门
P4779 【模板】单源最短路径(标准版) - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
import heapq
def dij(s, d):
done = [0]*(n+1)
hp = []
heapq.heappush(hp, (0, s)) # (堆,(优先级,元素))
while hp:
u = heapq.heappop(hp)[1] # 因为弹出的是元组,但是现在只要下一个顶点
if done[u]:
continue
done[u] = 1
for v, w in g[u]:
if done[v]:
continue
if d[v] > d[u]+w: # 第一次替换就是第一种能到的情况,会压入hp
d[v] = d[u]+w
heapq.heappush(hp, (d[v], v)) # 如果不换就说明到v的仍然是直通或之前留下来的方案快,而hp只留下一个点最快的方案,等这个点最快的方案最快了就弹出,如果没有发生替换
n, m, s0 = map(int, input().split())
g = [[] for _ in range(n+1)]
dis = [float("inf")]*(n+1) # 存到源点的距离
dis[s0] = 0
for _ in range(m):
u, v, w = map(int, input().split())
g[u].append((v, w))
dij(s0, dis)
for i in range(1, n+1):
print(dis[i], end = ' ')
输出源点到每点的路径code
import heapq
def dij(s, d, r):
done = [0]*(n+1)
hp = []
r[1] = '1 '
heapq.heappush(hp, (0, s)) # (堆,(优先级,元素))
while hp:
u = heapq.heappop(hp)[1] # 因为弹出的是元组,但是现在只要下一个顶点
if done[u]:
continue
done[u] = 1
for v, w in g[u]:
if done[v]:
continue
if d[v] > d[u]+w: # 第一次替换就是第一种能到的情况,会压入hp
d[v] = d[u]+w
r[v] = r[u]+f"{v} "
heapq.heappush(hp, (d[v], v)) # 如果不换就说明到v的仍然是直通或之前留下来的方案快,而hp只留下一个点最快的方案,等这个点最快的方案最快了就弹出,如果没有发生替换
n, m, s0 = map(int, input().split())
g = [[] for _ in range(n+1)]
dis = [float("inf")]*(n+1) # 存到源点的距离
dis[s0] = 0
road = ["-1"]*(n+1) # 不能到达输出-1
for _ in range(m):
u, v, w = map(int, input().split())
g[u].append((v, w))
g[v].append((u, w)) # 做题一定注意单双
dij(s0, dis, road)
for i in range(1, n+1):
print(road[i])
输出源点到某一点的最短路径
题目传送门
Dijkstra? - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
import heapq
def dij(s, d, r, k):
done = [0]*(n+1)
hp = []
r[1] = '1 '
heapq.heappush(hp, (0, s)) # (堆,(优先级,元素))
while hp:
u = heapq.heappop(hp)[1] # 因为弹出的是元组,但是现在只要下一个顶点
if u == k:
print(r[k])
return
if done[u]:
continue
done[u] = 1
for v, w in g[u]:
if done[v]:
continue
if d[v] > d[u]+w: # 第一次替换就是第一种能到的情况,会压入hp
d[v] = d[u]+w
r[v] = r[u]+f"{v} "
heapq.heappush(hp, (d[v], v)) # 如果不换就说明到v的仍然是直通或之前留下来的方案快,而hp只留下一个点最快的方案,等这个点最快的方案最快了就弹出,如果没有发生替换
n, m, s0, key = map(int, input().split())
g = [[] for _ in range(n+1)]
dis = [float("inf")]*(n+1) # 存到源点的距离
dis[s0] = 0
road = [""]*(n+1)
for _ in range(m):
u, v, w = map(int, input().split())
g[u].append((v, w))
g[v].append((u, w))
dij(s0, dis, road)
if dis[k] == float('inf'):
print(-1)
变式
题目传送门
P1576 最小花费 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
import heapq
def dij(s, d, k):
done = [0]*(n+1)
hp = []
heapq.heappush(hp, (1, s)) # (堆,(优先级,元素))
while hp:
u = heapq.heappop(hp)[1] # 因为弹出的是元组,但是现在只要下一个顶点
if u == k:
print('%.8f' % (100*d[k]))
return
if done[u]:
continue
done[u] = 1
for v, w in g[u]:
if done[v]:
continue
if d[v] > d[u]/w: # 第一次替换就是第一种能到的情况,会压入hp
d[v] = d[u]/w
heapq.heappush(hp, (d[v], v)) # 如果不换就说明到v的仍然是直通或之前留下来的方案快,而hp只留下一个点最快的方案,等这个点最快的方案最快了就弹出,如果没有发生替换
n, m = map(int, input().split())
g = [[] for _ in range(n+1)]
dis = [float("inf")]*(n+1) # 存到源点的距离
for _ in range(m):
u, v, w = map(int, input().split())
w = 1-w/100
g[u].append((v, w))
g[v].append((u, w))
a, b = map(int, input().split())
dis[a] = 1
dij(a, dis, b)