并查集入门

并查集的结构:

并查集用树型(非二叉树)结构实现,每个元素对应一个节点,每个组对应一棵树。


并查集是一种不相交集(Disjoint Sets)ADT,常常在使用中以森林来表示,用来管理元素的分组情况。,进行快速规整。可以以高效的进行:

1)Find,查询元素a和b是否属于同一个组

当且仅当两个元素处于同一个集合中时,Find返回相同的名字,即本操作可转换为寻找两个元素的最久远祖先是否相同,即当且仅当a和b在同一个集合中Find(a)==Find(b)。可以采用递归实现沿着树向上走来找到两棵树的根进行比较。

2)Union,合并元素a和b所在的组

设置一个数组Father[x],表示x的“父亲”的编号。那么,合并两个不相交集合的方法就是,找到其中一个树形集合的根节点,将另外一个树形集合的根节点指向它


并查集的优化方法:

1)路径压缩

寻找根节点时,我们一般采用递归查找,但是当元素很多亦或是整棵树变为一条链时,每次Find操作都是O(n)的复杂度。通过路径压缩可以使得并查集变得更加高效,对于每个查询的甚至是经过的节点节点,一旦经过Find找到根节点后,就直接把连向父亲的边直接改为连向根节点,这样以后再次Find时复杂度就变成O(1)了。


2)按秩合并

在树形数据结构中,如果发生了退化的情况,那么并查集的复杂度便会变得很高,为了防止退化的发生,在合并之前记录下每棵树的高度(Rank),合并时如果Rank不同由Rank小的向Rank大的连边。


该算法是动态的(dynamic),因为在算法执行的过程中集合可以通过Union运算发生改变

该算法是在线的(on-line),执行Find时,必须给出答案才能继续进行



常见的应用:求无向图的连通分量个数,最近公共祖先(LCA),实现Kruskal算法求最小生成树等。


代码实现:

int father[MAX_N];
int Rank[MAX_N];

void init()
{
    for(int i=0;i<n;i++)
    {
        father[i]=i;
        Rank[i]=0;
    }
}

bool same(int x,int y)
{
    return Find(x)==Find(y);
}

int Find(int x)
{
    if(father[x]==x)
        return x;
    else
        return father[x]=Find(father[x]);
}

void Unionset(int x,int y)
{
    x=Find(x);y=Find(y);
    if(x==y)
        return;
    if(Rank[x]<Rank[y])
        father[x]=y;
    else
        father[y]=x;
    if(Rank[x]==Rank[y])
        Rank[x]++;
}

时间复杂度:

O(n*α(n))

其中α(x),对于x=宇宙中原子数之和,α是阿柯曼函数的一个反函数,增长极慢,在可以想象的n的范围内α(x)不超过5,故可视为常数。事实上,路经压缩后的并查集的复杂度是一个很小的常数。


Tarjan的文章里证明了这个复杂度,感兴趣的可以看一下

文章:Tarjan R E, Van Leeuwen J. Worst-case analysis of set union algorithms[J]. Journal of the ACM (JACM), 1984, 31(2): 245-281.


posted @ 2016-05-29 08:25  闲鱼型选手  阅读(286)  评论(0编辑  收藏  举报