一、拓扑排序精讲
题目连接:117. 软件构建 (kamacoder.com)
文章讲解:代码随想录 (programmercarl.com)——拓扑排序
拓扑排序定义:给出一个 有向图,把这个有向图转成线性的排序 就叫拓扑排序。同时拓扑排序也是需要检测该有向图是否有环,即存在循环依赖的情况,如果有,则不能做线性排序。
特点:有向无环图 先行排序
思路:
step1. 找入度为 0 的节点,确定出发节点,加入结果集
step2. 将该节点从图中移除
结果集中的顺序就是想要的拓扑排序顺序。
Note:结果集中的顺序可能不唯一。
from collections import deque, defaultdict
# n -> 代表图中节点数,即文件个数
# edges -> 包含所有依赖关系的二元组(s, t),表示 t 文件依赖于 s 文件
def topological_sort(n, edges):
inDegree = [0] * n # 记录每个文件的入度
umap = defaultdict(list) # 记录每个文件的依赖关系
# 构造图和入度表
for s, t in edges:
# 表示有一个新的文件依赖 t
inDegree[t] += 1
# 将 t 添加到 s 的依赖列表中,表示要删 t 需要先删 0
umap[s].append(t)
# 初始化队列,加入所有入度为0的节点
que = deque()
for i in range(n):
if inDegree[i] == 0:
que.append(i)
result = []
# 列表不为空时,表示有文件可以继续处理
while que:
cur = que.popleft()
result.append(cur)
# 获取当前文件指向的文件
for file in umap[cur]:
# 当前文件入度 -1
inDegree[file] -= 1
if inDegree[file] == 0:
que.append(file)
if len(result) == n:
print(" ".join(map(str, result)))
else:
print(-1)
if __name__ == '__main__':
n, m = map(int, input().split())
edges = [tuple(map(int, input().split())) for _ in range(m)]
topological_sort(n, edges)
二、dijkstra(朴素版)精讲
题目连接:47. 参加科学大会(第六期模拟笔试) (kamacoder.com)
文章讲解:代码随想录 (programmercarl.com)——dijkstra(朴素版)
dijkstra算法用法:在 有权图 中求从起点到其他节点的最短路径。
Notes:
1. dijkstra算法可以同时求 起点到所有节点的最短路径
2. 权重不能为负数
dijkstra算法三部曲:
step1. 选择距离源节点近且未被访问过的节点;
step2. 将该节点标记为访问过;
step3. 更新非访问节点到远点的距离,即更新 minDist 数组。
minDist数组用来记录每一个节点距离源点的最小距离。
# edges -> (p1, p2, val)的列表,其中 p1 为起始节点编号,p2 终止节点编号,val 权重
def dijkstra(n, m, edges, start, end):
# 初始化邻接矩阵
grid = [[float('inf')] * (n + 1) for _ in range(n + 1)]
# 填充邻接矩阵
for p1, p2, val in edges:
grid[p1][p2] = val
# 初始化距离数组和访问数组
minDist = [float('inf')] * (n + 1)
visited = [False] * (n + 1)
minDist[start] = 0
# 遍历所有节点
for _ in range(1, n + 1):
minVal = float('inf')
cur = -1
# step1. 选择距离源节点近且未被访问过的节点
for v in range(1, n + 1):
if not visited[v] and minDist[v] < minVal:
minVal = minDist[v]
cur = v
# 如果找不到未访问的节点,结束
if cur == -1:
break
# step2. 将该节点标记为访问过
visited[cur] = True
# step3. 更新非访问节点到远点的距离
for v in range(1, n + 1):
# 检查节点 v 是否未访问且存在边从 cur 到 v,同时检查通过 cur 到达 v 的新路径是否更短
# grid[cur][v] 表示从节点 cur 到 节点 v 的权重
if not visited[v] and grid[cur][v] != float('inf') and minDist[cur] + grid[cur][v] < minDist[v]:
minDist[v] = minDist[cur] + grid[cur][v]
# 如果目标节点不可到达
if minDist[end] == float('inf'):
return -1
else:
return minDist[end]
if __name__ == '__main__':
n, m = map(int, input().split())
edges = []
for _ in range(m):
p1, p2, val = map(int, input().split())
edges.append((p1, p2, val))
start = 1
end = n
result = dijkstra(n, m, edges, start,end)
print(result)
def dijkstra(n, m, edges, start, end):
grid = [[float('inf')] * (n + 1) for _ in range(n + 1)]
for p1, p2, val in edges:
grid[p1][p2] = val
minDist = [float('inf')] * (n + 1)
visited = [False] * (n + 1)
# 新增数组用于记录前驱节点
prev = [-1] * (n + 1)
minDist[start] = 0
for _ in range(1, n + 1):
minVal = float('inf')
cur = -1
for v in range(1, n + 1):
if not visited[v] and minDist[v] < minVal:
minVal = minDist[v]
cur = v
if cur == -1:
break
visited[cur] = True
for v in range(1, n + 1):
if not visited[v] and grid[cur][v] != float('inf') and minDist[cur] + grid[cur][v] < minDist[v]:
minDist[v] = minDist[cur] + grid[cur][v]
# 更新前驱节点为当前处理的节点
prev[v] = cur
if minDist[end] == float('inf'):
return -1, []
else:
path = []
current = end
# 回溯路径
while current != -1:
path.append(current)
current = prev[current]
# 将路径反转,得到从起点到终点的顺序
path.reverse()
return minDist[end], path
if __name__ == '__main__':
n, m = map(int, input().split())
edges = []
for _ in range(m):
p1, p2, val = map(int, input().split())
edges.append((p1, p2, val))
start = 1
end = n
result, path = dijkstra(n, m, edges, start, end)
# 输出结果
if result == -1:
print(-1)
else:
print("->".join(map(str, path)))