并查集 Union-Find-Set

  先看这样一个问题:若某个家族人员过于庞大,要判断两个是否是亲戚,确实还很不容易,给出某个亲戚关系图,求任意给出的两个人是否具有亲戚关系。 规定:x和y是亲戚,y和z是亲戚,那么x和z也是亲戚。如果x,y是亲戚,那么x的亲戚都是y的亲戚,y的亲戚也都是x的亲戚

  这样一类问题,若使用常规的数据结构进行表述和存储,当数据量很大时会带来很大的数据内存开销,因为需要将所有的关系对都描述清楚确实繁琐又容易出错,而且存储量开销大的情况下下进行相关的查找和其他操作也变得时间消耗过大。为了解决这样一类问题,便使得并查集这样一种简单的数学处理方式渐渐走进人们的视野。

  并查集是一种树型的数据结构,用于处理一些不相交集合(Disjoint Sets)的合并及查询问题。在一些有N个元素的集合应用问题中,我们通常是在开始时让每个元素构成一个单元素的集合,然后按一定顺序将属于同一组的元素所在的集合合并,其间要反复查找一个元素在哪个集合中,当所有顶点都处理完之后就可以得到一个或者多个顶点集合,所有相交元素都处于同于集合。并查集能够实现较快的合并和判断元素所在集合的操作,利用这样一种特性,我们可以轻易的求得图是否连通,网络是否连通等问题。

并查集的操作通常有三个: 

1> 初始化:初始化所有的单个元素到单个集合(Init_Set),使得每个元素的root节点都能标志当前集合,常用的方式有,Father[i] = i 或者 Father[i] = 0.

2> 查找: 查找给定的元素所属集合(Find_Set),查找方式就是找到改X元素在所在集合的root标志节点,也就是祖先节点,相同集合的元素拥有相同的祖先节点root,更具这个标记就可以判断不同的元素是否属于相同的集合。

3> 合并:将不相同集合的元素进行合并(Union_Set)。利用第二部查找找到的两个集合的root标记,若两个元素不属于相同的集合即x和y,也就是x和y对应的的root不相同。就可以将x和y集合进行合并,将一个集合的祖先指向另一个集合的祖先,使得他们最终有相同的root标记。

 

代码如下:

int Father[N];

//初始化集合.每个集合的标记都是它本身
void init_set(int n)     
{
	for(int i=0; i < n; i++)        
	{    
		Father[i] = i;        
	}    
}

// 递归查找到x的祖先节点
int find_set(int x)  
{
	while(x != Father[x])
		x = Father[x];
	return x;    
}

// 合并X和Y两个集合
void union_set(int x,int y)  
{
	int xroot = find_set(x);
	int yroot = find_set(y);

	// 不属于相同集合则合并
	if (xroot != yroot) 
	{
		Father[xroot] = yroot;
	}
}


并查集的优化:路径压缩

每一次执行find查找集合的祖先标记节点时,都是最花费时间的,尤其是当所有的合并完成后集合的状态变成一个单支树时,其时间复杂度就变成了O(n),大量的查找将会耗费特别大的代价。如何优化这个时间复杂度么,常规的方法就是采用状态路径压缩,就是将我们找到的集合的祖先节点后,在查找的时候进行路径压缩将改集合的所有子孙节点都指向同一个祖先节点,这样单支结构就变成了多叉树的结构,从而使得后面的每次查找find_set的时间开销都变成了O(1)。如下图所表示的结构变化:


优化代码如下所示:

int Father[N];
int Rank[N];	// 用来标记集合的秩

//初始化集合.每个集合的标记都是它本身
void init_set(int n)     
{
	for(int i=0; i < n; i++)        
	{    
		Father[i] = i;  
		Rank[i] = 0;   // 初始化每个集合的秩为0
	}    
}

// 递归查找到x的祖先节点
int find_set(int x)  
{
	// 每次查找时都将上一次的合并进行路径
	// 压缩从而使得下一次的查找时间变得更短
	if (x != Father[x])
	{
		//回溯压缩路径
		Father[x] = find_set(Father[x]);
	}

	
	return Father[x];
}

// 合并X和Y两个集合
void union_set(int x,int y)  
{
	int xroot = find_set(x);
	int yroot = find_set(y);

	if (xroot == yroot) return;

	if (Rank[xroot] > Rank[yroot])	
	{	// 如果x的秩大于y的,将y指向x
		Father[yroot] = xroot;
	}
	else
	{
		// 当秩相同时 改变其中一个集合的秩
		if (Rank[xroot] == Rank[yroot])
			Rank[yroot]++;
		// 将秩小的集合指向秩高的集合 进行合并
		Father[xroot] = yroot;
	}
	// 不属于相同集合则合并
	if (xroot != yroot) 
	{
		Father[xroot] = yroot;
	}
}


  

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值