并查集理解与应用

Posted migeater

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了并查集理解与应用相关的知识,希望对你有一定的参考价值。

disjoint-set data structure

union-find data structure

merge-find set

技术分享图片

记号

#define fori(n) for(int i=0;i<(n);i++)

#define Mem(x) memset((x),0,sizeof(x));

定义

树形的数据结构,用于处理不相交集合的合并与查询问题;

但不支持分割集合

应用

  • 维护无向图的连通性,

    判断两个点是否在同一联通块内,增加一条边是否产生环

实现

用数组储存,[]结构实现

用数字代表元素:

parent(x)表示x在树形结构上的父节点对应的数字,

代表元表示每个联通块.

举例说明

现在有编号为 2,4,6的三个结点

如果 p[2]=4;表明 2号结点指向 4号结点

4号结点表示 {2,4} 联通块

操作

  • CREATE_SET(x);{x}创建新集合
  • MERGE-SETS(x,y) x,y所属集合合并
  • FIND-SET(x) 返回x所在集合的代表元

    结构

       

    技术分享图片

       

    技术分享图片

       

    技术分享图片

    使用两个优化要点达到近乎常数时间

    • union by rank
      • 作者更喜欢union by size

        让更""的树的根指向更""的树

        建立数组rank[]来判断树深度,

        CREATE-SET(x),rank[x]=0,

        MERGE,rank更大的根将成为更小的根的parent;如果两根rank相同,就任选一个父根,并且增加父根的root的值

void unite(int x,int y){
  int xx=find(x);

  int yy=find(y);

  //if(xx==yy) return;

  if(rank[xx]<rank[yy])

    p[xx]=yy;

  else

    p[yy]=xx;

   

  if(rank[xx]==rank[yy])

    rank[xx]++;
}

  • path compression↓后,为了简单起见,不再谢盖rank的值,rank[]不再具有实际意义,仍能加快算法<可能需要深入了解相关论文<
  • 也可以union by size(用树的体积,这样path compression 就无影响)
  • path compression 路径压缩

    make each node on the find path point directly to the root

    具体见find函数的代码部分

    技术分享图片

加深印象

https://www.hackerearth.com/zh/practice/notes/disjoint-set-union-union-find/

可以访问诸如↑网站,可以看到丰富的图像,用图片来解释unionset

代码

最基本的由三部分组成

void init(int n);

int find(int);

void unite(int a,int b);

前两部分基本上都一样

第三部分还可以优化,优化的方式也不同

init()

就那样

void init(int n){fori(n)p[i]=i;}//注意题目中的编号是否从1开始

void init(int n){ //union by rank
  fori(n){
    rank[i]=0;

    p[i]=i;

  }
}

find()

int find(int x){return p[u]==u?u:p[u]=find(p[u]);}

  • unite()

void unite(int a,int b){

p[find(b)]=find(a);

} //b{a所在联通块}

  • unite() with union by rank

void unite(int x,int y){
  x=find(x);

  y=find(y);

  //if(x==y) return;

  if(rank[x]<rank[y])

    p[x]=y;

  else

    p[y]=x;

   

if(rank[x]==rank[y])

  rank[x]++;
}

+各种应用需要的代码

/* 下标从 1 开始 */

int p[maxn];

set <int> point; //已经储存了并查集中的所有点

int flag; //是否有环

int cnt; //不同集合的合并次数

int size[maxn]; // 表示联通块含有的元素数量

int init(int n)

{

point.clear();

fori(n) {

p[i] = i;

size[i] = 1;

}

cnt=0;

flag=0;

}

int find(int u)

{

return p[u]==u?u:p[u]=find(p[u]);

}

void unite(int a,int b)

{

int aa=find(a);

int bb=find(b);

if(aa==bb){

flag=1;

}else if(size(aa)<size(bb)){

cnt++;

p[aa]=bb;

size[bb]+=size[aa];

}else{

cnt++;

p[bb]=aa;

size[aa]+=size[bb];

}

}

时间复杂度研究

假设创建了n次集合,使用了ffind操作

不用任何优化

技术分享图片

仅使用路径压缩

技术分享图片

仅使用union by rank/size

技术分享图片

两次优化

技术分享图片

技术分享图片








以上是关于并查集理解与应用的主要内容,如果未能解决你的问题,请参考以下文章

总结一下我理解的带权并查集

并查集

并查集

带权并查集

并查集的理解

并查集的理解