引入

在一些有 NNN 个元素的集合应用问题中,我们通常是在开始时让每个元素构成一个单元素的集合,然后按一定顺序将属于同一组的元素所在的集合合并,其间要反复查找一个元素在哪个集合中。

举个例子:
设初始有若干元素:1,2,3,4,5,6,7,8
元素之间有若干关系:13,24,56,35,78,48
关系合并过程:
初始 {1},{2},{3},{4},{5},{6},{7},{8}
1~3:{1,3},{2},{4},{5},{6},{7},{8}
2~4:{1,3},{2,4},{5},{6},{7},{8}
5~6:{1,3},{2,4},{5,6},{7},{8}
3~5:{1,3,5,6},{2,4},{7},{8}
7~8:{1,3,5,6},{2,4},{7,8}
4~8:{1,3,5,6},{2,4,7,8}

概念

并查集 是一种表示不相交集合的数据结构,用于处理不相交集合的合并与查询问题。在不相交集合中,每个集合通过代表来区分,代表是集合中的某个成员,能够起到唯一标识该集合的作用。一般来说,选择哪一个元素作为代表是无关紧要的,关键是在进行查找操作时,得到的答案是一致的(通常把并查集数据结构构造成树形结构,根节点即为代表)。
在不相交集合上,需要经常进行如下操作:

  1. findSet(x): 查找元素 x 属于哪个集合,如果 x 属于某一集合,则返回该集合的代表。
  2. unionSet(x,y): 如果元素 x 和元素 y 分别属于不同的集合,则将两个集合合并,否则不做操作

并查集的实现方法是使用有根树来表示集合——树中的每个结点都表示集合的一个元素,每棵树表示一个集合,每棵树的根结点作为该集合的代表。

并查集基础操作

初始化

现共有 NNN 个元素,对这 NNN 个元素要进行查询与合并操作,现进行初始化;例如 N=10N = 10N=10,初始化方法如下, father[iii] 为 iii 的父结点编号,初始化时结点的父结点为本身,即自己代表自己,建立 NNN 个独立集合:

void Make_Set(int n) {for (int i = 1; i <= n; i++)father[i] = i;
}

查询

查询操作是递归查询,在查询某个结点在哪一个集合中时,需沿着其父结点,递归向上,因所属集合代表指向的仍然是其本身,所以可以以 father[x]==xfather[x] == xfather[x]==x 作为递归查询出口。

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

合并

在进行集合的合并时,只需将两个集合的代表进行连接即可,即一个代表 作为 另一个代表的父结点。

void Union_Set(int x, int y) {father[Find_Set(x)] = Find_Set(y);
}

优化

路径压缩

最简单的并查集效率是比较低的。
如果继续进行类似的合并,能会形成一条长长的链,随着链越来越长,我们想要从底部找到根结点会变得越来越难。怎么解决呢?

路径压缩:对于一个集合中的结点,只需要关心它的根结点是谁,不必知道各结点之间的关系(对树的形态不关心),希望每个元素到根结点的路径尽可能短,最好只需要一步,极大地提高了查询效率。

路径压缩需要在查询操作时,把沿途的每个结点的父节点都设为根结点即可。下一次再查询时,就可以节约很多时间。

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

按秩合并(启发式合并)

由于路径压缩只在查询时进行,每次查询也只压缩一条路径,所以并查集最终的结构仍然可能是比较复杂。

这启发我们:应该把深度低的树往深度高的树上合并,用 rankrankrank 数组记录根结点对应树的深度(如果不是根节点,其 rankrankrank 相当于以它作为根节点的子树的深度)。一开始,把所有元素的 rankrankrank(秩)设为1。合并时比较两个根结点,把 rankrankrank 较小者往较大者上合并。

void Make_Set(int n) {for (int i = 1; i <= n; i++)father[i] = i, rank[i] = 1;
}
void Union_Set(int x, int y) {int a = Find_Set(x), b = Find_Set(y);if (a == b) return;if (rank[a] <= rank[b]) father[a] = b;else father[b] = a;if (rank[a] == rank[b]) rank[b]++;
}

例题

  1. 亲戚(relation)

这是一道 并查集 的题目,我们首先将所有人的父节点设为自己,如果一对父子的父节点不相同则调用并集函数,在查找时,如果两者父节点相同证明他们两是亲戚,否则不是。

