概念
首先上一下百科的东西,
并查集,在一些有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);
}
}
}