1.引入什么是并查集?
导引问题:【犯罪团伙】
警察抓到了n个罪犯,警察根据经验知道他们属于不同的犯罪团伙,却不能判断有多少个团伙,但通过警察的审讯,知道其中的一些罪犯之间相互认识,已知同一犯罪团伙的成员之间直接或间接认识。有可能一个犯罪团伙只有一个人。
请你根据已知罪犯之间的关系,确定犯罪团伙的数量。已知罪犯的编号从1至n。
输入:
第一行:n(<=10000,罪犯数量),
第二行:m(<=100000,关系数量)
以下若干行:每行两个数:I和j,中间一个空格隔开,表示罪犯i和罪犯j相互认识。
输出:一个整数,犯罪团伙的数量。
2.抽象的算法:
◆ 开始把n个人看成n个独立集合。
◆每读入两个有联系的人i和j,查找i和j所在的集合p和q,如果p和q是同一个集合,不作处理;如果p和q属于不同的集合,则合并p和q为一个集合。
◆最后统计集合的个数即可得到问题的解。
3.什么是并查集?
◆并查集是一种树型的数据结构,用于处理一些不相交集合S={S1, S2,…,Sn},每个集合Si都有一个特殊元素root[Si],称为集合的代表元.
◆并查集支持三种操作:
Init(X):集合初始化:把元素xi加到集合Si中。每个集合Si只有一个独立的元素xi,并且元素xi就是集合Si的代表元素。father[xi]=-1(或者xi); 每个结点都是一颗独立的树,-1是该树的代表元素。
void Initial(int father[]){
for(int i=0;i<size;i++)
father[i]=-1;
}
Find(x):查找:查找xi所在集合Si的代表root[Si]。
优化:路径压缩。查找出对应得根节点
int Find(int father[],int x){
while(father[x]>=0)
x=father[x];
return x;
}
Union(int father[],x,y): 合并:把x和y所在的两个不同集合合并。
void Union(int father[],int root1,int root2){
father[root2]=root1;//将根root2连接到跟root1下面
}
4.并查集的压缩
下面是采用路径压缩的方法查找元素:
int find(int x) //查找x元素所在的集合,回溯时压缩路径
{
return father[x]==-1?x: father[x] = find(father[x]); //回溯时的压缩路径
//从x结点搜索到祖先结点所经过的结点都指向该祖先结点
}
上面是一采用递归的方式压缩路径, 但是,递归压缩路径可能会造成溢出栈,下面我们说一下非递归方式进行的路径压缩:(即先找到父节点,再将路径上的所有节点的父节点都设为根节点。)
int find(int x)
{
int k, j, r;
r = x;
while(father[r]>=0) //查找跟节点
r = father[r]; //找到跟节点,用r记录下
k = x;
while(k != r) //非递归路径压缩操作
{
j = parent[k]; //用j暂存parent[k]的父节点
parent[k] = r; //parent[x]指向跟节点
k = j; //k移到父节点
}
return r; //返回根节点的值
}
例题:两个人一组,输入人的组数n,再输入n组人。每组的两个人表示这两个人是好朋友,a得好朋友是b,b得好朋友是c,则c也是a得好朋友。求最大的好朋友团体的人数,并输出。
#include <iostream>
#include <cstdio>
using namespace std;
#define N 10000005
int p[N];
int sum[N];
int Find_set(int x) {//采用了路径压缩
return p[x]==-1 ? x:p[x]=Find_set(p[x]);
}
int main() {
int n;
while (scanf("%d", &n) != EOF) {
memset(p, -1, sizeof(p));
for (int i=1; i<=N; i++)
sum[i] = 1;
while (n --) {
int a, b;
scanf("%d%d", &a, &b);
a = Find_set(a);
b = Find_set(b);
if (a != b) {
p[a] = b;
sum[b] += sum[a];
}
}
int ans = 1;
for (int i=1; i<=N; i++) {
if (p[i]==-1 && sum[i]>ans)
ans = sum[i];
}
printf("%d\n", ans);
}
}