(一)普通并查集
普通并查集由三部分组成:
(1)一个整型数组
(2)初始化函数,inin
(3)一个查找函数,find
(4)一个合并函数,join
—>1.1 初始化,inin
使每个元素自身成为一个集合,即pre[i]=i。
void inin(int x) //初始化
{
int i;
for (i = 0; i <= x; i++)
{
pre[i] = i;
rank1[i] = 1;
}
}
–>1.2 查找函数,find
查找一个元素的根节点。
int find(int x)
{
int r;
r = x;
while (r != pre[r]) //它的节点不是他本身时。
{
r = pre[r];
}
return r; //返回根节点
}
–>1.3 合并函数,join
把两个元素的根节点合并,形成树
void join(int a, int b)
{
int fa;
int fb;
fa = find(a); //查找a的跟节点
fb = find(b); //查找b的跟节点
if (fa != fb) //当a和b根节点不同时,把他们合并
{
pre[fa] = fb;
}
}
到这里,我们会发现,在执行上面的代码后,形成的树的深度可能会太大,一字长蛇阵也是有可能的,这样查找起来的效率就会很低,当然,在一些题中,数据量小,效率就无所谓,但在一些数据量非常大的题中,效率就很重要。而且形成的树可能会退化。为了避免这个问题,我们对代码做一些改进。
压缩路径:
意思就是让所有的节点都指向根节点,使树的层数在维持一种较低的水平,从而提高查找效率。
代码实现:
#define N 55005
int pre[N]; //树的节点
int rank1[N]; //树的深度
int n, m;
void inin(int x) //初始化
{
int i;
for (i = 0; i <= x; i++)
{
pre[i] = i;
rank1[i] = 1;
}
}
int find(int x) //查找结点x的根结点
{
if (pre[x] == x)
{ //递归出口:x的上级为x本身,即x为根结点
return x;
}
else
return pre[x] = find(pre[x]); //递归查找 此代码相当于 先找到根结点rootx,然后pre[x]=rootx
}
void join(int a, int b) //节点和并
{
int fa;
int fb;
fa = find(a);
fb = find(b);
if (fa == fb)
return ;
else if (rank1[fa] > rank1[fb])
{
pre[fb] = fa;
}
else
{
if (rank1[fa] == rank1[fb])
{
rank1[fb]++;
}
pre[fa] = fb;
}
}
例题:
在三角洲不同地区生活的人们有自己信仰的图腾,为了避免冒犯到他们,首脑们现在想知道他们最多可能有多少种图腾。但这对他们来说是件无趣的事,所以现在任务交到了你的身上。 当你看到两个地区的人们在膜拜同一外观的石像时,就可以断定他们拥有同样的图腾。已知三角洲共有n(n <= 50000)块区域,你看到了m(m<=n(n-1)/2)组地区的人们在膜拜同样的石像,现在请问他们至多有多少种图腾。
Input
有多组数据。对于每组数据:
第一行:两个整数n和m。
以下m行:每行包含两个整数i和j,表示你发现i地区和j地区的人们在膜拜同样的石像。地区编号从1到n。
输入的最后一行中,n = m = 0。
Output
对于每组测试数据,输出一行,输出数据序号( 从1开始) 和图腾的最大数量。(参见样例)
Sample Input
10 9
1 2
1 3
1 4
1 5
1 6
1 7
1 8
1 9
1 10
10 4
2 3
4 5
4 8
5 8
0 0
Sample Output
Case 1: 1
Case 2: 7
#include<iostream>
#include<cstdio>
using namespace std;
#define N 55005
int pre[N]; //树的节点
int rank1[N]; //树的深度
int n, m;
int sum;
void inin(int x) //初始化
{
int i;
for (i = 0; i <= x; i++)
{
pre[i] = i;
rank1[i] = 1;
}
}
int find(int x) //查找结点x的根结点
{
if (pre[x] == x)
{ //递归出口:x的上级为x本身,即x为根结点
return x;
}
else
return pre[x] = find(pre[x]); //递归查找 此代码相当于 先找到根结点rootx,然后pre[x]=rootx
}
void join(int a, int b) //节点和并
{
int fa;
int fb;
fa = find(a);
fb = find(b);
if (fa == fb)
return ;
else if (rank1[fa] > rank1[fb])
{
pre[fb] = fa;
sum--;
}
else
{
if (rank1[fa] == rank1[fb])
{
rank1[fb]++;
}
pre[fa] = fb;
sum--;
}
}
int main()
{
int a, b;
int k=0;
while (cin >> n >> m&&n&&m)
{
k++;
inin(n);
sum = n;
while (m--)
{
cin >> a >> b;
join(a, b);
}
cout << "Case ";
cout << k << ':';
cout <<" "<< sum << endl;
}
return 0;
}
并查集算法的用途;
1、维护无向图的连通性。支持判断两个点是否在同一连通块内,和判断增加一条边是否会产生环。
2、用在求解最小生成树的Kruskal算法里。
下面是大佬对并查集的讲解:
普通并查集