1、概念
并查集是一种用于处理一些不相交集合(Disjoint Sets)的合并及查询等问题的树形数据结构。
下面我们举个例子来引入对并查集的理解。
大家都看过古惑仔吧,在香港呢分布着很多的帮派,而每个帮派都有一个老大,比如什么洪兴老大蒋天生,铜锣湾陈浩南。各个帮派人数众多,小弟们通过大哥们相连,而大哥的大哥的最终大哥就是共同的老大,所以拐了很多个弯最终还是同个帮派的人,由共同的老大连接着,这些众多的帮派的原理也就是我们所说的并查集。而某两个古惑仔有了矛盾,而不知道对面是哪个帮派的不敢下手,毕竟别打了自己人,所以就需要确认一下, 报出自己直接上级的大哥肯定不稳妥,毕竟一个帮派的大哥有那么多,而帮派老大只有一个,只要看看老大是不是一样就知道是不是属于同一个帮派啦,这里一层层地上溯寻找最终老大的过程就是查找。若某天,一个帮派的老大很佩服另外一个帮派的老大, 自愿做了另外一个老大的小弟(虽然几率很小),这样两个帮派就成了一家人,无论小弟有多少,之后都共同信奉一个老大,这就是合并的过程。
2、基本操作
顾名思义,并查集包含两种基本操作:
(1)查找(Find)。查找元素所在的集合即根节点。
(2)合并(Combine)。先判断两个元素是否在同一集合,若否,则将 两个集合合并。
下面给出代码帮助理解:
int pre[1050];//pre数组用于记录根节点
void Init(int n) //初始化结点,将根节点的pre[i]置为它本身,n为节点数
{
for(int i=0;i<=n;i++)
pre[i]=i;
}
int Find(int x)
{
int r=x;
while(r!=pre[r])//判断是否为根节点
r=pre[r];
return r;//返回根节点
}
void Combine(int x,int y)
{
int fx=Find(x),fy=Find(y);
if(fx!=fy)//若根节点不同
{
pre[fy]=fx;//将其中一个置为另一个的根节点,实现合并
}
}
3、优化
再用刚才的例子,如果小弟们都很喜欢做大哥,小弟又收了小弟,这样一层一层收下去,很容易造成关系复杂,最底层的小弟需要上溯很多层才能找到最后的老大,因此,我们优化一下帮派的结构,将除了老大的所有人都置为老大的小弟,这样就 实现了优化。
4、例题及代码
题目里的桌子数其实就是我们介绍的帮派数,判断多少个根节点就得出了所需的桌子数。下面给出代码及注释
#include <iostream>
#include <stdio.h>
#include <cstring>
using namespace std;
int pre[1050];//pre数组用于记录根节点
int t[1050]={0};//用于判断是否为根节点
void Init(int n) //初始化结点
{
for(int i=0;i<=n;i++)
pre[i]=i;
}
int Find(int x)
{
int r=x;
while(r!=pre[r])//判断是否为根节点
r=pre[r];
int i=x,j;
while(pre[i]!=r)//优化过程,最终pre[i]都指向根节点
{
j=pre[i];
pre[i]=r;
i=j;
}
return r;//返回根节点
}
void Combine(int x,int y)
{
int fx=Find(x),fy=Find(y);
if(fx!=fy)//若根节点不同
{
pre[fy]=fx;//将其中一个置为另一个的根节点,实现合并
}
}
int main()
{
int n,N,M,i,a,b,ans;
scanf("%d",&n);
while(n--)
{
scanf("%d%d",&N,&M);
Init(N);
while(M--) //吸收并整理数据
{
scanf("%d%d",&a,&b);
Combine(a,b);
}
memset(t,0,sizeof(t));
for(i=1;i<=N;i++) //标记根结点
{
t[Find(i)]=1;//同时实现优化
}
for(ans=0,i=1;i<=N;i++)//ans记录所需桌子数
if(t[i])
ans++;
printf("%d\n",ans);
}
return 0;
}
同类例题: