并查集

概念

首先上一下百科的东西,

并查集,在一些有N个元素的集合应用问题中,我们通常是在开始时让每个元素构成一个单元素的集合,然后按一定顺序将属于同一组的元素所在的集合合并,其间要反复查找一个元素在哪个集合中。这一类问题近几年来反复出现在信息学的国际国内赛题中,其特点是看似并不复杂,但数据量极大,若用正常的数据结构来描述的话,往往在空间上过大,计算机无法承受;即使在空间上勉强通过,运行的时间复杂度也极高,根本就不可能在比赛规定的运行时间(1~3秒)内计算出试题需要的结果,只能用并查集来描述。

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

其实并查集也就跟等价类差不多。

解决方案(请以树的思想来理解value数组)

我们常遇到这么一个问题,两两元素等价,那么与某一个元素等价的元素有哪些呢。

首先记录一个value数组,大小为n+1,其中n为一共有多少元素,索引0我们不使用,value[i]代表i与哪一个元素等价。初始时value[i]=i,因为每个元素都是自己的等价类。

以下函数用来寻找根节点:

int unionsearch(int root) //查找父结点
{
	int son, tmp;
	son = root;
	while(root != value[root]) //我的父节点不是根
		root = value[root];
	while(son != root) //继续寻找父节点,知道找到根为止
	{
		tmp = value[son];
		value[son] = root;
		son = tmp;
	}
	return root; //返回根节点

以下函数用来加入新的关系:

void join(int root1, int root2) //root1与root2是等价的
{
	int x, y;
	x = unionsearch(root1);//找到root1的根节点
	y = unionsearch(root2);//找到root2的根节点
	if(x != y) 
		value[x] = y; //统一根节点,x、y哪一个为根都可以
}

但其实你会发现这样构造出来的数组其实就是多棵树,每棵树的根节点作为等价类的代表,唯一遗憾的是我们想要根据一个元素,找到他的根节点很简单,要想找到全部的等价类却还是很复杂。

因此,出现了路径归并:
对于每一个元素来说,递归寻找他的父节点,找到根为止,然后返回,并把,一路上的节点的值进行更改。

public int compass(int node,int[] value){
		if(value[node]==node) 
			return node;
		else{
			int res=compass(value[node],value);
			value[node]=res;
			return res;
		}
	}

例题

题目

大学的同学来自全国各地,对于远离家乡步入陌生大学校园的大一新生来说,碰到老乡是多么激动的一件事,于是大家都热衷于问身边的同学是否与自己同乡,来自新疆的小赛尤其热衷。但是大家都不告诉小赛他们来自哪里,只是说与谁同乡,从所给的信息中,你能告诉小赛有多少人确定是她的同乡吗?

输入

每个测试实例首先包括2个整数,N(1 <= N <= 1000),M(0 <= M <= N*(N-1)/2),代表现有N个人(用1~N编号)和M组关系;

在接下来的M行里,每行包括2个整数,a,b,代表a跟b是同乡;

当N = 0,M = 0输入结束;

已知1表示小赛本人。

输出

对于每个测试实例,输出一个整数,代表确定是小赛同乡的人数。

样例

输入:
3 1
2 3
5 4
1 2
3 4
2 5
3 2
0 0

输出:
0
4

代码

这里的解决方案并没有使用以上的代码,因为针对特殊的要求,我们取最简单的写法即可,思路并无什么差别。

import java.util.Scanner;

public class 认老乡 {
	public static void main(String[] args) {
		Scanner scan=new Scanner(System.in);
		int n,m;
		//并查集
		while(true){
			n=scan.nextInt();//n个人
			m=scan.nextInt();//m组关系
			if(n==0&&m==0) break;
			
			int[] value=new int[n+1];
			//初始化并查集数组
			for(int i=0;i<value.length;i++){
				value[i]=i;
			}
			
			int args1;
			int args2;
			int tmp;
			
			for(int i=0;i<m;i++){
				args1=scan.nextInt();
				args2=scan.nextInt();
				tmp=value[args2];
				//统一父节点
				for(int j=1;j<value.length;j++){
					if(value[j]==tmp) value[j]=value[args1];
				}
			}
			
			int res=-1;
			for(int i=1;i<=n;i++){
				if(value[i]==value[1])
					res++;
			}
			System.out.println(res);
		}
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值