【数据结构•并查集】

【数据结构•并查集】(理论基础)
一、并查集的定义及运算
  在一些问题中,需要根据给出的各个元素之间的联系,将这些元素分成几个集合,每个集合中的元素直接或间接有联系,在这类问题中主要涉及的是对集合的合并和查找,因此将这种集合称之为并查集。并查集的主要操作有:
  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

提示信息
  分析:本题的本质: 判断两个量是否在图的同一个连通块中,因为图太庞大, 因此每次需要遍历,因此采用并查集解本题。

你可能感兴趣的:(C++专栏,数据结构,算法)