不相交集是一种用于描述等价问题的有效的数据结构,它的实现非常简单。对于一个不相交集的操作只有两种,一种合并操作,将两个集合合并为一个等价问题,另一种便是find,它返回的是给定元素的集合的名字。
对于不相交集我们用一个数组来实现即可,每个数组元素的下标代表着这个元素的值,而数组内的具体内容则是其指向的父节点。每个等价集合都会有一个最终的根节点,它无需再指向谁,这时一种做法是,我们在里面放置一个负值,负值的绝对值代表着这个等价集的深度。这样我们再合并两个等价集的时候便可以将深度小的集合合并到大的里面去,这样可以优化算法性能。
一个不相交集以及相关操作函数的申明可以是这样的:
typedef int * disjoint_set;
typedef int set_type;
typedef int element_type;
void init(disjoint_set);
void set_union(disjoint_set s,set_type root1,set_type root2);
void find(element_type x,disjoint_set s);
为了直观起见,我们给了int三个别名。一个不相交集的操作有三个,第一个是初始化,第二个是合并,第三个是查找。注意的是需要预先给出大小,将dijoint_set的空间申请好。
不相交集的初始化操作是这样的:
void init(disjoint_set s)
{
int i;
for(i=num_of_sets;i>0;i--)
s[i]=0;
} 我们将数组内的元素都赋值为零,此时表示谁都没有指向谁,大家全是分散的集合,只有和自己形成了等价关系。
然后一个不相交集的求并算法,即将两个集合划归为等价集合的操作是这样的,我们按照高度合并,高度低的合并到高度高的集合里面去:
void set_union(disjoint_set s,set_type root1,set_type root2)
{
if(s[root2]<s[root1])
s[root1]=root2;//将根节点保存一个负值,显示为树的深度
else
{
if(s[root1]==s[root2])
s[root1]--;//如果深度相同,则合并后深度加一
s[root2]=root1;
}
} 然后是find操作,通过find操作我们不仅可以判断两个元素是否等价,还可以对union操作进行支持,因为它返回的是根:
set_type find(element_type x,disjoint_set s)
{
if(s[x]<=0)
return x;
else
return s[x]=find(s[x],s);
}//注意的是,这里的find包含了路径压缩操作
find是一个递归的操作,如果当前的数组里面的元素小于等于0,那么便是根了,如果不是那么我们便递归的使用自己下一个指向去执行即可。因为find操作是返回根,那么根不是自己,肯定就是通过对自己指向元素执行find操作所返回的值了。注意的是我们这里取了一个巧,用到了路径压缩。当找到根后,从当前节点到根路径上的所有节点都会变为直接指向根,这也是一个优化。
当然如果我们的等价关系中需要一些其他信息,比如字符,那么单纯的数字便行不通了,一种可能的想法是为这些字符和数字之间建立一个映射关系,那么便有可以转化为用数组来进行操作了。
不相交集是解决等价问题的数据结构,通过合并操作和find操作进行管理。采用数组实现,每个元素指向父节点,根节点用负值表示集合深度。初始化时,所有元素指向自身。合并操作根据高度合并,优化性能。若处理包含其他信息的等价关系,可通过映射关系转换为数组操作。
969

被折叠的 条评论
为什么被折叠?



