并查集(poj1611、2524、2492、1182)

定义:

            并查集是一种树型的数据结构,用于处理一些不相交集合(Disjoint Sets)的合并及查询问题。常常在使用中以森林来表示。

  集就是让每个元素构成一个单元素的集合,并就是按一定顺序将属于同一组的元素所在的集合合并。

主要操作:

     初始化:

        把每个点所在集合初始化为其自身。

  通常来说,这个步骤在每次使用该数据结构时只需要执行一次,无论何种实现方式,时间复杂度均为O(N)。    

          查找:

       查找元素所在的集合,即根节点。

          合并:

        将两个元素所在的集合合并为一个集合。

  通常来说,合并之前,应先判断两个元素是否属于同一集合,这可用上面的“查找”操作实现。

  

最近几天都在做关于“并查集”应用的题,下面是我在OJ刷过的,把代码贴出来和大家看下,最后再总结下应用并查集的心得:(难度由简单到难)

POJ2524:http://poj.org/problem?id=2524

#include<stdio.h>

int father[50001];
int Find(int x)
{
	if(father[x]==x)
		return x;
	else
		return father[x]=Find(father[x]);
}
void Union(int x,int y)
{
	int rx,ry;
	rx=Find(x);
	ry=Find(y);
	father[rx]=ry;
}
void main()
{
	int x,y,rx,ry;
    int n,m,num=0;
	int i;
	while(scanf("%d%d",&n,&m)!=EOF)
	{
		if(n==0 && m==0)
			break;
		num++;
        for(i=0;i<n;i++)
			father[i]=i;
		while(m--)
		{
			scanf("%d%d",&x,&y);
			rx=Find(x);
			ry=Find(y);
			if(rx!=ry)
			{
				Union(x,y);
				n--;
			}
		}
	printf("Case %d: %d\n",num,n);
	}

}
该题是最简单的并查集应用:其中函数
int Find(int x)
{
	if(father[x]==x)
		return x;
	else
		return father[x]=Find(father[x]);
}


在查找最终根节点时运用递归函数在第一次查找时进行了路径优化,即第一次查找是的时间复查度是O(n),n代表查找节点到根节点路径上的节点数,以后再次查找该路径上节点的根节点时的时间复杂度是O(1)。

poj1611:    http://poj.org/problem?id=1611

#include<stdio.h>

int father[30001],a[30001];
int getFather(int x)
{
	if(father[x]==x)
		return x;
	else 
		return father[x]=getFather(father[x]);
}
void Union(int x,int y)
{
	int rx,ry;
	rx=getFather(x);
	ry=getFather(y);
	father[rx]=ry;
}
void main()
{
	int n,m,num;
	int i,ans;
	while(scanf("%d%d",&n,&m)!=EOF)
	{
		if(n==0 && m==0)
			break;
		ans=0;
		for(i=0;i<n;i++)
		{
           father[i]=i;
		}
		while(m--)
		{
           scanf("%d",&num);
		   for(i=0;i<num;i++)
		       scanf("%d",&a[i]);
		   for(i=1;i<num;i++)
			 Union(a[i-1],a[i]);
		}
		for(i=0;i<n;i++)
		{
			if(getFather(i) == getFather(0))
				ans++;
		}
		printf("%d\n",ans);
	}
	
}

该题和上题一样,注意一下怎么进行节点合并就好了。题目意思不难懂代码也易懂!

POJ2492:http://poj.org/problem?id=2492

