连通分量的个数-->并查集的使用

本文介绍了一个使用并查集算法解决无向图连通分量计数问题的C语言程序实现。该程序能够处理多个测试案例,通过输入顶点数和边数及其连接情况来确定无向图中的连通分量个数。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

数据结构实验:连通分量个数
Time Limit: 1000ms Memory limit: 65536K 有疑问?点这里^_^
题目描述
在无向图中,如果从顶点vi到顶点vj有路径,则称vi和vj连通。如果图中任意两个顶点之间都连通,则称该图为连通图,
否则,称该图为非连通图,则其中的极大连通子图称为连通分量,这里所谓的极大是指子图中包含的顶点个数极大。
例如:一个无向图有5个顶点,1-3-5是连通的,2是连通的,4是连通的,则这个无向图有3个连通分量。

输入
第一行是一个整数T,表示有T组测试样例(0 < T <= 50)。每个测试样例开始一行包括两个整数N,M,(0 < N <= 20,0 <= M <= 200)
分别代表N个顶点,和M条边。下面的M行,每行有两个整数u,v,顶点u和顶点v相连。
输出
每行一个整数,连通分量个数。
示例输入
2
3 1
1 2
3 2
3 2
1 2
示例输出
2

1

/*并查集------>定连通分量的个数*/
# include <stdio.h>
# include <memory.h>
struct node
{
    int u;
    int v;
} edges[210];
int parent[22];
int N,M;//N顶点数M边数
int getConnectedComponent();//返回连通分量的个数
void UFset();
void Union(int R1,int R2);
int Find(int x);
int main()
{
    int T,u,v,i;
    int connectedComponent;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d%d",&N,&M);
        for(i=0;i<M;i++)
        {
            scanf("%d%d",&u,&v);
            edges[i].u = u;
            edges[i].v = v;
        }
        connectedComponent = getConnectedComponent();
        printf("%d\n",connectedComponent);
    }
    return 0;
}
int getConnectedComponent()
{
    int num = 0;
    int i,u,v;
    UFset();
    for(i=0;i<M;i++)
    {
        u = edges[i].u;
        v = edges[i].v;
        if(Find(u)!=Find(v))
        {
            Union(u,v);
        }
    }
    for(i=1;i<=N;i++)
    {
        if(parent[i] < 0) // parent[i] < 0 说明为第i个元素该集合的根
        {
            num++;
        }
    }
    return num;
}
void UFset()
{
    int i;
    for(i=0;i<22;i++)
    {
        parent[i] = -1;
    }
}
int Find(int x)
{
    int s;
    int tmp;
    for(s=x;parent[s]>=0;s=parent[s]);
    while(s!=x)
    {
        tmp = parent[x];
        parent[x] = s;
        x = tmp;
    }
    return s;
}

void Union(int R1,int R2)
{
    int r1 = Find(R1);
    int r2 = Find(R2);
    int tmp = parent[r1] + parent[r2];
    if(parent[r1] > parent[r2])
    {
        parent[r1] = r2;
        parent[r2] = tmp;
    }
    else
    {
        parent[r2] = r1;
        parent[r1] = tmp;
    }
}


