并查集

18 篇文章 0 订阅
16 篇文章 0 订阅

并查集是一个非常精巧的数据结果,可以用来解决等价类划分问题,当时做了一个网上的ACM题和查一些资料才发现,离散数学的作用强大。也就是说现实生活中满足自反性,对称,传递三个性质的问题我们都可以用并查集来解决划分类的问题。比如说求连通子图的问题,某个节点一定属于其所在子图中,满足自反性,A-B相邻,其实B-A也就是说相邻(连通子图是无向的)满足对称性,A-B相邻,B-C相邻,则A-C也是相通的,满足传递性。也就是说生活中的问题,你都可以自己去建模,看他们满不满足这三个性质。

如这样的题目。

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

下面代码分两个,一个是最基本的并查集,后一个是对一些优化,如路径压缩等等。同时根节点即(parent[i]<0),同时-parent[i]表示该集合里面元素的个数。




代码1:

//并查集数据结构主要用于求连通子图,最小生成树的kruskal算法和最近公共祖先(Least Common Ancestors,LCA,等价类问题)
//前提条件:任意两个集合不能相交.
// (1) 不相交集合的并
// (2)  查找某个集合元素所属的集合

#include<stdio.h>
#define MAX_ELEMENTS 100

int parent[MAX_ELEMENTS] ;

/*
 *创建集合,并对集合初始化
 * */
void MakeSet()
{
	int i;
	//	for( i=0 ;i<10 ;i++)
	//		parent[i]=-1;  //从而表示初始化时每个结点都是根节点
	//用于测试
	parent[0]=-4;
	parent[1]=4;
	parent[2]=-3;
	parent[3]=2;
	parent[4]=-3;
	parent[5]=2;
	parent[6]=parent[7]=parent[8]=0;
	parent[9]=4;
}
/*
 *递归
 * */
int Find1Recursive(int i)
{
	if( parent[i]>=0)
		Find1Recursive( parent[i] );
	else
		return i;
}

/*
 *非递归
 * */
int Find1(int i)
{
	while( parent[i]>=0 )
	{
		i=parent[i];
	}
	return i;
}
/*
 *把第一棵树作为第二棵树的子树
 * */
void Union1(int i ,int j)  //刚开始设计的时候暂时没有考虑合并的树的扁平问题,所以合并后根节点的parent[root]没有发生变化
{
	int pID=Find1(i);
	int qID=Find1(j);
	if(pID!=qID)
		parent[pID]=qID;
}

/*
 *
 *对Find1的改进,由于Union操作用到Find操作,所以Find操作的效率至关重要,我们可以通过路径压缩的思想,降低树的高度
 *查找结点i时,从i到根节点的所有结点都使其指向根节点
 * */
int Find2(int i)
{
	int p=i;
	while( parent[i]>=0 )
		i=parent[i]; //此时退出循环找到了根节点i
	while(parent[p]>=0) //使路径上的结点都指向根节点
	{
		int t=parent[p];
		parent[p]=i;
		p=t;
	}
	return i;
}

/*
 *对Union1的改进,把较小结点数的树作为较大树的子树,从而防止树的高度退化为单链表
 * */
void Union2(int i ,int j)
{
	int pID=Find2(i);
	int qID=Find2(j);
	if(pID==qID)
		return ;
	else
	{
		if(parent[pID] < parent[qID]) //如 -9 ,-5
		{
			parent[pID]=parent[pID]+parent[qID];
                        parent[qID]=pID;
		}

		else
		{
			parent[qID]=parent[pID]+parent[qID];
                        parent[pID]=qID;
			
		}
	}
}

void PrintSet()
{
	int i;
	for( i=0 ;i<10 ;i++)
		printf("%d ",parent[i]);
	printf("\n");
}

int main()
{
	MakeSet();
	printf("the original set :\n");
	PrintSet();
	int i=7;
	int s=Find1Recursive(i);
	printf("\n%d belongs to Set %d\n",i,s);
	s=Find1(i) ;
	printf("\n%d belongs to Set %d\n",i,s);
	Union1(1,6);
	printf("after the Find1 and Union1 set :\n");
	PrintSet();


	printf("\n*************\n");
	MakeSet();
	printf("the original set :\n");
	PrintSet();
	i=5;
	s=Find2(i);
	printf("\n%d belongs to Set %d\n",i,s);
	i=9;
	s=Find2(i);
	printf("\n%d belongs to Set %d\n",i,s);

	Union2(1,6);
	printf("after the Find2 and Union2 set :\n");
	PrintSet();

	printf("%d 所属集合的元素的个数:%d\n",i,- parent[ Find1(i) ] );
	return 1;
}





代码2:


//并查集数据结构主要用于求连通子图,最小生成树的kruskal算法和最近公共祖先(Least Common Ancestors,LCA,等价类问题)
//前提条件:任意两个集合不能相交.
// (1) 不相交集合的并
// (2)  查找某个集合元素所属的集合

#include<stdio.h>
#define MAX_ELEMENTS 100

int parent[MAX_ELEMENTS] ;
int count=0;
/*
 *创建集合,并对集合初始化
 * */
void MakeSet(int n)
{
	int i;
	for( i=0 ;i<n ;i++)
		parent[i]=-1;  //从而表示初始化时每个结点都是根节点
}

int Find(int i)
{
	int p=i;
	while( parent[i]>=0 )
		i=parent[i]; //此时退出循环找到了根节点i
	while(parent[p]>=0) //使路径上的结点都指向根节点
	{
		int t=parent[p];
		parent[p]=i;
		p=t;
	}
	return i;
}


int Union(int i ,int j)
{
	int pID=Find(i);
	int qID=Find(j);
	if(pID==qID)
		return ;
	else
	{
		if(parent[pID] < parent[qID]) //如 -9 ,-5
		{
			parent[pID]=parent[pID]+parent[qID];
			parent[qID]=pID;
		}

		else
		{
			parent[qID]=parent[pID]+parent[qID];
			parent[pID]=qID;

		}
                count--;
	}
}



int main()
{
	int n;
	printf("input a number represent n people:");
	scanf("%d",&n); //输入n个人
        count=n;    //统计集合的个数
	MakeSet(n);
	int m;
	printf("input a number represent m relationship:");
	scanf("%d",&m); //输入m,m表示m对亲戚关系,抽象来看,即m对等价类,满足自反性,对称性,传递性
	int i;
	int relation1,relation2;
	printf("input m 个relation1 relation2:\n");
	for(i=0 ;i <m ;i++)
	{
		scanf("%d %d",&relation1,&relation2);
		Union(relation1 ,relation2) ;
	}
	printf("input end");
	printf("input u v ,check they are relationship :");
	int u,v;
	scanf("%d %d",&u,&v);
	if(Find(u)== Find(v) )
		printf("YES,%d and %d they are relationship\n",u,v);
	else
		printf("NO,%d and %d they are not relationship\n",u,v);
	printf("input a num ,check how many people they know each other:");
	scanf("%d",&i);
	printf("%d 所属集合的元素的个数:%d\n",i, -parent[ Find(i) ] );
        printf("集合的个数:%d\n",count );
	return 1;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值