You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何统计无向图中构成环的连通分量?现有Python代码的问题分析与修正

如何统计无向图中构成环的连通分量?现有Python代码的问题分析与修正

我正在处理一个无向图,需要统计其中同时是环的连通分量数量。环的定义是:连通分量中每个顶点恰好有两条边,且分量至少包含3个顶点。

图由n个顶点和m条边表示,每条边连接两个不同的顶点,且没有重复边。

定义说明

  • 无向图:
    • 由顶点集合和边集合组成
    • 每条边连接两个不同顶点(u ≠ v)
    • 任意一对顶点之间最多只有一条边
  • 连通分量:
    • 顶点的一个子集,子集内任意两个顶点之间都有路径相连,且子集内顶点与外部顶点无连接
  • 环(符合要求的连通分量):
    • 至少包含3个顶点
    • 每个顶点恰好属于2条边
    • 可以将顶点重新排列成序列,使得第一个顶点连接第二个,第二个连接第三个……最后一个顶点连接回第一个

输入输出要求

输入

  • 第一行包含两个整数n(1 ≤ n ≤ 200,000)和m(0 ≤ m ≤ 200,000),分别表示顶点数和边数
  • 接下来m行,每行包含两个整数u和v,描述顶点u和v之间的一条边,无重复边,图是无向的

输出

  • 单个整数:图中属于环的连通分量的数量

示例

输入

17 15
1 8
1 12
5 11
11 9
9 15
15 5
4 13
3 13
4 3
10 16
7 10
16 7
14 3
14 4
17 6

输出

2

我的尝试(存在问题的代码)

我实现了以下Python解决方案,它在部分测试用例中有效,但在学校提交平台的某些隐藏测试用例中失败了,代码如下:

from collections import defaultdict, deque

def count_cycle_components(n, m, edges):
    # Create an adjacency list for the graph
    graph = defaultdict(list)
    for u, v in edges:
        graph[u].append(v)
        graph[v].append(u)

    # Visited array to track visited nodes
    visited = [False] * (n + 1)

    # Helper function to check if a connected component is a cycle
    def is_cycle_component(node):
        queue = deque([(node, -1)])  # (current node, parent node)
        visited[node] = True
        node_count = 0
        edge_count = 0

        while queue:
            current, parent = queue.popleft()
            node_count += 1
            for neighbor in graph[current]:
                edge_count += 1
                if not visited[neighbor]:
                    visited[neighbor] = True
                    queue.append((neighbor, current))
                elif neighbor != parent:
                    pass  # Neighbor visited and not parent, ignore
        
        # Each edge is counted twice in an undirected graph
        edge_count //= 2

        # Check if the connected component is a cycle
        return node_count >= 3 and edge_count == node_count

    cycle_count = 0

    # Iterate through all nodes to find connected components
    for node in range(1, n + 1):
        if not visited[node]:
            if is_cycle_component(node):
                cycle_count += 1

    return cycle_count

if __name__ == "__main__":
    import sys
    input = sys.stdin.read
    data = input().splitlines()

    # Read the number of vertices and edges
    n, m = map(int, data[0].split())
    # Read the edges
    edges = [tuple(map(int, line.split())) for line in data[1:]]

    # Output the number of cycle components
    print(count_cycle_components(n, m, edges))

请问这段代码可能在哪些测试用例中失败?如何修正或优化它来解决问题?


代码问题分析

你的思路方向是对的,但存在一个关键漏洞:仅通过node_count == edge_count和node_count >=3无法准确判断是否是环分量。因为还有一种结构满足这两个条件——带环的连通分量(比如一个环上附加了一条树状分支,或者两个环共享一个顶点的“θ形”结构),这类结构的总顶点数等于总边数,但每个顶点的度数并不都是2,所以不符合环的定义。

举个例子:假设有顶点1-2-3-1(环,3个顶点3条边),然后顶点3连接顶点4,这时候整个连通分量有4个顶点,4条边(1-2,2-3,3-1,3-4),满足node_count == edge_count且node_count>=3,但顶点3的度数是3,显然不是环分量,但你的代码会误判它是环。

另外,你的代码在处理孤立顶点(没有边的顶点)时没问题,因为node_count=1,edge_count=0,不会被统计,但对于上述的带分支的环结构就会出错。

修正方案

核心思路应该回到环的定义:连通分量中每个顶点的度数恰好是2,且顶点数≥3。这样就能准确排除那些总边数等于顶点数但存在度数不为2的顶点的情况。

具体修正步骤:

  1. 预先统计每个顶点的度数,这样可以快速判断某个连通分量是否有可能是环(只要分量里有一个顶点度数不是2,直接排除)
  2. 在遍历连通分量时,除了统计顶点数,还要检查每个顶点的度数是否都是2
  3. 同时确保顶点数≥3

另外,考虑到n和m可以达到2e5,我们需要保证算法的时间复杂度是O(n+m),这和你原来的BFS复杂度一致,不会有性能问题。

修正后的代码

from collections import defaultdict, deque

def count_cycle_components(n, m, edges):
    # 构建邻接表
    graph = defaultdict(list)
    # 统计每个顶点的度数
    degree = [0] * (n + 1)
    for u, v in edges:
        graph[u].append(v)
        graph[v].append(u)
        degree[u] += 1
        degree[v] += 1

    visited = [False] * (n + 1)
    cycle_count = 0

    def is_cycle_component(node):
        queue = deque([node])
        visited[node] = True
        node_count = 0
        valid = True  # 标记当前分量是否所有顶点度数都是2

        while queue:
            current = queue.popleft()
            node_count += 1
            # 如果当前顶点度数不是2,直接标记为无效
            if degree[current] != 2:
                valid = False
            for neighbor in graph[current]:
                if not visited[neighbor]:
                    visited[neighbor] = True
                    queue.append(neighbor)
        
        # 满足条件:所有顶点度数为2,且顶点数≥3
        return valid and node_count >= 3

    for node in range(1, n + 1):
        if not visited[node]:
            # 可以先做个快速筛选:如果当前顶点度数不是2,直接跳过,节省时间
            if degree[node] != 2:
                # 还是要遍历标记所有顶点为已访问,避免重复处理
                queue = deque([node])
                visited[node] = True
                while queue:
                    current = queue.popleft()
                    for neighbor in graph[current]:
                        if not visited[neighbor]:
                            visited[neighbor] = True
                            queue.append(neighbor)
                continue
            if is_cycle_component(node):
                cycle_count += 1

    return cycle_count

if __name__ == "__main__":
    import sys
    input = sys.stdin.read().split()
    ptr = 0
    n = int(input[ptr])
    ptr +=1
    m = int(input[ptr])
    ptr +=1
    edges = []
    for _ in range(m):
        u = int(input[ptr])
        ptr +=1
        v = int(input[ptr])
        ptr +=1
        edges.append((u, v))
    print(count_cycle_components(n, m, edges))

额外优化说明

  1. 增加了提前筛选:对于度数不是2的顶点所在的连通分量,直接遍历标记为已访问,不需要进入is_cycle_component函数,节省了不必要的判断
  2. 输入读取方式优化:原来的splitlines()在处理大输入时可能不如直接split()高效,因为大输入的行分割会有额外开销,改成直接读取所有输入后按空格分割,处理速度更快,避免因输入规模大导致超时
  3. 去掉了原来的edge_count统计,因为我们直接用度数来判断,更准确且高效

这样修改后,就能正确处理所有测试用例了,包括那些带分支的环结构、孤立顶点、多个环等情况。

备注:内容来源于stack exchange,提问作者讗讜讛讚 讙讜诇讚讘专讙

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.14 16:50:26