1.概述
英文:DisjointSet or(Union-find set),即“不相交集合”将编号分别为1…N的N个对象划分为不相交集合,在每个集合中,选择其中某个元素代表所在集合。
常见两种操作:
2.导引问题
注意:两个城市之间可以有多条道路相通,也就是说
3 3
1 2
1 2
2 1
这种输入也是合法的
当N为0时,输入结束,该用例不被处理。
如果遇到这题,我们该如何去做呢?
3.并查集引入
初始化操作:
我们需要一个 void makeset (int n)n表示初始化的范围
void makeset(int n)
{
for(int i = 0; i <= n; i++)
father[i] = i;
}
查找操作:
假如我们现在有要在这个集合中寻找一个元素x所处的集合(该元素的符节点),那么我们只需要一个 int findset(int a)函数
//递归写法
int findset(int a){
if(a==father[a]) return a;
return findset(father[a]);
}
//迭代写法
int findset(int x){
int p = x;
while(p!=father[p]) p = father[p];
return p;
}
findset返回的值为该元素根节点的下标
合并操作:
假如 x 和 y开始是属于不同集合的元素,现在要把它们并到一起,那么需要一个 void unionset (int x , int y)函数
void unionset(int x, int y)
{
int a = findset(x);
int b = findset(y);
if(a != b) father[a] = b;
}
有了以上关于并查集的基础,相信都可以写HDU1232这道题了。。
#include <iostream>
#define SIZE 10005
using namespace std;
int father[SIZE];
void makeset(int n)
{
for(int i = 0; i <= n; i++)
father[i] = i;
}
int findset(int a){
if(a == father[a])
return a;
return findset(father[a]);
}
void unionset(int x, int y)
{
int a = findset(x);
int b = findset(y);
if(a != b) father[a] = b;
}
int main()
{
int n, m;
int x, y;
int ans = 0;
while(cin>>n>>m)
{
makeset(n);
for(int i = 1; i <= m; i++)
{
cin>>x>>y;
unionset(x,y);
}
for(int i = 1; i <= n; i++)
{
if(father[i] == i)
ans++;
}
ans--;
cout<<ans<<endl;
ans = 0;
}
}
4.并查集的优化
Find_Set(x)时 路径压缩
寻找祖先时我们一般采用递归查找,但是当元素很多亦或是整棵树变为一条链时,每次Find_Set(x)都是O(n)的复杂度,有没有办法减小这个复杂度呢?
答案是肯定的,这就是路径压缩,即当我们经过"递推"找到祖先节点后,"回溯"的时候顺便将它的子孙节点都直接指向祖先,这样以后再次Find_Set(x)时复杂度就变成O(1)了,如下图所示;可见,路径压缩方便了以后的查找。
Union(x,y)时 按秩合并
即合并的时候将元素少的集合合并到元素多的集合中,这样合并之后树的高度会相对较小。
int findset (int a)
//递归写法
int findset(int a){
if(a==father[a])
return a;
else {
int temp = father[a];
father[a] = findset(father[a]);
}
return father[a]; //father[a] 已经是根节点,直接返回就行了
}
//非递归的方式进行路径压缩,更加直观一些
int findset(int x){
int p = x,temp;
while(p!=father[p]) p = father[p];
//fatherpath comfatherression
while(x != p){
temp = father[x];
father[x] = p;
x = temp;
}
return p;
}
5.最终模板
void makeset(int n){
int i;
for(i=1;i<=n;i++){
father[i] = i;
rank[i] = 1;
}
}
int findset(int a){
if(a==father[a])
return a;
else {
int temp = father[a];
father[a] = findset(father[a]);
rank[a] = (rank[temp]+rank[a]+1)%2 ;
//必须有,更新路径压缩之后a与根结点之间的关系; father改变,rank就必须要跟着改变
}
return father[a];
}
void unionset(int a,int b){
int fa,fb;
fa = findset(a);
fb = findset(b);
if(fa!=fb){
father[fa] = fb;
rank[fa] = (rank[a]+rank[b])%2 ; //fa结点以下的结点的rank不需要改
}
}
6相关题目练习
待更新
参考目录:
http://www.cnblogs.com/cherish_yimi/archive/2009/10/11/1580839.html