天天看点

并查集简介

(2010-03-06)

1、定义

        并查集(union-find sets)是一种简单的用途广泛的集合。并查集是若干个不相交集合(Disjoint Sets),能够实现较快的集合合并和判断元素所在集合的操作,应用很多。实际中通常采用树形结构来表示并查集:每棵树表示一个集合,不相交的集合则构成森林。一棵树的每个节点包含如下信息:父节点指针p[x],树的深度rank[x]。

2、操作

2.1 建立一个集合

      初始时,为每个元素x都建立一个集合:Make-Set(x),集合的元素只有它自己,伪代码如下:

Make-Set(x)

   p[x]←x

   rank[x]←0

2.2 查找元素所在集合

       由于我们用一棵树来表示集合,则为操作方便,每个集合有一个代表元素,我们将之定义为该树的根,则查找元素x所在集合的操作Find-Set(x)伪代码如下:

Find-Set(x)

  if x=p[x]

     then return x

     else return Find-Set(p[x])

2.3 合并不相交集合

       合并元素a所在集合和元素b所在集合是将a所在树的根节点的父节点改为b所在树的根节点。这个操作Unin(a,b)只需O(1)的时间。

Union(a,b)

  Link(Find-Set(a),Find-Set(b))  

Link(a,b)

  p[a]←b

由此可见Union的速度取决于Find-Set的速度。

3、优化

      上述Find-Set(x)操作的时间复杂度与树的深度成线性关系,因此其平均时间复杂度为O(logN),但在最坏情况下(树退化成链表),时间复杂度为O(N)。所以有必要对算法进行优化。

3.1 启发式合并

       在Link函数中,我们将深度较小的树指到深度较大的树的根上。这样可以防止树的退化,最坏情况不会出现。可以证明:这样优化后树的深度为O(log N),所以Find-Set(x)的时间复杂度为O(log N)。改进后的Link函数伪代码如下所示:

Link(a,b)

  if rank[a]>rank[b]

  then p[b]←a

  else p[a]←b

     if rank[a]=rank[b]

     then rank[b]←rank[b]+1

3.2 路径压缩

      为了使得Find-Set(x)的效率更高,我们还要进行路径压缩,即在找到x的最远祖先(集合代表根)时顺便把查找路径上的子孙的父节点直接改为该根,这样以后Find-Set的时间复杂度可以认为是常数的。改进后的Find-Set伪代码如下:

Find-Set(x)

        if p[x]≠x

     then p[x]←Find-Set(p[x])

     return p[x]

递归的程序有许多栈的操作,改成非递归会更快些:

Find-Set(x)

     r←x

        while p[r]≠ r

     do r←p[r]

        while r≠x

     do q←p[x]

     p[x]←r

     x←q

       return r

3.3 时间复杂度

        经过启发式合并和路径压缩之后的并查集进行n次查找的时间复杂度是O(nα(m,n))(执行n-1次合并和m>=n次查找)。其中α(m,n)是一个增长极其缓慢的函数,是Ackerman函数的某个反函数,它可以看作是小于5的。所以可以认为并查集的时间复杂度几乎是线性的。

 【附】

Ackerman函数 一种双递归函数,满足以下定义:

Ackerman(1,0)=2 Ackerman(0,m)=1 ,m>=0 Ackerman(n,0)=n+2,n>=2 Ackerman(n,m)=Ackerman(Ackerman(n-1,m),m-1), n,m>=1

继续阅读