#include<bits/stdc++.h>
using namespace std;
const int Maxn = 2e4 + 5;
int n, m, p, a, b, fa[Maxn], Rank[Maxn];
void Make_Set(){for(int i = 0;i <= n; ++i) fa[i] = i, Rank[i] = 1;return ;
}
int Find_Set(int s) {return fa[s] == s ? s : (fa[s] = Find_Set(fa[s]));
}
void Union_Set(int s, int e){int a = Find_Set(s), b = Find_Set(e);if(a == b) return;if(Rank[a] >= Rank[b]) fa[b] = a;else fa[a] = b;if(Rank[a] == Rank[b]) Rank[a] ++;
}
bool Pd_Set(int s, int e){return Find_Set(s) == Find_Set(e);
}
int main(){scanf("%d %d", &n, &m);Make_Set();for(int i = 1; i <= m; i++) {scanf("%d %d", &a, &b);Union_Set(a, b);}scanf("%d", &p);for(int i = 1; i <= p; i++) {scanf("%d %d", &a, &b);puts(Pd_Set(a, b) ? "Yes" : "No");}return 0;
}
  1. 银河英雄传说
    一条链也是一棵树,只不过是树的特殊形态。因此可以把每一列战舰看作一个集合,用 并查集 维护。最初,NNN 个战舰构成 NNN 个独立的集合。
    在没有路径压缩的情况下,fa[x]fa[x]fa[x] 就表示排在第 xxx 号战舰前面的那个战舰的编号。一个集合的代表就是位于最前边的那艘战舰。另外,让树上每条边权值为 1,这样树上两点之间的距离 −1-1−1 就是二者之间间隔的战舰数量。
#include<bits/stdc++.h>
using namespace std;
const int Max = 3e4 + 5;
int t, a, b;
int fa[Max], dis[Max], size[Max];
char c;
int Find_Set(int x){if (fa[x] == x)return x;int k = fa[x];fa[x] = Find_Set(fa[x]);dis[x] += dis[k];size[x] = size[fa[x]];return fa[x];
}
void Make_Set(){for(int i = 1; i <= Max - 5; i++)fa[i] = i, size[i] = 1;
}
void Union_Set(int s, int e){int a = Find_Set(s), b = Find_Set(e);fa[a] = b;dis[a] += size[b];size[a] += size[b];size[b] = size[a];
}
int main(){scanf("%d", &t);Make_Set();while(t--){scanf("\n%c", &c);scanf("%d %d", &a, &b);if(c == 'M')Union_Set(a, b);else{if (Find_Set(a) == Find_Set(b))printf("%d\n", abs(dis[a] - dis[b]) - 1);elseputs("-1");}}return 0;
}
  1. 食物链
    如前面还没有真话,那么无法判断的话就是真话。如果严格按照 A吃B,B吃C,C吃A,把集合划分成 ABC 三类,假设2 1 3是真话,那么到底把 1、3 分别归到A、B还是B、C还是C、A呢,我们无法判断。所以这里我们可以把1分成 1、1+n、1+2n1、1+n、1+2n1、1+n、1+2n 分别放到 ABC 三个集合中,再来判断正确性。
#include<cstring>
#include<cstdio>
const int Max = 150005;
int fa[Max], a, b, c;
int n, m;
void Make_Set(){for(int i = 0; i <= 3 * n; i++) fa[i] = i;return ;
}
int Find_Set(int s) {return fa[s] == s ? s : (fa[s] = Find_Set(fa[s]));
}
void Union_Set(int x,int y){int t1 = Find_Set(x);int t2 = Find_Set(y);if(t1 != t2)fa[t1] = t2;
}
int main(){scanf("%d %d", &n, &m);Make_Set();int ans = 0;for(int i = 0; i < m; i++){scanf("%d %d %d", &a, &b, &c);int x = b - 1;int y = c - 1;if(x < 0 || x >= n || y < 0 || y >= n){ans++;continue;}if(a == 1){if(Find_Set(x) == Find_Set(y + n) || Find_Set(x) == Find_Set(y + 2 * n))ans++;else{Union_Set(x, y);Union_Set(x + n, y + n);Union_Set(x + 2 * n, y + 2 * n);}}else if(a == 2){if(Find_Set(x) == Find_Set(y) || Find_Set(x) == Find_Set(y + 2 * n))ans++;else{Union_Set(x, y + n);Union_Set(x + n, y + 2 * n);Union_Set(x + 2 * n, y);}}}printf("%d", ans);return 0;
}

