先说并查集的概念:
并查集处理的是求在树中,求某些元素是否在同一个集合。这种操作不关注同一个集合中某些点的邻接关系,只关心他们是否在同一个集合中。其中的操作有三种分别是合并两个集合,查询一个元素在哪个集合,查询两个元素是否在同一个集合。处理方法用一个数组par[]记录每个节点的根结点,一开始将每个节点的根结点初始化为自身,随后由给出的邻接点修改该数组的值,之后不断地把任意节点直接连到根结点上,把跟节点编号作为这个节点所在集合的编号。查询时利用下标访问数组就能把根结点找出来。
三种操作对应算法:
1.获取根结点:
Int getPar(int a)
{
if(par[a]!=a)
par[a] = getPar(par[a]); //在获取根结点的同时进行压缩
return par[a];
}
2.合并根结点为了避免树的高度太高而导致浪费时间需要设置合并的规则可以把根结点大的合并到根结点小的,也可以根据树的高度把树高度低的合并到树高度的节点这样需要附加一个数组计算该点到根结点的高度。
合并函数:(把根结点大的合并到小的)
void merge(int a, int b)
{
par[a] = par[b] = min(getPar(a), getPar(b));
}
}注:把根结点大的合并到小的有个弊端:
例如 给出两组数 2 3;1 3;如果只用两次合并merge(2, 3); merge(1,3)后会出现2与1,3不在一个集合中,修改的办法是每次合并直接把两个节点的根结点合并,而不是合并两个节点 merge(getPar(2), getPar(3)); merger(getPar(1),getPar(3)); 最后查询a集合时使用if(getPar(i) == a) 而不是if(par[i] == a)因为有些结点可能还没有更新。
Poj1611
题目描述: n个学生分属m个团体,(0 < n<= 30000 ,0 <= m <= 500) 一个学生可以属于多个团体。一个学生疑似患病,则它所属的整个团体都疑似患病。已知0号学生疑似患病,以及每个团体都由哪些学生构成,求一共多少个学生疑似患病。
代码:
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int MAXN = 30000;
int par[MAXN];
int getPar(int c){
if(par[c] != c)
par[c] = getPar(par[c]);
return par[c];
}
void merge(int a, int b){
par[a] = par[b] = min(getPar(a), getPar(b));
}
int main()
{
int m, n, k, a, b, ans, i;
while(1){
scanf("%d%d", &n, &m);
for(i = 0; i < n; i++)
par[i] = i;
if(n == 0 && n == 0)
break;
while(m--){
scanf("%d%d",&k, &a);
k--;
while(k--){
scanf("%d", &b);
merge(getPar(a), getPar(b));
a = b;
}
}
ans = 0;
for(i = 0; i < n; i++)
{
if(getPar(i) == 0)
ans++;
}
printf("%d\n", ans);
}
return 0;
}