# 返回有向无环图(DAG)的其中一个拓扑序 # 如果图中有环,返回空列表 # 节点编号从 0 到 n-1 deftopologicalSort(n: int, edges: List[List[int]]) -> List[int]: g = [[] for _ inrange(n)] in_deg = [0] * n for x, y in edges: g[x].append(y) in_deg[y] += 1# 统计 y 的先修课数量
topo_order = [] q = deque(i for i, d inenumerate(in_deg) if d == 0) # 没有先修课,可以直接上 while q: x = q.popleft() topo_order.append(x) for y in g[x]: in_deg[y] -= 1# 修完 x 后,y 的先修课数量减一 if in_deg[y] == 0: # y 的先修课全部上完 q.append(y) # 加入学习队列
# 返回从起点 start 到每个点的最短路长度 dis,如果节点 x 不可达,则 dis[x] = math.inf # 要求:没有负数边权 # 时间复杂度 O(n + mlogm),其中 m 是 edges 的长度。注意堆中有 O(m) 个元素 defDijkstra(n: int, edges: List[List[int]], start: int) -> List[int]: # 注:如果节点编号从 1 开始(而不是从 0 开始),可以把 n 加一 g = [[] for _ inrange(n)] # 邻接表 for x, y, wt in edges: g[x].append((y, wt)) # g[y].append((x, wt)) # 无向图加上这行
dis = [inf] * n dis[start] = 0# 起点到自己的距离是 0 parent = [-1] * n h = [(0, start)] # 堆中保存 (起点到节点 x 的最短路长度,节点 x)
while h: dis_x, x = heappop(h) if dis_x > dis[x]: # x 之前出堆过 continue for y, wt in g[x]: new_dis_y = dis_x + wt if new_dis_y < dis[y]: dis[y] = new_dis_y # 更新 x 的邻居的最短路 parent[y] = x # 懒更新堆:只插入数据,不更新堆中数据 # 相同节点可能有多个不同的 new_dis_y,除了最小的 new_dis_y,其余值都会触发上面的 continue heappush(h, (new_dis_y, y))
return dis, parent
defget_path(parent: List[int], start: int, end: int) -> List[int]: if parent[end] == -1and end != start: # end 节点不可达 return [] path = [] curr = end while curr != -1: path.append(curr) curr = parent[curr] return path[::-1] # 反转路径,得到从 start 到 end 的顺序
# 返回一个二维列表,其中 (i,j) 这一项表示从 i 到 j 的最短路长度 # 如果无法从 i 到 j,则最短路长度为 math.inf # 允许负数边权 # 如果计算完毕后,存在 i,使得从 i 到 i 的最短路长度小于 0,说明图中有负环 # 节点编号从 0 到 n-1 # 时间复杂度 O(n^3 + m),其中 m 是 edges 的长度 defFloyd(self, n: int, edges: List[List[int]]) -> List[List[int]]: f = [[inf] * n for _ inrange(n)] # path[i][j] 记录从 i 到 j 最短路径上,i 的下一个节点 path = [[-1] * n for _ inrange(n)]
# 创建邻接矩阵 for i inrange(n): f[i][i] = 0 path[i][i] = i for x, y, wt in edges: f[x][y] = min(f[x][y], wt) # 如果有重边,取边权最小值 path[x][y] = y f[y][x] = min(f[y][x], wt) # 无向图 path[y][x] = x
for k inrange(n): for i inrange(n): if f[i][k] == inf: # 针对稀疏图的优化 continue for j inrange(n): f[i][j] = min(f[i][j], f[i][k] + f[k][j]) path[i][j] = path[i][k] return f, path
# 进行 n-1 轮松弛操作(假设有 n 个节点,节点编号从 0 到 n - 1) for _ inrange(n - 1): for u, v, w in edges: if dis[u] != inf and dis[u] + w < dis[v]: dis[v] = dis[u] + w parent[v] = u
# 第 n 轮松弛,用于检测负权环 has_negative_cycle = False for u, v, w in edges: if dis[u] != inf and dis[u] + w < dis[v]: has_negative_cycle = True # 在实际应用中,可以标记受负权环影响的节点 # 例如,将它们的 dis 设为 -inf break return dis, parent, has_negative_cycle
# 根据 parent 数组回溯从 start 到 end 的最短路径 (与 Dijkstra 版本相同) defget_path(parent: List[int], start: int, end: int) -> List[int]: if parent[end] == -1and end != start: # end 节点不可达 return [] path = [] curr = end while curr != -1: path.append(curr) curr = parent[curr] return path[::-1] # 反转路径,得到从 start 到 end 的顺序
classUnionFind: def__init__(self, n: int): # 一开始有 n 个集合 {0}, {1}, ..., {n-1} # 集合 i 的代表元是自己,大小为 1 self._fa = list(range(n)) # 代表元 self._size = [1] * n # 集合大小 self.cc = n # 连通块个数
# 返回 x 所在集合的代表元 # 同时做路径压缩,也就是把 x 所在集合中的所有元素的 fa 都改成代表元 deffind(self, x: int) -> int: # 如果 fa[x] == x,则表示 x 是代表元 ifself._fa[x] != x: self._fa[x] = self.find(self._fa[x]) # fa 改成代表元 returnself._fa[x]
# 判断 x 和 y 是否在同一个集合 defis_same(self, x: int, y: int) -> bool: # 如果 x 的代表元和 y 的代表元相同,那么 x 和 y 就在同一个集合 # 这就是代表元的作用:用来快速判断两个元素是否在同一个集合 returnself.find(x) == self.find(y)
# 把 from 所在集合合并到 to 所在集合中 # 返回是否合并成功 defmerge(self, from_: int, to: int) -> bool: x, y = self.find(from_), self.find(to) if x == y: # from 和 to 在同一个集合,不做合并 returnFalse self._fa[x] = y # 合并集合。修改后就可以认为 from 和 to 在同一个集合了 self._size[y] += self._size[x] # 更新集合大小(注意集合大小保存在代表元上) # 无需更新 _size[x],因为我们不用 _size[x] 而是用 _size[find(x)] 获取集合大小,但 find(x) == y,我们不会再访问 _size[x] self.cc -= 1# 成功合并,连通块个数减一 returnTrue
def_lowbit(self, x): """返回 x 的二进制表示中最低位的 1 所代表的值""" return x & (-x)
defupdate(self, index, delta): """ 在原数组的 index 位置上增加 delta。 index 是 1-based。 """ while index <= self.size: self.tree[index] += delta index += self._lowbit(index)
defquery(self, index): """ 查询原数组前缀 [1, index] 的和。 index 是 1-based。 """ result = 0 while index > 0: result += self.tree[index] index -= self._lowbit(index) return result
defquery_range(self, left, right): """ 查询原数组区间 [left, right] 的和。 left 和 right 都是 1-based。 """ if left > right: return0 returnself.query(right) - self.query(left - 1)