并查集

**********************************************************************/jacobian

一.并查集的概念

并查集是一种不相交的数据结构,维护了一个不相交动态集的集合S={S1,S2,…, Sk},值得注意的是,Si也是一个集合,我们用集合Si的一个成员(代表)来标识集合Si

首先强调的是,用一个成员表示一个集合。在某些实际应用中,我们关心的是,对于给定的元素,可以很快的找到这个元素所在的集合(的代表),以及合并两个元素所在的集合(两个元素不在同一个集合)

 

指针上述描述,我们定义三种method:


1.MAKE_SET(x) :建立一个新的集合,它的唯一成员(也是代表)是x本身。因为各个集合是不相交的,因而x不会出现在别的某个集合中。

 

2.UNIONxy:将包含xy的两个动态集合(表示为Sx,Sy)合并成一个新的集合,即这个两个集合的并集,这个操作要求,xy不在同一个集合。

 

3.Find_Set(x):找到元素 x 所在的集合的代表,该操作也可以用于判断两个元素是否位于同一个集合,只要将它们各自的代表比较一下就可以了。

1.动态集合用树形结构示例

其中,第一个集合的成员{a,b,c,d},代表为a,第二个集合的成员{e,f,g}代表为e

 

二.并查集的函数操作

2.1

树的节点表示集合中的元素,箭头指向父节点。根节点的指针指向自己。沿着每个节点的父节点不断向上查找,最终就可以找到该树的根节点,即该集合的代表元素。

在构造不相交动态集的集合时,集合的每个元素(也是集合)只包括一个元素,因为只有唯一的元素,此元素的父节点就是本身,所以箭头指向本身。(不相交动态集的集合每个元素都是一个单元素集合,即父节点是其自身),如下图所示。

                                                       图2

 

 

2.2查找和路径压缩

                                                 图3

在查找操作中,如果每次都沿着父节点向上查找,那时间复杂度就是树的高度,完全不可能达到常数级。这里需要应用一种非常简单而有效的策略——路径压缩。

路径压缩,就是在每次查找时,令查找路径上的每个节点都直接指向根节点。


2.3 动态集的合并我们都以树形结构来描述

合并的总体思路:从下图我们可以看出,就是将一个集合的树根指向另一个集合的树根。

 


                                                     图4

2.3.1节点x的秩

节点x的秩代表了节点x的高度(只有一个节点的树,根节点的高度为0,高度为:节点x到某一后代叶节点的最长简单路径上边的数目)的一个上界


2.3.2按秩合并算法

 

case1.如果根节点没有相同的秩,让较大秩的根成为较小秩的根的父节点,但根的秩本身保持不变。(如下图所示)

                                                     图5

解释:根为a的集合{a,b,d}(根a的秩为2)合并根为c集合{c}(根c的秩为0),合并之后的树如上图,新的树根为a(根a的秩为2),其高度可以为2(路径:a->b->d)也可以为1(路径:a->c),所以秩描述的节点的高度的上界,秩没有发生变化。


case2.如果根有相同的秩,任意选择两个根中的一个作为另个一个集合根的父节点,并使新的根节点的秩加1。(如下图所示)

                                                 图6

解释:根为a的集合{a,b,d,c}(根a的秩为2)合并根为e集合{e,f,g}(根e的秩为2),合并之后的树如上图,新的数根为a(根a的秩为3),其高度可以为2(路径:a->b->d)也可以为1(路径:a->c),还可以为3(路径:a->e->f->g)所以秩描述的节点的高度的上界,其根a的秩加1。


2.3.4 合并方法2

按集合元素个数多少进行合并:元素少的集合的并到元素多的集合,让元素多的集合的根成为元素少的集合的根的父节点,并重置元素多集合的元素个数为两个集合元素个数之和。

 

按集合包含的元素个数(或者说树中的节点数)合并,将包含节点较少的树根,指向包含节点较多的树根。这个策略与按秩合并的策略类似,同样可以提升并查集的运行速度,而且省去了额外的 rank 数组。

初始时所有集合的唯一元素x其parent[x]=-1;

注意:

对于节点x,若 parent[x]的值是正数,则表示该元素的父节点的索引;

若是负数,则表示该元素是所在集合的代表(即树根),而且值的相反数即为集合中的元素个数




补充讲解:

