【讲解+模板】并查集

【讲解+模板】并查集

并查集

并查集是一种树型的数据结构,用于处理一些不相交集合(Disjoint Sets)的合并及查询问题。常常在使用中以森林来表示。集就是让每个元素构成一个单元素的集合,也就是按一定顺序将属于同一组的元素所在的集合合并。在一些有N个元素的集合应用问题中,通常是在开始时让每个元素构成一个单元素的集合,然后按一定顺序将属于同一组的元素所在的集合合并,其间要反复查找一个元素在哪个集合中。这一类问题近几年来反复出现在信息学的国际国内赛题中,其特点是看似并不复杂,但数据量极大,若用正常的数据结构来描述的话,往往在空间上过大,计算机无法承受;即使在空间上勉强通过,运行的时间复杂度也极高。 ——摘自搜狗百科

基本定义

并查集是一种树型的数据结构,用于处理一些不相交集合(Disjoint Sets)的合并及查询问题。常常在使用中以森林来表示。
集就是让每个元素构成一个单元素的集合,也就是按一定顺序将属于同一组的元素所在的集合合并。 ——摘自搜狗百科

主要操作

初始化

把每个点所在集合初始化为其自身。 通常来说,这个步骤在每次使用该数据结构时只需要执行一次,无论何种实现方式,时间复杂度均为O(N)。
——摘自搜狗百科

查找

查找元素所在的集合,即根节点。——摘自搜狗百科

合并

将两个元素所在的集合合并为一个集合。 通常来说,合并之前,应先判断两个元素是否属于同一集合,这可用上面的“查找”操作实现。——摘自搜狗百科

由于是利用集合进行的合并查询等操作,故称之为并(合并)查(查询)集(集合)。

算法实现

初始化

//把每个元素的代表元素定义为本身(各自成独立集合)
int father[max_data]
for(int i = 1; i <= n; i++)
      father[i] = i;

查询父亲节点

不断查找代表(父亲)节点,直到查询到代表(父亲)节点为本身的元素,这个元素即为这个集合的代表。

递归实现

(有爆栈危险)

int find(int x)
{
    if(father[x] != x) return find(father[x]);
    else retuen x; 
} 

循环实现

int find(int x)
{
    if(father[x] != x) return find(father[x]);
    else retuen x; 
} 

路径压缩

由于可能遇到单链过长的情况,过长的单链在进行并查集操作时及其耗费时间;所以我们可以进行路径压缩优化,即在进行查找操作时顺便使每一个经过的单链上的点直接指向根节点,由此我们得到了一个复杂度几乎为常数的算法。
并查集路径压缩

递归实现(路径压缩)

(有爆栈危险)

int find(int x)
{
    if(father[x] != x) father[x] = find(father[x]);
    return x;
}

循环实现(路径压缩)

int find(int x)
{
    int r = x, k = x, j;
    while(r != father[r]) r = father[r];//r即为查询到的根节点
    while(r != k) j = father[k], father[k] = r, k = j;//路径压缩(j用来暂存代表)
    return r;
}

合并两个集合(一行极简)

这里写图片描述

//把元素y所在集合的代表元素修改为元素x所在集合的代表元素
void unionn(int x, int y){father[find(y)] = find(x);}

合并优化

1.启发式合并:记录集合中元素个数,每次合并使较小集合合并到较大集合中。(先决条件:路径压缩)

memset(rank, 0, sizeof(rank));  //并查集初始化
void unionn(int x, int y)  //并查集:启发式合并(小集合并入大集合)
{
    x = find(x);
    y = find(y);
    if(rank[x] > rank[y]) father[y] = x, rank[x] += rank[y], rank[y] = 0;
    else father[x] = y, rank[y] += rank[x], rank[x] = 0;
}

2.按秩合并:在对两个不同子集连接时,按照rank来连,也就是rank低的连在rank高的下面。rank高的做父亲节点。(不进行路径压缩时使用)
代码和启发式合并差不多,不过rank记录的不再是集合内元素个数而是树的高度,留给读者自行完成。(当然还是路径压缩了好啦)

查询两个元素是否在同一集合内(一行极简)

//查询两个元素分别所在集合的代表元素,若相同返回1,否则返回0
bool judge(int x, int y){return find(x) == find(y);}

典型例题

  1. 洛谷P2307 迷宫
  2. 虚位以待

小结

总的来说,并查集是一种把一个一个元素划分成一伙一伙元素的高效率算法,作用十分强大且易于理解;Kruskal等高效率算法的实现依赖的就是并查集这种高效率的算法,建议大家深入理解并熟悉这种算法,方便在竞赛场上使用。

博文修改记录

1.2017.10.31 新增合并优化

奈芙莲·卢可·印萨尼亚

猜你喜欢

转载自blog.csdn.net/Mashiro_ylb/article/details/78360288
今日推荐