【数据结构•并查集】(理论基础)
一、并查集的定义及运算
在一些问题中,需要根据给出的各个元素之间的联系,将这些元素分成几个集合,每个集合中的元素直接或间接有联系,在这类问题中主要涉及的是对集合的合并和查找,因此将这种集合称之为并查集。并查集的主要操作有:
1、合并两个不相交集合
2、判断两个元素是否属于同一个集合
3、路径压缩
假设有n个不同元素的集合s,这些元素被分成了不相交集合。最初假设每个元素自成一个集合。下面定义一个m次合并(union)和寻找的运算序列,每次执行合并指令后,两个不相交子集合并成一个子集。由观察可知,合并次数最多为n-1次。每个子集中,用一个特殊的元素作为集合的名字或代表。例如集合s={1,2,3,…,11}有4个子集,分别是{1,7,10,11}、{2,3,5,6}、{4,8}、{9},这些子集被标记为1,3,8,9。寻找运算返回一个包含特定元素的名字。例如运算find(11)返回1,即包含元素11的那个集合的名字1。
在程序设计中,并查集可以用树型数据结构来记录,用树来表示每个集合,集合中的元素存贮在节点中,树中除根节点外的每个元素都指向父节p(x)的指针。根有一个空指针,用做集合的名字或集合的代表。这样就产生了一个森林,其中每棵树对应一个集合。
1556645001478328322.bmp
假定元素是整数1,2,3,…,则森林可以方便地用数组a[1…n]来表示,a[j]是元素j的父节点,1 <= j <= n,空的父节点可以用0来表示,如上图所示对应的4个集合{1,7,10,11},{2,3,5,6},{4,8},{9}的4棵树,由于元素是整数,用数组表示更为优越。
二、运算的实现
设定义两个函数:
find(x):寻找并返回包含元素x的集合的名字。
union(x,y):包含元素x和y的两个集合用它们的并集替换。并集的名字或者是原来的包含元素x那个集合的名字,或者是原来包含元素y的那个集合的名字。
对于任意假定元素x,用root(x)表示包含x的树的根。那么find(x)总是返回root(x),由于合并运算必须有两棵树的根作为它的参数,我们假定对于任意两个元素x和y,union(x,y)实际上表示union(root(x),root(y))。
1、按秩合并
合并运算直接实现有一个明显的缺点,就是树的高度变得非常大,大到寻找的运算的时间需要o(n),在极端的情况下,树可以退化。举例说明:u(1,2),u(2,3),…u(n-1,n),再执行find(1),find(2)…find(n),其时间代价就为n+(n-1)+…+1=n^2。为了限制树的高度,采用秩合并措施,给每个节点存贮一个非负数作为节点的秩,记为rank,节点的秩就是它的高度。比如,执行合并运算时union(x,y)时,比较rank(x)和rank(y)
rank(x)
【并查集的应用】家族 vijos 1034
描述 description
若某个家族人员过于庞大,要判断两个是否是亲戚,确实还很不容易,现在给出某个亲戚关系图,求任意给出的两个人是否具有亲戚关系。
规定:x和y是亲戚,y和z是亲戚,那么x和z也是亲戚。如果x,y是亲戚,那么x的亲戚都是y的亲戚,y的亲戚也都是x的亲戚。
输入输出格式
输入格式:
第一行:三个整数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是否具有亲戚关系。
输出格式:
p行,每行一个’Yes’或’No’。表示第i个询问的答案为“具有”或“不具有”亲戚关系。
(注:请注意大小写,'Yes’和’No’的第一个字母为大写,其余的均为小写!!)
输入输出样例
输入样例#1:
6 5 3
1 2
1 5
3 4
5 2
1 3
1 4
2 3
5 6
输出样例#1:
Yes
Yes
No
提示信息
分析:本题的本质: 判断两个量是否在图的同一个连通块中,因为图太庞大, 因此每次需要遍历,因此采用并查集解本题。