合并—查找算法

1. 查找-合并算法的解释
查找:检查两个对象是否属于同一个集合。(是否是同一个集合,这是对某些问题的抽象)
合并:用一个集合替代两个对象分别对应的集合。

 

数据结构:使用一个大小为N的数组id[],p和q是连通的如果他们对应的id值一样。
例如:
节点:      i      0   1   2    3   4   5   6   7   8   9  
初始:    id[i]   0   1   2    3   4   5   6   7   8   9
当前值:id[i]   0   1   9    9   9   6   6   7   8   9
根据当前值,可以看出0是一个集合,1是一个集合,2,3,4,9属于一个集合,5,6属于一个集合,7是一个集合,8是一个集合。
可以看出,5,6是连通的,2,3,4,9是连通的。
问题:节点3,6连通么?
查找操作:因为id[3]=9;id[6]=6,不相同,所以3,6是非连通的。
合并操作:对3,6节点对,.把所有和id[3]相同的id数组里元素值设为id[6].

结果:
i   0 1 2 3 4 5 6 7 8 9
id[i] 0 1 6 6 6 6 6 7 8 6
快速查找的方法:查找可在线性时间内完成,但是合并操作需要O(N)操作 。(因为合并操需要遍历数组中的每个元素)
因此,若执行M对节点的连通性的检查,需要O(MN)的操作。

 

#include <stdio.h>
#define N 10
void main()
{
	int i,p,q,t,id[N];
	for(i=0;i<N;i++) id[i]=i;
	while(scanf("%d %d\n",&p,&q))
	{
		if(id[p]==id[q]) continue;
		for(t=id[p],i=0;i<10;i++)
		{
			if(id[i]==t) id[i]=id[q];
			printf("%d %d\n",p,q);
		}
	}
}


 

 

1.2 快速合并
数据结构:使用一个大小为N的数组id[],id[i]表示的是节点i的父节点,节点i的根节点是id[id[id[…id[i]…]]],直到值不变,其实,每一个根节点都是自连接的,即id[i]=i。

(这里使用了树,这个数据结构,注意其构成)

例如:
节点:      i      0   1   2    3   4   5   6   7   8   9 
初始:    id[i]   0   1   2    3   4   5   6   7   8   9
当前值:id[i]   0   1   9    4   9   6   6   7   8   9
例如,根据当前值可以看出,3的根节点是9,5的根节点是6
查找操作:就是检查p和q的根节点是否相同,因此,3,5是不连通的。合并操作:为了合并p和q分别所在的集合,设置节点p的根节点的id值为q节点的根节点。
例如:
i   0 1 2 3 4 5 6 7 8 9
id[i] 0   1   9   4   9   6   6   7   8   6
快速合并的方法:查找可能最坏情况下需要O(N)操作
合并操作,只需要常数操作。
总的来说:执行M对N个节点的查找合并操作,需要O(MN)时间。 

具体代码如下:

#include <stdio.h>
#define N 10
void main()
{
	int i,p,q,t,id[N];
	for(i=0;i<N;i++) id[i]=i;
	while(scanf("%d %d\n",&p,&q)==2)
	{
		for(i=p;i!=id[i];i=id[i]);//这里id[i]代表了i的根节点,就是不断寻找i的根节点id[i],直到i的根节点就是id[i]
		for(j=q;j!=id[j];j=id[j]);//这里同理,上一步是寻找p的根节点,这里寻找q的根节点(这两行都是查找操作)
		if(i==j) continue;//如果p,q的根节点相同,则pq属于同一个集合
		id[i]=j;//使得p的根节点与q的根节点相同
		printf(" %d %d\n",p,q);
	}
}	


1.3 加权快速合并算法

在快速合并的基础上改进。因为快速合并仅仅是把一个节点的根设置为另一个节点的根。这样构成的树可能就比较高(极端情况是一个线性表的形式),因此,要设法减少树的高度,其实很简单,在合并的时候,把树的数目小的合并到数目大的上去就可以了。因此,需要增加一个数组sz来记录这个节点为根的树的大小。
查找操作:和快速合并一样。
合并操作:把小树合并到大树,更新sz数组相应的元素。
部分代码:
int i=root(p),j=root(q);
if(sz[i]<sz[j]) {id[i]=j;sz[j]+=sz[i]};
else        {id[j]=I;sz[i]+=sz[j]};
改进的操作:查找操作O(lgN)时间可以;合并操作O(1);
总的来说:执行M对N个节点的查找合并操作,需要O(MlgN)时间。
还可以继续改进:路径压缩。在快速合并的基础上采用路径合并,即再查找p节点的过程中,把相应的路径上的节点的父节点设置为root(p)。一种简单的替换是仅仅把父节点设置为它的祖父节点就可以。
public int root(int i){
while(i!=id[i]){
id[i]=id[id[i]];
i=id[i];
}

return i;
}
改进2需要的操作:O(MlgN),实际过程中,是线性的,因为lgN是个常数。

#include <stdio.h>
#define N 10
void main()
{
	int i,j,p,q,id[N],sz[N];
	for(i=0;i<N;i++)
	{
		id[i]=i;sz[i]=1;
	}
	while(scanf("%d %d\n",&p,&q)==2)
	{
		for(i=p;i!=id[i];) i=id[i];
		for(j=q;j!=id[j];) j=id[j];
		if(i==j) continue;
		if(sz[i]<sz[j])
		{
			id[i]=j;sz[j]+=sz[i];
		}
		else
		{
			id[j]=i;sz[i]+=sz[j];
		}
    }
	for(int x=0;x<N;x++)
	{
		printf("%d ",id[x]);
	}
}


 


1.4 应用
1)网络连通性的检查;
2)Kruskal’s的最小生成树算法。

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值