<think>好的,我需要帮助用户理解如何使用并查集算法计算无向连通分量的数量。首先,我得回顾一下并查集的基本概念。并查集主要用于处理集合的合并与查询问题,对于连通分量来说,每个连通分量可以看作一个独立的集合。初始时,每个节点都是自己的父节点,表示各自独立的集合。随着边的处理,合并相连的节点所属的集合。最终,不同的根节点数量就是连通分量的数量。 接下来,我需要确定具体的步骤。首先是初始化父数组和秩数组,父数组记录每个节点的根节点,秩数组用于优化合并操作。然后,遍历所有的边,对每一条边的两个节点执行查找根节点的操作,如果根不同则合并它们所在的集合。合并时根据秩的大小来决定谁合并到谁下面,以保持树的平衡,提高效率。最后,统计父数组中根节点是自己的节点数量,这就是连通分量个数。 用户可能需要具体的代码示例,所以我要考虑用Python来实现这个过程。代码结构应该包括并查集类的定义,包含初始化、查找、合并等方法。然后处理输入,创建并查集实例,逐步合并边,最后统计连通分量。需要注意的是,节点编号可能从1开始,而Python列表索引从0开始,可能需要调整。 还要考虑一些边界情况,比如没有边的,此时连通分量数量等于节点数。或者有多个独立的连通分量,合并是否正确。此外,路径压缩和按秩合并是优化并查集的关键,能显著提高性能,尤其是在处理大规模数据时。 用户提供的引用中提到了并查集应用和实现,比如引用[1]说明并查集用于处理不相交集合的合并和查询,引用[4]提到使用并查集求无向连通分量。需要确保我的回答符合这些引用中的正确方法。例如,合并时要注意避免重复合并,以及正确统计根节点数量。 可能用户还会关心时间复杂度,所以需要指出路径压缩和按秩合并使得每次操作接近常数时间,整体复杂度约为O(Eα(N)),其中α是阿克曼函数的反函数,增长缓慢,适合处理大数据。 最后,总结步骤,确保逻辑清晰,用户能够逐步理解并实现算法。同时,生成相关问题,帮助用户进一步学习,比如如何优化、时间复杂度分析或其他应用场景。</think>### 使用并查集计算无向连通分量数量的方法 #### **核心原理** 并查集通过维护**动态连通性**来统计连通分量。初始时每个节点自成一个集合,遍历所有边逐步合并相连的节点集合,最终独立集合的数量即为连通分量的数量[^1][^4]。 --- #### **具体步骤** 1. **初始化父数组和秩数组** - 父数组 `parent[i]` 表示节点 `i` 的根节点,初始时每个节点的父节点是自身:$parent[i] = i$ - 秩数组 `rank[i]` 用于优化合并操作,初始为1:$rank[i] = 1$ 2. **查找根节点(路径压缩优化)** ```python def find(self, x): if self.parent[x] != x: self.parent[x] = self.find(self.parent[x]) # 路径压缩 return self.parent[x] ``` 3. **合并集合(按秩合并优化)** ```python def union(self, x, y): root_x = self.find(x) root_y = self.find(y) if root_x != root_y: if self.rank[root_x] > self.rank[root_y]: self.parent[root_y] = root_x else: self.parent[root_x] = root_y if self.rank[root_x] == self.rank[root_y]: self.rank[root_y] += 1 ``` 4. **统计连通分量** - 遍历所有节点,统计根节点仍为自身的节点数量:$count = \sum_{i=1}^{n} [parent[i] == i]$ --- #### **完整代码实现** ```python class UnionFind: def __init__(self, size): self.parent = list(range(size)) self.rank = [1] * size def find(self, x): if self.parent[x] != x: self.parent[x] = self.find(self.parent[x]) return self.parent[x] def union(self, x, y): root_x = self.find(x) root_y = self.find(y) if root_x != root_y: if self.rank[root_x] > self.rank[root_y]: self.parent[root_y] = root_x else: self.parent[root_x] = root_y if self.rank[root_x] == self.rank[root_y]: self.rank[root_y] += 1 def count_components(n, edges): uf = UnionFind(n) for u, v in edges: uf.union(u, v) return sum(1 for i in range(n) if uf.find(i) == i) # 示例输入(假设节点编号从0开始) n = 5 edges = [[0,1], [1,2], [3,4]] print(count_components(n, edges)) # 输出2(两个连通分量:{0,1,2} 和 {3,4}) ``` --- #### **关键点说明** - **时间复杂度**:路径压缩 + 按秩合并后,单次操作接近 $O(\alpha(n))$(阿克曼函数反函数),整体复杂度约为 $O(E \cdot \alpha(N))$ - **空间复杂度**:$O(N)$ - **处理节点编号**:若节点编号从1开始,需调整索引范围(例如代码中 `i in range(1, n+1)`)[^5] --- #### **应用场景** - 网络连接检测(如社交网络好友关系) - 像分割中的区域合并 - 最小生成树算法(如Kruskal算法) ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值