欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

不相交集(并查集)

程序员文章站 2022-03-15 19:19:08
...

1、不相交集(两集合中没有相交的元素):是一种解决等价问题的有效数据结构,这种数据结构实现起来非常简单,每个例程只要几行代码,而且可以使用一个简单的数组实现。实现也非常快,每种操作只需要常数平均时间。

只能进行合并和查找的所求元素的所在的集合,因此也被称为并查集。

2、等价关系 等价关系必须满足下面三个性质:
(1):自反性,对于集合S中的任意元素a,a R a;(R为定义的关系,比如R为<=, >=等等)
(2);对称性,a R b当且仅当b R a
(3):传递性,若a R b且b R c,则a R c

3、动态等价性问题

集合S中元素a的等价类是集合S的一个子集,该等价类中包含所有与a有等价关系的元素。所以为确定a是否等价b,只需要验证a和b是否属于同一个等价类中。

find操作,它返回包含给定元素的等价类集合的名字。比如:find(a) == find(b),那么a和b处于同一个集合中。
添加操作,如果想添加关系a ~ b,那么首先要判断a和b是否有关系(可以通过find操作验证它们是否在同一个等价类中)。如果a和b不在同一个等价类中,那么要使用求并操作union,这种操作将含有a和b的两个等价类合并为一个等价类。把这种算法称为不相交集合的求并/查找算法。该算法是动态的,因为在算法的执行过程中,集合可以通过union操作而发生改变。

解决动态等价性问题的方案有两种,第一种方案可以保证指令find能够以常数最坏情形运行时间执行,而另一种方案可以保证操作union能够以常数最坏情形运行时间执行。但是两个操作不能同时以常数最坏情形时间执行。

第一种方案:为使find操作快,可以在一个是数组中保存每个元素的等价类的名字。此时find就是简单的O(1)查找。设执union(a,b),并设a在等价类i中,b在等价类j中,此时我们扫描该数组,将所有的i都改变为j。union的时间复杂度为O(N);

第二种方案:将所有在同一个等价类中的元素放到一个链表中,更新的时候不用搜索整个数组。如果我们还要跟踪每个等价类的大小,并在执行union时将较小的等价类的名字改为较大的等价类的名字。

4、基本数据结构

(1)、可以将每个元素都看作是一棵独立的树,每个集合的名字用树根表示,可以用一个数组就可以实现该思路。将所有的元素用一个数组表示,数组每个成员s[i]表示元素i的父亲,如果i是根,那么s[i]=-1;
不相交集(并查集)

代码实现:

#define numsets 8
typedef int disjset[numsets+1];
void Initilialize(disjset s)
//不相交集合的初始化 
{
     int i;
     for(i = numsets;i>0;i--)
     {
           s[i] = -1;//因为不相交都是根,所以初始化为-1
     }
}

(2)、 合并操作union(4,5),将一棵树的父节点链接到另一棵树的根节点合并两棵树。
不相交集(并查集)

代码实现:

typedef int settype;
void setunion(disjset s,settype root1,settype root2)
//Union操作 
{
     s[root2] = root1;
}

(3)、查找算法:通过返回包含x的树的根而完成。

代码实现:

typedef int elementtype;
settype find(elementtype x,disjset s)
//不相交集的查找算法 
{
        if(s[x] <= 0)
        return x;
        else
        return find(s[x],s)
} 

5、灵巧求并算法
(1)、按照大小求并:合并的时候,使得较小的树总是成为较大的数的子树,而不是用任意的方式进行合并,为了实现按大小合并的方法,需要记住每棵树的大小,可以让每个根元素包含它的树的大小的负值。新的树的大小为两棵树大小的和。

例:
不相交集(并查集)
不相交集(并查集)
代码实现:

void setunion(disjset s,settype root1,settype root2)
//按大小求并 
{
     if(s[root2] < s[root1]){//root2树比较大
        s[root2] += s[root1];//更新树的大小
        s[root1] = root2;//root1的父节点变为root2
    }
    else{
        s[root1] += s[root2];
        s[root2] = root1;
    }
}

(2)、按高度求并:按高度求并,它同样保证所有的树的深度最多为O(logN)。我们跟踪每棵树的高度而不是大小并执行合并使得浅的树成为深的树的子树。只有两棵深度相等的树求并的时候树的高度才增加(树的深度加1)。
为了实现按高度求并,需要记住每棵树的高度,可以让每个根元素包含它的树的高度的负值。只有合并的两棵树高度相等的时候才需要更新树的高度(根元素的值减去1)。
不相交集(并查集)
代码实现:

void setunion(disjset s,settype root1,settype root2)
//按高度求并 
{
     if(s[root2] < s[root1]){//root2树比较高
        s[root1] = root2;//直接合并, root1成为root2树的子树 
    }
    else{//root1树比较高,或相等。

         //如果相等则更新树的高度
        if(s[root1] == s[root2])
            s[root1]--;
         s[root2] = root1;
    }  
} 

6、路径压缩:路径压缩的效果是从X到根节点的路径上的每一个结点都使它的父节点成为根节点。
不相交集(并查集)

代码实现:

SetType Find(ElementType X, DisjSet S)  
{  
    if(S[x]<=0)  
        return x;  
    else  
        return S[x] = Find(S[x],S);  
}