#include<stdio.h>
int father[2001],rank[2001];
int Find(int x)
{
	int temp;
	
	if(father[x]==x)
		return x;
    temp=father[x];
    father[x]=Find(father[x]);
	rank[x]=(rank[x]+rank[temp])%2;
	return father[x];
}
void Union(int x,int y)
{
	int rx,ry;
	rx=Find(x);
	ry=Find(y);
	father[rx]=ry;
	rank[rx]=(rank[y]-rank[x]+1)%2;
}
void main()
{
	int Case=0,n,m,N;
	int i,flag;
	int x,y,rx,ry;
	scanf("%d",&N);
	while(N--)
	{
		Case++;
		flag=0;
		scanf("%d%d",&n,&m);
		for(i=0;i<=n;i++)
		{
			father[i]=i;
			rank[i]=0;
		}
		for(i=0;i<m;i++)
		{
			scanf("%d%d",&x,&y);
			rx=Find(x);
			ry=Find(y);
			if(rx==ry)
			{
				if(rank[x]==rank[y])
				{
					flag=1;
				}
			}
			else
				Union(x,y);
		}
		if(flag==1)
		{
			printf("Scenario #%d:\n",Case);
			printf("Suspicious bugs found!\n\n");
		}
		else
		{
			printf("Scenario #%d:\n",Case);
			printf("No suspicious bugs found!\n\n");
		}

	}
}

对于该题,我们看下这两个函数:

int Find(int x)
{
	int temp;
	
	if(father[x]==x)
		return x;
    temp=father[x];
    father[x]=Find(father[x]);
	rank[x]=(rank[x]+rank[temp])%2;
	return father[x];
}
void Union(int x,int y)
{
	int rx,ry;
	rx=Find(x);
	ry=Find(y);
	father[rx]=ry;
	rank[rx]=(rank[y]-rank[x]+1)%2;
}

一个是合并函数,和上面两题不同:

      这一题,增加一个rank[]数组,该数组用来记忆节点i与根节点的关系,这是这两天来做并查集,觉得并查集应用最重要的一点,因为一般比较复杂一点的题,就是要找到节点与根节点的关系(这是难点)。另外,就是当我们合并两棵树的时候,将一棵树接到另一棵树上时,就必须要更新这棵树(被合并树)上的节点与新的根节点的关系
,这又是一个难点,而通过这两天的学习,总结了那么一点点,请看下面:

rank[rx]=(rank[y]-rank[x]+1)%2;

在和并时,我们必须更新好被合并树根节点与新的根节点的关系,而接下来:

rank[x]=(rank[x]+rank[temp])%2;

我们在再次查找被合并数上的节点的根节点时,在压缩该节点与新的根节点路径上的节点时,同时跟新该节点与新的根节点的关系。

上面两点往往是并查集应用的难点:

1、寻找节点与根节点的关系;

2、合并一棵树后更新被合并树上节点与新的根节点的关系;

 

最后,接下来是并查集应用的一道难题:验证了上面的难点,该题的难点就是寻找节点与根节点的关系,唉,本人在做这道题时寻找关系的艰辛,就不说了,一言难尽啊!
POJ1182:http://poj.org/problem?id=1182

#include<stdio.h>
int father[50001],rank[50001];
int Find(int x)
{
    int temp;
	if(father[x]==x)
		return x;
	temp=father[x];
	father[x]=Find(father[x]);
	rank[x]=(rank[temp]+rank[x])%3;
	return father[x];
}

void Union(int a,int b,int len)
{
	
     int ra=Find(a);
	 int rb=Find(b);
	 father[ra]=rb;
	 rank[ra]=(rank[b]-rank[a]+3+len)%3;
}

int main()
{
	int i,n,k;
	int d,x,y;
	int rx,ry;
    int ans=0;
	scanf("%d%d",&n,&k);
	for(i=1;i<=n;i++)
		father[i]=i;
     
	while(k--)
	{
		scanf("%d%d%d",&d,&x,&y);
		if(x>n || y>n || (d==2 && x==y))
			ans++;

		else
		{
			rx=Find(x);
			ry=Find(y);
			if(rx==ry)
			{
				if((rank[x]-rank[y]+3)%3 != d-1)
					ans++;
			}
			else 
				Union(x,y,d-1);
		}
	}

	printf("%d\n",ans);
	return 0;
}

好吧,本次并查集的学习,到此就告一段落,重点内容在上面一点点,感兴趣的同学可以稍稍滑动鼠标,上次看看!

    

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值