图论--连通分量

最近正好在上图论,写点笔记,方便自己复习。

无向图的连通分量问题

最常见的两种解法就是并查集和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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值