路径压缩的两种方法的思路(以秩合并方法示意):

1.递归法:

如果x等于parent[x]说明节点x就是根节点,直接返回就可以了

如果x不等于parent[x],说明x不是根节点,只需要将x的父节点设为根节点即可,递归总是把问题的规模往小的方向递推

Parent[x]=Find_Set(Parent[x])

2.迭代法;

先找到根节点,在沿着路径回溯,把路径上的节点的父节点都置为根节点



程序代码示例(都是用的全部变量示例的,可以把parent,rank作为函数的参数)

程序1:用秩合并(合并奇偶下表)

#include<stdlib.h>
#include<stdio.h>
#define MAX_SIZE 100
int Parent[MAX_SIZE];//父节点的序号
int Rank[MAX_SIZE];//全局变量,集合的秩
void Make_Set(int size)
{
	int i;
	for(i=0;i<size;++i)
	{
		Parent[i]=i;
		Rank[i]=0;
	}
}

int Find_Set(int x)
{
int p=x;//用p来保存根
while(Parent[p]!=p)
{
	p=Parent[p];
}
while(x!=p)
{
	int temp=Parent[x];//x的父节点,沿路径回溯,回溯过程中压缩路径
	Parent[x]=p;
	x=temp;//x沿路径回溯,移动到父节点
}//压缩路径
return x;
}

//递归
/*
int Find_Set(int x)
{
if(x!=Parent[x])
{Parent[x]=Find_Set(Parent[x]);}
return Parent[x];
}
*/
void Union(int x,int y)
{
int Root1,Root2;
Root1=Find_Set(x);
Root2=Find_Set(y);
if(Root1==Root2) return ;
else
{
if(Rank[Root1]<Rank[Root2])//按集合所在集合的代表(根)节点的秩合并, 秩大的集合的代表做秩小的集合代表的父节点
	Parent[Root1]=Root2;
else
{
	if(Rank[Root1]==Rank[Root2])
		Rank[Root1]++;
	Parent[Root2]=Root1;
}
}
}
void main()
{
	int size=10;
	Make_Set(size);
	int i;
	for(i=1;i<size/2;++i)
	{
	Union(2*i-1,2*i+1);
	Union(2*i-2,2*i);
	}
for(i=0;i<size;++i)
	printf("%d,%d\n",Parent[i],Rank[i]);
}

2.程序二:

#include<stdlib.h>
#include<stdio.h>
#define MAX_SIZE 100
int Parent[MAX_SIZE];//父节点的序号
void Make_Set(int size)
{
	int i;
	for(i=0;i<size;++i)
	{
		Parent[i]=-1;//abs(Parent[Root])存的是代表元素为Root的集合的元素个数,值得注意的是Root一定的是根元素
	}
}
//迭代法
/*
int  Find_Set(int x)
{
int p=x;//用p来保存根
while(Parent[p]>=0)
{
	p=Parent[p];
}
while(x!=p)
{
	int temp=Parent[x];//x的父节点,沿路径回溯,回溯过程中压缩路径
	Parent[x]=p;
	x=temp;//x沿路径回溯,移动到父节点
}//压缩路径
return x;
//return Root;
}
*/
//递归

int  Find_Set(int x)
{
if(Parent[x]<0) return x;
else
Parent[x]=Find_Set(Parent[x]);
return Parent[x];
}

void Union(int x,int y)
{
int Root1,Root2;
Root1=Find_Set(x);
Root2=Find_Set(y);
if(Root1==Root2) return ;
else
{
if(Parent[Root1]<Parent[Root2])//按集合的元素个数合并, 元素少的集合的并到元素多的集合
	{
	Parent[Root1]+=Parent[Root2];
    Parent[Root2]=Root1;
    }
else
{
	Parent[Root2]+=Parent[Root1];
	Parent[Root1]=Root2;
}
}
}

void main()
{
	int size=10;
	Make_Set(size);
	int i;
	for(i=1;i<size/2;++i)
	{
	Union(2*i-1,2*i+1);
	Union(2*i-2,2*i);
	}
for(i=0;i<size;++i)
	printf("%d,",Parent[i]);
printf("\n");
for(i=0;i<size;++i)
{
	if(Parent[i]<0)
		printf("%d\n",-Parent[i]);
}
}







评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值