首先给出正常的并查集的几种操作的伪代码(普遍情况下的并查集)
Define TYPE//TYPE 表示数据类型,相当于int之类的
Meke_set(x)
x.p=x
Find_set(x)//路径压缩
TYPE y=x
While y.p!=y
Y=y.p
TYPE z=x
While z.p!=z
TYPE MID=z.p
z.p=y
z=MID
return y
Union(x,y)
TYPE z=Find_set(x)
TYPE p=Find_set(y)
p.y=z
并查集的基本思想是 把对象元素划分为一个一个的集合,并对其进行统一操作
并查集有几个特点:
1. 每个集合均有一个代表元素,通过这个代表元素来实现对集合的统一操作
2. 对于一般的并查集实现来说,在同一个集合中,每个元素是不加区分的,这里着重提到了一般,说明有些应用中,并查集的元素是需要加以区分的(见后面的例题“奇数偶数”)
首先分析一下上述两条性质:
问题1:什么样的元素叫做代表元素?什么样的操作叫做统一操作?统一操作就是指同一种操作吗?
问题2:对于元素不加区分的集合来说,统一操作是?元素需要区分开来的集合,统一操作是指?传统的并查集实现方法能适用于区分元素的并查集操作吗?
明确提出上述几个问题后,回答应该是很轻松的
答1:既然是集合,每个元素都可以作为代表元素。
答2:统一操作不是指统一种操作。但是若集合中的元素是不加区分的,那么统一操作是指同一种操作。
答3:传统的并查集实现方法不能适用于对元素加以区分的操作。
除了上述两点性质之外,伪代码中也有一些值得注意的地方。
1. 关于元素类型的描述有什么需要设计或者说可以扩充的地方?
2. 上述的伪代码中,一直都是从子节点访问父节点,有没有什么应用只能通过从父节点访问子节点的方式才能实现呢?
3. 以上的提问都忽略了union和make_set操作,关于这两个操作有什么主意的地方?
下面从几道例题中给出尚未明确解决的问题的答案,这些答案都是不完整的,只能代表一部分用法!!!
例一:团伙
某城市里居住着n个人,任何两个认识的人不是敌人就是朋友,而且满足:
1. 我朋友的朋友就是我朋友
2. 我敌人的敌人就是我的朋友
所有的事朋友的人组成一个团伙。告诉你关于这n个人的m条信息(即某两个人是朋友,某两个人是敌人),请你计算出这个城市里最多有多少个团伙。
解:这是一道标准的并查集的题目,对并查集稍有了解的同学应该都能很快做出来。
例二:代码等式
每一个由元素0和1组成的飞控的序列称为一个二进制代码。一个代码等式就是形如:
X1x2x3。。。xn=y1y2y3。。。yn 的等式。这里的每一个xi,yj 都是一个二进制的数字(0和1)或者是一个变量。每一个变量都是一个有固定长度的二进制代码,它可以在代码等式中取代变量的位置,称这个长度为变量的长度。为了解一个代码等式,需要给其中的变量赋予适当的二进制代码,使得我们用它们替代代码等式只哦那个的相应的变量后,这个等式成立,给出一个代码等式,计算一共有多少组解。
例如,a,b,c,d,e是变量且长度为4,2,4,4,2。考虑等式1bad1=acbe,这个等式共有十六组解。
变量最多26个,且长度少于10。
解:这题也很简单,值得注意的是:在这题的解法中,并查集的作用在于划分变量集合,这是并查集的一个很重要的作用。
下面着重看例三:
例三:奇数偶数
你和你的朋友玩这样一个游戏:
你和你的朋友写下一个由0和1组成的字符串,你从其中选择连续的字串(例如从第三到第五连续的三个数字),然后问他这个子串中 的1的个数是奇数还是偶数。你的朋友回答,然后你继续问类似的问题。
你估计你的朋友的回答中有一些错误,你想证明他的错误。所以,你决定编写一个程序解决这个问题。这个程序会根据你的问题以及他的回答来判断他是否错了。你程序的目标是找到第1个可能的回答错误,也就是说存在一个数字串满足前面所有的问题,但是对当前的问题不满足,而且没有一个数字串满足。
解:
可以做一个基本假设:出现问题的区间[a,b]在之前没有被询问过,(若是被询问过的区间出问题很简单便可知道结果)
由这个假设,出现问题的区间[a,b]肯定 是与之前被询问的某区间[c,d]的推论出现了矛盾。那么,询问[c,d]之后得到的结论就成为了关键
1. 若[c,d]中的1的个数为偶数,那么[c,j][j+1,d]的奇偶性相同。
2. 若[c,d]中的1的个数为奇数,那么[c,j][j+1,d]的奇偶性相异
这两种推论是解决问题的关键。
一个直观的想法是:问过[c,d]区间的状况后,把[c,j][j+1,d]两个区间放在“一块儿”做好在之后一起处理的准备。典型的使用并查集的动机。
但是,传统的并查集似乎不太好支持这个问题,例如:知道上述的[c,j]为奇后, [j+1,d]一定为奇吗?显然不是,很明显在这道题目中,每次“统一操作”需要把集合里的每个元素区别对待。
从这里回到前面的问题二:传统的并查集实现能支持需要把同一个集合中的不同元素区别对待的应用吗??显然是不能的,如果要用并查集解这道题,需要对传统的实现做一些小的修改:1.每个元素不仅要记录其父节点是哪一个,还要记录和其父节点是什么样的关系2.find_set(x)函数中的路径压缩部分需要改为递归实现。3.另外,前面提到的对于TYPE的描述也稍微有所体现(尽管不是很明显)。