查并集是一个很重要的数据结构,特别适合解决一些类似于图,集合的问题。比如这个:http://acm.hdu.edu.cn/showproblem.php?pid=1232
什么是并查集?
为了解释并查集的原理,我将举一个更有爱的例子。 话说江湖上散落着各式各样的大侠,有上千个之多。他们没有什么正当职业,整天背着剑在外面走来走去,碰到和自己不是一路人的,就免不了要打一架。但大侠们有一个优点就是讲义气,绝对不打自己的朋友。而且他们信奉“朋友的朋友就是我的朋友”,只要是能通过朋友关系串联起来的,不管拐了多少个弯,都认为是自己人。这样一来,江湖上就形成了一个一个的群落,通过两两之间的朋友关系串联起来。而不在同一个群落的人,无论如何都无法通过朋友关系连起来,于是就可以放心往死了打。但是两个原本互不相识的人,如何判断是否属于一个朋友圈呢?
我们可以在每个朋友圈内推举出一个比较有名望的人,作为该圈子的代表人物,这样,每个圈子就可以这样命名“齐达内朋友之队”“罗纳尔多朋友之队”……两人只要互相对一下自己的队长是不是同一个人,就可以确定敌友关系了。
但是还有问题啊,大侠们只知道自己直接的朋友是谁,很多人压根就不认识队长,要判断自己的队长是谁,只能漫无目的的通过朋友的朋友关系问下去:“你是不是队长?你是不是队长?”这样一来,队长面子上挂不住了,而且效率太低,还有可能陷入无限循环中。于是队长下令,重新组队。队内所有人实行分等级制度,形成树状结构,我队长就是根节点,下面分别是二级队员、三级队员。每个人只要记住自己的上级是谁就行了。遇到判断敌友的时候,只要一层层向上问,直到最高层,就可以在短时间内确定队长是谁了。由于我们关心的只是两个人之间是否连通,至于他们是如何连通的,以及每个圈子内部的结构是怎样的,甚至队长是谁,并不重要。所以我们可以放任队长随意重新组队,只要不搞错敌友关系就好了。于是,门派产生了。
并查集的优化:路径压缩
这样可以将查询一个结点的根节点的时间复杂度降到O(1)。
实现
#include <iostream>
using namespace std;
//查找根节点
int find(int * pre,int length,int v){
int _v=v;
int root;
if(pre[_v]==_v){
return v;
}
while(pre[_v]!=_v){
_v=pre[_v];
}
root=_v;
//顺便完成路径压缩
_v=v;
while(pre[_v]!=_v){
int temp=pre[_v]; // 在改变上级之前用临时变量 j 记录下他的值
pre[_v]=root;//把上级改为根节点
_v=temp;
}
return root; //返回根节点 r
}
//判断v1 v2是否连通
//如果已经连通,就不用管了,如果不连通,就把它们所在的连通分支合并起,将v2合并到v1里面
void join(int * pre,int length,int v1,int v2){
int root_v1=find(pre,length,v1);
int root_v2=find(pre,length,v2);
if(root_v1==root_v2){
return ;
}else{
pre[v2]=root_v1;
}
}
void print(int * pre,int length){
for(int i=1;i<length;i++){
cout<<pre[i]<<" ";
}
cout<<endl;
}
int main(){
const int vertex=4;
const int n=vertex+1;
int pre[n];
//初始化
for(int i=1;i<n;i++){
pre[i]=i;
}
print(pre,n);
join(pre,n,1,2);
join(pre,n,3,4);
print(pre,n);
//判断有几个根节点
int arr_root[n];
//初始化为0
for(int i=0;i<n;i++){
arr_root[i]=0;
}
//将是根节点的点置为1
for(int i=1;i<n;i++){
arr_root[find(pre,n,i)]=1;
}
//统计一下置为1的结点的个数
int ans=0;
for(int i=1;i<n;i++){
if(arr_root[i]==1){
ans++;
}
}
cout<<ans<<endl;
system("pause");
return 0;
}
关于数据结合和算法的其他文章:
这又是一个flag //www.greatytc.com/p/1dc543da3897
update:
在论坛上面看见的一个问题,用并查的思想可以很快的算出来。