并查集,在一些有N个元素的集合应用问题中,我们通常是在开始时让每个元素构成一个单元素的集合,然后按一定顺序将属于同一组的元素所在的集合合并,其间要反复查找一个元素在哪个集合中。这一类问题近几年来反复出现在信息学的国际国内赛题中,其特点是看似并不复杂,但数据量极大,若用正常的数据结构来描述的话,往往在空间上过大,计算机无法承受;即使在空间上勉强通过,运行的时间复杂度也极高,根本就不可能在比赛规定的运行时间(1~3秒)内计算出试题需要的结果,只能用并查集来描述。
并查集是一种树型的数据结构,用于处理一些不相交
集合(Disjoint Sets)的合并及查询问题。常常在使用中以森林来表示。
两种优化方式:
- 第一个优化是启发式合并。在优化单链表时,我们将较短的表链到较长的表尾,在这里我们可以用同样的方法,将深度较小的树指到深度较大的树的根上。这样可以防止树的退化,最坏情况不会出现。SUB-Find-Set(x)的时间复杂度为O(log N),PROBLEM-Relations时间复杂度为O(N + logN (M+Q))。SUB-Link(a,b)作相应改动。
- 第二个优化是路径压缩。它非常简单而有效。如图所示,在SUB-Find-Set(1)时,我们“顺便”将节点1, 2, 3的父节点全改为节点4,以后再调用SUB-Find-Set(1)时就只需O(1)的时间。
案例:
Description
若某个家族人员过于庞大,要判断两个是否是亲戚,确实还很不容易,给出某个亲戚关系图,求任意给出的两个人是否具有亲戚关系。 规定:x和y是亲戚,y和z是亲戚,那么x和z也是亲戚。如果x,y是亲戚,那么x的亲戚都是y的亲戚,y的亲戚也都是x的亲戚。
Input
第一行:三个整数n,m,p,(n< =5000,m< =5000,p< =5000),分别表示有n个人,m个亲戚关系,询问p对亲戚关系。 以下m行:每行两个数Mi,Mj,1< =Mi,Mj< =N,表示Mi和Mj具有亲戚关系。 接下来p行:每行两个数Pi,Pj,询问Pi和Pj是否具有亲戚关系。
Output
P行,每行一个’Yes’或’No’。表示第i个询问的答案为“具有”或“不具有”亲戚关系。
// 并查集 import java.util.Scanner; public class Main{ static int tree[]; static int n; static int m; static int relationOne; static int relationTwo; public static int findFather(int node){ if(node!=tree[node]){ return tree[node]=findFather(tree[node]); } return node; } public static void main(String args[]){ Scanner reader=new Scanner(System.in); n=reader.nextInt(); m=reader.nextInt(); tree=new int[n+1]; relationOne=reader.nextInt(); relationTwo=reader.nextInt(); for(int i=1;i<=n;i++){ tree[i]=i; } for(int i=1;i<=m;i++){ int one=reader.nextInt(); int two=reader.nextInt(); int oneFather=findFather(one); int twoFather=findFather(two); if(oneFather!=twoFather){ //父结点不同 if(oneFather>twoFather){ //指向更大的结点 tree[twoFather]=oneFather; }else{ tree[oneFather]=twoFather; } } } int relationOneFather=findFather(relationOne); int relationTwoFather=findFather(relationTwo); if(relationOneFather==relationTwoFather){ System.out.println("YES"); }else{ System.out.println("NO"); } } }