我理解的并查集:
其实并查集就是用数组或结构数组静态的表示内部相互联系的集合吧,类似于树。看个简单的例子:
可以看出,5的父节点是2,所以S[5]=2,原理就是用s[data]=parent类似树的形式让子元素的数组值等于其父元素来建立起元素之间的联系,根节点的数组值为-1。
查找与联合(并)的实现:
SetType S[maxsize];
ElementType Find(SetType S,ElementType X)
{//返回查找元素的根节点
for(;S[X]>=0;X=S[X]);//结合上面的例子体会
//当X等于根节点的值时s[X]=-1,退出循环返回查找元素的根节点。
return X;
}
void Union(SetType S,SetType Root1,SetType Root2)
{
S[Root2]=Root1;//这里默认让Root2的集合插入到Root1集合里
}
显然在并的时候这样默认插入的话,会导致集合的不平衡进而使查找的效率变低,与二叉搜索树类似的问题,我们需要像平衡二叉树那样来解决这个问题。
按秩归并:
为了解决上面这个问题,我们可以增加对两个集合高度的比较,显然由于我们只是简单的用数组表示关系,无法像结构体那样增加一个高度成员,但是之前我们让根元素的数组值为-1,只是单纯代表它是根元素,我们可以让根元素的数组值为(-集合的高度)/(-集合元素的个数),这就是所谓的秩,让对应值的小的集合插入到大的集合中。
按集合的高度:
SetType S[maxsize];
void Union(SetType S,SetType Root1,SetType Root2)
{//注意根节点的负的高度!!!
if(S[Root2]<S[Root1])
S[Root1]=Root2;
else{
if(S[Root2]==S[Root1]) S[Root1]--;//高度相同时默认Root2插入到Root1中
S[Root2]=Root1;
}
按集合元素的个数:
SetType S[maxsize];
void Union(SetType S,SetType Root1,SetType Root2)
{//注意根节点的负的高度!!!
if(S[Root2]<S[Root1])
S[Root2]+=S[Root1];
S[Root1]=Root2;
else{
S[Root1]+=S[Root2];
S[Root2]=Root1;
}
额外的思考:
(1)集合的高度
这个高度有可能在定义时就知道了,也有可能不知道,所以还是讨论一下如何获得一个集合的高度:
我认为在集合中查找所有元素,记录所有叶子元素到根元素的结点数,最大的就是一个集合的高度。
int GetHeight(int S[],int N)
{
int numh=1,maxh=0;
for(int i=0;i<N;i++){
int tmp=S[i];
for(;tmp>=0;tmp=S[tmp])//就是在find的时候顺便高度++;
numh++;
if(maxh<numh) maxh=numh;
numh=1;
}
return maxh;
}
这样时间复杂度是O(nlogn),显然多捞啊,我想的是用一个容器来储存每一步的S[tmp](也就是非叶子节点),因为只有叶子结点才可能得到最大的高度,只要不是叶子结点就不用进行查找。但这样复杂度也不低,所以在不知道高度的前提下。。。。。。
(2)求集合个数
对于每个并查集的数组,可能存在多个根节点,根节点的数组值小于0但小于0的不一定是根节点,所以不能单纯遍历判断数组值正负来计算集合个数。
int s[]={......};
int count_sets(int N)
{
int cnt = 0;
for (int i = 0; i < N; i++)
if (Find(S,i) == i)//结合上面的例子理解!!!
cnt++;
return cnt;
}
路径压缩:
如果我们按照集合元素的个数来进行集合的合并,也就是说根节点的数组值是 - 集合元素个数,此时我们可以通过改变Find函数来改变集合的路径:
SetType Find(SetType S,ElementType X)
{
if(S[X]<0)
return X;
else
return S[X]=Find(S,S[X]);
}
结合图来理解这个做法!!!我们不只是查找X的根节点,而是边查找边把经过的父节点都变成根节点的子节点。这就是路径压缩。如果我们需要反复的使用Find,反正返回的都是根节点,用了路径压缩后就会很方便。