进击高手【第十二期】并查集相关推荐

  1. 并查集路径压缩_第二十五天:并查集

    今天是释然发题解的第二十五天,以后会经常和大家分享学习路上的心得,希望和大家一起进步,一起享受coding的乐趣 本文约1400字,预计阅读5分钟 昨天我们学习了动态规划之线性规划,忘记的小伙伴们可以 ...

  2. 数据结构与算法(十二)并查集(Union Find)及时间复杂度分析

    本文主要包括以下内容: 并查集的概念 并查集的操作 并查集的实现和优化 Quick Find Quick Union 基于size的优化 基于rank的优化 路径压缩优化 并查集的时间复杂度 并查集的 ...

  3. 一个很有意思的并查集详解

    并查集是我暑假从高手那里学到的一招,觉得真是太精妙的设计了.以前我无法解决的一类问题竟然可以用如此简单高效的方法搞定.不分享出来真是对不起party了.(party:我靠,关我嘛事啊?我跟你很熟么?) ...

  4. 并查集算法(有趣的讲解)

    转载自: http://blog.csdn.net/dellaserss/article/details/7724401/ 这个文章是几年前水acm的时候转的, 当时也不知道作者是谁, 要是有人知道的 ...

  5. hdu 1232 并查集

    并查集是我暑假从高手那里学到的一招,觉得真是太精妙的设计了.以前我无法解决的一类问题竟然可以用如此简单高效的方法搞定.不分享出来真是对不起party了.(party:我靠,关我嘛事啊?我跟你很熟么?) ...

  6. 看了必懂的并查集原理(转载)

    看了必懂的并查集原理(转载) 并查集是我暑假从高手那里学到的一招,觉得真是太精妙的设计了.以前我无法解决的一类问题竟然可以用如此简单高效的方法搞定.不分享出来真是对不起party了.(party:我靠 ...

  7. 算法笔记16.并查集

    并查集是我暑假从高手那里学到的一招,觉得真是太精妙的设计了.以前我无法解决的一类问题竟然可以用如此简单高效的方法搞定.不分享出来真是对不起party了.(party:我靠,关我嘛事啊?我跟你很熟么?) ...

  8. 并查集详解,不会的同学可以来瞅瞅,转载的,不过加了一些自己的理解。

    并查集是我暑假从高手那里学到的一招,觉得真是太精妙的设计了.以前我无法解决的一类问题竟然可以用如此简单高效的方法搞定.不分享出来真是对不起party了.(party:我靠,关我嘛事啊?我跟你很熟么?) ...

  9. 并查集详解 ——图文解说,简单易懂(转)特别好玩

    并查集详解 --图文解说,简单易懂(转) 2016年03月10日 17:38:08 阅读数:6931 标签: 并查集数据结构并查集算法图文解说 更多 个人分类: 算法--并查集 并查集是我暑假从高手那 ...

最新文章

  1. Docker 简介与安装
  2. AXI DMA DRIVER 阶段性 kernel driver 构建并测试(三 )
  3. apache评分表的意义_APACHE评分系统及评分表
  4. php和python对比-python与java、php、go的优势对比
  5. gsea结果分析图怎么看_微信公众平台数据分析怎么看
  6. 《设计模式详解》行为型模式 - 状态模式
  7. krb5安装包 linux_Linux:krb5
  8. ki4so-发起一个史上最开源的sso项目
  9. 即时游戏中用户信息融合的研究
  10. 【采用】社交网络分析与金融反欺诈应用(知识图谱?)
  11. iOS性能优化-列表卡顿
  12. Win10卸载skype
  13. Java-Swing编程介绍
  14. ASO优化:关键词该怎么选
  15. SpringBoot之整合thymeleaf渲染Web页面
  16. 中兴笔试与面试经验总结
  17. c语言record的作用,C语言基础 record 2-指针,结构体,链表,文件的输入输出
  18. 2021年三门中学高考成绩查询,2021长沙市地区高考成绩排名查询,长沙市高考各高中成绩喜报榜单...
  19. 玩转微信小程序 之 初步了解微信小程序(2019/04/05)
  20. matlab卷积矩阵绝对值,MATLAB矩阵分析和计算

热门文章

  1. 搭建Web服务器-迅为IMX6ULL开发板
  2. 一次性搞定JavaScript 从 ES6 到 ES12的基础框架知识
  3. 把虚拟机迁移到云服务器,VMware业务系统迁移上云方案
  4. 【Python】利用zipfile.ZipFile和write()创建压缩包
  5. ubuntu16.04 更新清华镜像源详细操作步骤
  6. 读取gpio管脚电平需要设置什么模式_MT7688/MT7628-GPIO使用
  7. 洛谷P1478 陶陶摘苹果(升级版)
  8. vue this.$refs 打印出来是空的原因
  9. python实现主成分估计
  10. 什么高大填空四个字动人_照样子填空填四字成语什么什么什么地想