最近正好在上图论,写点笔记,方便自己复习。
无向图的连通分量问题
最常见的两种解法就是并查集和dfs,结合下面畅通工程的题目,提供两种解法。
并查集做法:
# 初始化两个数组,分别用于存储每个节点的父节点和树的高度
father = [0] * 1005
height = [0] * 1005
# 查找节点的根节点,并进行路径压缩
def find_father(x):
if x != father[x]:
father[x] = find_father(father[x]) # 路径压缩
return father[x]
# 合并两个集合
def union(x, y):
x = find_father(x)
y = find_father(y)
if x != y:
father[x] = y # 将一个集合的根节点连接到另一个集合的根节点
# 主函数处理输入和输出
def main():
while True:
data = input().split()
if len(data) == 1:
break # 如果只有一个参数,结束输入
n, m = map(int, data)
if n == 0:
break # 如果 n 为 0,结束输入
# 初始化每个节点的父节点为它自身
for i in range(1, n + 1):
father[i] = i
# 处理 m 条边
while m:
x, y = map(int, input().split())
union(x, y) # 合并两个节点
m -= 1
# 计算连通分量的数量
answer = -1
for i in range(1, n + 1):
if find_father(i) == i:
answer += 1
print(answer) # 输出连通分量的数量
# 调用主函数
main()
dfs做法:
def find_component(n, roads):
# 创建一个图的邻接表表示
graph = {i: [] for i in range(1, n + 1)}
for u, v in roads:
graph[u].append(v)
graph[v].append(u)
# 初始化一个访问标记数组
visited = [False] * (n + 1)
# 深度优先搜索(DFS)函数
def dfs(node):
stack = [node]
while stack:
top = stack.pop()
for neighbor in graph[top]:
if not visited[neighbor]:
stack.append(neighbor) # 将未访问的邻居节点加入栈中
visited[neighbor] = True # 标记邻居节点为已访问
# 计数连通分量
components = -1
for i in range(1, n + 1):
if not visited[i]:
components += 1 # 发现一个新的连通分量
visited[i] = True
dfs(i) # 对该节点进行 DFS 遍历
return components
def main():
while True:
roads = []
data = input().split()
if len(data) == 1:
break # 如果只有一个参数,结束输入
n, m = map(int, data)
if n == 0:
break # 如果 n 为 0,结束输入
# 读取 m 条边
while m:
x, y = map(int, input().split())
roads.append((x, y))
m -= 1
# 计算并输出连通分量的数量
answer = find_component(n, roads)
print(answer)
# 调用主函数
main()
有向图的强连通分量集合(SCC):
算法分为以下四步:
Step 1: 对原图 GG 执行一次 DFS
我们对图 GG 进行一次深度优先搜索,记录每个节点的“完成时间”(即 DFS 访问完一个节点后退出的时间)。完成时间的顺序可以用一个栈来存储。
stack = []
def dfs(v,visited, stack):
visited[v] = True
for i in range(len(graph)):
if graph[v][i] == 1 and not visited[i]:
dfs(i, visited, stack)
stack.append(v)
Step 2: 计算图的转置 GT
将 G 的所有边反向,得到转置图 GT
def tanspose():
trans_graph = [[0]*len(graph) for _ in range(len(graph))]
for i in range(len(graph)):
for j in range(len(graph)):
trans_graph[i][j] = graph[j][i]
return trans_graph
Step 3: 按照完成时间的逆序对 GT 进行 DFS
visited = [False] * len(graph)
while stack:
v = stack.pop()
if v not in visited:
scc_stack = []
dfs(v, visited, scc_stack)
Step 4: 收集所有的强连通分量
步骤3中的一个scc_stack就是一个强连通分量集合
scc = []
visited = [False] * len(graph)
while stack:
v = stack.pop()
if v not in visited:
scc_stack = []
dfs(v, visited, scc_stack)
scc.append(scc_stack)
return scc
完整代码展示:
图部分:
# 强连通图
graph = [
[0, 1, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 1, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 0, 0, 0],
[0, 0, 0, 0, 0, 1, 0, 0],
[0, 0, 0, 0, 0, 0, 1, 0],
[0, 0, 0, 0, 0, 0, 0, 1],
[0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0]
]
算法运作部分:
def find_scc():
visited = [False] * len(graph)
for i in range(len(graph)):
if not visited[i]:
dfs(i, graph, visited, stack) # 步骤1
scc = []
visited = [False] * len(graph)
trans_graph = tanspose() # 步骤2
while stack:
v = stack.pop()
if v not in visited:
scc_stack = [] # 强连通分量
dfs(v, trans_graph, visited, scc_stack) # 步骤3
scc.append(scc_stack) # 步骤4
return scc