c++并查集


前言


一、并查集

1、并查集原理

在一些应用问题中,需要将n个不同的元素划分成一些不相交的集合。开始时,每个元素自成一个单元素集合,然后按一定的规律将归于同一组元素的集合合并。在此过程中要反复用到查询某一个元素归属于那个集合的运算。适合于描述这类问题的抽象数据类型称为并查集(union-find set)。

例如某公司今年校招共招10个人,北京招4人,河南招3人,西安招3人,10个人来自不同的学校,刚开始互相都不认识,所以每个人都是一个独立的小团体,我们给这些人进行编号:{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};然后我们使用下面的数组来存储该小集合。数组中存的-1中的1表示现在每个团体中都只有1个人,负号表示这个人就是这个团体的根结点。
在这里插入图片描述
毕业后,学生们要去公司上班,每个地方的学生自发组织成小队一起上路,于是北京学生小分队S1={0, 6, 7, 8},河南小分队S2={1, 4, 9},西安小分队S3={2, 3, 5},这时10个人就形成了3个小团体。假设三个队长由0,1,2担任。下面为集合的树形表示。
在这里插入图片描述
下面为S1、S2、S3集合使用数组表示。可以看到北京的队长为0,所以数组中下标为0的空间中存储了-4,4表示S1这个集合有4个人,负号表示编号为0的同学是树的根结点,即队长。而数组中下标为6,7,8的空间中存了0,这存储的是6,7,8的父结点的下标0,即表示编号为6,7,8的同学的队长为0。S2和S3集合在数组中的存储也同理。
在这里插入图片描述
仔细观察数组,可以得出以下结论:

  1. 数组的下标对应集合中元素的编号。
  2. 数组中如果为负数,负号代表根,数字代表该集合中元素个数。
  3. 数组中如果为非负数,代表该元素双亲在数组中的下标。

在公司工作一段时间后,北京小分队中8号同学与河南小分队1号同学奇迹般的走到了一起,两个小圈子的学生相互介绍,最后成为了一个小圈子。此时就相当于S1和S2集合合并了,下面为合并后集合的树形表示。
在这里插入图片描述
下面为集合在数组中的表示。现在0集合有7个人,2集合有3个人,总共两个朋友圈。
在这里插入图片描述

通过以上例子可知,并查集一般可以解决一下问题:

  1. 查找元素属于哪个集合
    沿着数组表示树形关系向上一直找到根。(即:树中元素为负数的位置)
  2. 查看两个元素是否属于同一个集合
    沿着数组表示的树形关系向上一直找到树的根,如果根相同表明在同一个集合,否则不在。
  3. 将两个集合归并成一个集合
    将两个集合中的元素合并。
    将一个集合名称改成另一个集合的名称。
  4. 集合的个数
    遍历数组,数组中元素为负数的个数即为集合的个数。

2、并查集实现

下面我们来简单实现一个并查集。
在这里插入图片描述
下面实现返回元素根结点的函数。
在这里插入图片描述
下面再来实现合并集合的函数。
在这里插入图片描述
下面再来实现判断两个元素在同一集合,查看集合数的函数。这样我们就实现了一个简单的并查集。
在这里插入图片描述

3、并查集应用

1.省份数量

题目链接:省份数量
在这里插入图片描述
这个题目我们可以使用并查集来解决,即相连的城市就表示在同一个集合,这样最后只需要知道并查集中有多少集合,就知道了省份的数量。我们先将上面实现的并查集放到题目中。
在这里插入图片描述
然后我们创建一个并查集,遍历矩阵,并且将相连的城市作为同一个集合,进行合并。
在这里插入图片描述
上面我们使用到了一个并查集,那么写这个题之前就需要实现一个并查集,这样是很麻烦的,所以下面我们不使用并查集来做这一题。
在这里插入图片描述

2.等式方程的可满足性

题目链接:等式方程的可满足性
在这里插入图片描述
这个题目我们也可以使用并查集的思想来实现,我们可以创建一个可以存26个元素的数组,然后我们可以先遍历一遍数组,将相等的值放到一个集合里面,然后再遍历一遍数组,查看不相等的字符串,如果不相等的字符串也在同一个集合,那么这就是相悖的。
在这里插入图片描述

  • 11
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是C++实现并查集解决格子游戏的代码和解释: ```cpp #include <iostream> #include <vector> using namespace std; const int MAXN = 1005; int f[MAXN * MAXN], rank[MAXN * MAXN]; int n, m; // 初始化并查集 void init() { for (int i = 1; i <= n * m; i++) { f[i] i; rank[i] = 1; } } // 查找根节点 int find(int x) { if (x != f[x]) { f[x] = find(f[x]); } return f[x]; } // 合并两个集合 void merge(int x, int y) { int fx = find(x); int fy = find(y); if (fx != fy) { if (rank[fx] < rank[fy]) { swap(fx, fy); } f[fy] = fx; rank[fx] += rank[fy]; } } int main() { cin >> n >> m; init(); vector<vector<int>> grid(n, vector<int>(m)); // 输入格子游戏的矩阵 for (int i = 0; i < n; i++) { for (int j = 0; j < m; j++) { cin >> grid[i][j]; } } // 遍历矩阵,合并相邻的格子 for (int i = 0; i < n; i++) { for (int j = 0; j < m; j++) { if (i > 0 && grid[i][j] == grid[i - 1][j]) { merge((i - 1) * m + j + 1, i * m + j + 1); } if (j > 0 && grid[i][j] == grid[i][j - 1]) { merge(i * m + j, i * m + j + 1); } } } // 统计不同集合的个数 int cnt = 0; for (int i = 1; i <= n * m; i++) { if (f[i] == i) { cnt++; } } cout << cnt << endl; return 0; } ``` 解释: 首先,我们需要定义一个并查集,用于维护格子之间的连通性。在这个并查集中,每个格子都是一个节点,如果两个格子相邻且颜色相同,则它们属于同一个集合。我们可以使用一个二维数组来表示格子游戏的矩阵,其中每个元素表示该位置的格子颜色。 接下来,我们需要遍历整个矩阵,对于每个格子,如果它和它上面或左边的格子颜色相同,则将它们合并到同一个集合中。这里我们使用路径压缩和按秩合并两种优化方式,可以提高查询效率。 最后,我们统计不同集合的个数,即为最终答案。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值