当前位置:   article > 正文

并查集(C++实现)_c++并查集

c++并查集

目录

一、并查集原理

二、并查集应用

2.1 并查集举例

2.2 并查集数组规律

2.3 并查集功能

三、并查集实现

3.1 并查集

3.2 根据名字查找

四、例题

4.1 省份数量

4.2 等式方程的可满足性


一、并查集原理

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

  • 并查集并查集,就是看你在哪个集合中,并且可以将集合合并的数据结构

二、并查集应用

2.1 并查集举例

某公司今年招收10人,上海招收4人,成都招收3人,武汉招收3人;这10个人来自不同的学校,每个学生都是一个独立的小团体,现在给这些学生编号:{0,1,2,3,4,5,6,7,9};给以下数据用来存储该小集合,数组中的数据代表:该小集合中具有成员的个数:
在这里插入图片描述
毕业后,学生们要去公司上班,每个地方的学生自发组织成小分队一起上路,于是:
上海学生小分队s1={0,6,7,8},成都学生小分队s2={1,4,9},武汉学生小分队s3={2,3,5}就相互认识了,10个人形成了三个小团体。假设右三个群主0,1,2担任队长,负责大家的出行。
在这里插入图片描述
一趟火车之旅后,每个小分队成员就互相熟悉,成为了一个朋友圈。
在这里插入图片描述

从上图可以看出:
编号为6,7,8同学属于0号小分队,该小分队中有4人(包含队长0);
编号为4和9的同学属于1号小分队,该小分队有3人(包含队长1);
编号为3和5的同学属于2号小分队,该小分队有3人(包含队长1)。

2.2 并查集数组规律

此时数组中便会出现以下规律:

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

在公司工作一段时间后,西安小分队中8号同学与成都小分队1号同学奇迹般地走到了一起,两个小圈子的同学相互介绍,最后成为了一个圈子:
在这里插入图片描述
现在0集合有7个人,2集合有3个人,总共有两个朋友圈。

2.3 并查集功能

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

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

三、并查集实现

3.1 并查集

并查集需要有下面的接口

  • 查找元素属于哪个集合
  • 查看两个元素是否属于一个集合
  • 合并两个集合
  • 集合的个数

在这里插入图片描述


查找元素属于哪个集合


查看两个元素是否属于一个集合


合并集合

我们只能做到将一个集合合并到另一个集合,让其作为子集。

步骤(右集合合并至左集合):

1. 找到两个集合的根

2. 判断两个集合的根是否不同,相同则不做处理

3. 将右集合中记录集合数累加到左集合中。

4. 再将右集合指向左集合。



集合的个数

3.2 路径优化(压缩)

当我们合并集合的时候,如果将大集合合并到小集合中,则会导致路径过长,效率变低。

例我们现在有一个大集合、一个小集合

 然后我们使用两种方式进行合并(一种是小集合并到大集合中,一中是大集合并到小集合中)

很明显,将小集合并到大集合中更能提高性能。

那体现再程序中就是当我们找到根的时候,来判断一下两个集合哪个大即可,然后进行合并:

  1. //合并集合
  2. void Union(int x1, int x2)
  3. {
  4. //1.找到两个集合的根
  5. int root1 = FindRoot(x1);
  6. int root2 = FindRoot(x2);
  7. //如果两个根不相同---则合并
  8. if (root1 != root2)
  9. {
  10. //默认root1为大集合,判断root2是否大于root1
  11. if (abs(_ufs[root1]) < abs(_ufs[root2]))
  12. swap(root1, root2);
  13. //将小的集合合并到大集合中
  14. _ufs[root1] += _ufs[root2];
  15. _ufs[root2] = root1;
  16. }
  17. }

当合并出现极端情况时,此时查询根节点就会接近于线性,此时我们要在find中,顺带进行路径压缩。

例:

 此时我们希望路径能压缩至这种形式: 

 

因为我们在findRoot中找到了根,所以我们直接再次将走过的路径让其指向根即可,实现如下:

  1. int FindRoot(int x)
  2. {
  3. int root = x;
  4. //因为根为负数,找到负数即可
  5. while (_ufs[root] >= 0)
  6. {
  7. root = _ufs[root];
  8. }
  9. //路径压缩
  10. while (_ufs[x] >= 0)
  11. {
  12. //记录父亲节点
  13. int parent = _ufs[x];
  14. //让该节点直接指向root
  15. _ufs[x] = root;
  16. x = parent;
  17. }
  18. return root;
  19. }

3.3 根据名字查找

这里我们可以实现一个根据名字查找编号。

实现思路:

        用一个数组来存放所有学生的信息,再使用map来建立映射关系。

map的使用<string, int>类型,根据名字查找到数组中该学生的下标,使用该下标再访问数组即可查到数据。

  1. //学生信息数组
  2. vector<student_info> arr{ student_info("Brant", 20, 10),
  3. student_info("James", 40, 70),
  4. student_info("Curry", 30, 100) };
  5. //并查集
  6. vector<student_info> UnionFind_arr(3);
  7. map<string, int> UnionFind_map;
  8. for (int i = 0; i < arr.size(); i++)
  9. {
  10. UnionFind_arr[i] = arr[i];
  11. UnionFind_map[arr[i]._name] = i;
  12. }
  13. //可以通过名字找到编号
  14. cout << "James信息的下标为:" << UnionFind_map["James"] << endl;
  15. cout << UnionFind_arr[UnionFind_map["James"]] << endl;

 student_info类

  1. class student_info
  2. {
  3. public:
  4. friend ostream& operator<<(std::ostream& os, const student_info& info);
  5. student_info(string name,int age,int score)
  6. :_name(name),_age(age),_score(score)
  7. {
  8. }
  9. string _name;
  10. int _age;
  11. int _score;
  12. };
  13. ostream& operator<<(std::ostream& os, const student_info& info)
  14. {
  15. os << "Name: " << info._name << "\n"
  16. << "Age: " << info._age << "\n"
  17. << "Score: " << info._score << "\n";
  18. return os;
  19. }

四、例题

我觉得,把这两道并查集的题目解决,才算学会了并查集。

4.1 省份数量

4.1 省份数量

解题思路:

        这个省份数量就类似我们本篇博客举的实习例子很像,是同一个集合,就将其合并到一起。最后通过统计有几个这样的集合即可。

只不过这题使用了二维数组来表达这种关系。

直接将我们编写的并查集类放入代码中,调用其中的函数即可通过。

  1. class UnionFindSet
  2. {
  3. public:
  4. UnionFindSet(size_t n)
  5. :_ufs(n,-1)
  6. {}
  7. void Union(int x1, int x2)
  8. {
  9. int root1 = FindRoot(x1);
  10. int root2 = FindRoot(x2);
  11. if (root1 != root2)
  12. {
  13. _ufs[root1] += _ufs[root2];
  14. _ufs[root2] = root1;
  15. }
  16. }
  17. int FindRoot(int x)
  18. {
  19. int root = x;
  20. while (_ufs[root] >= 0)
  21. {
  22. root = _ufs[root];
  23. }
  24. return root;
  25. }
  26. bool IsInset(int x1,int x2)
  27. {
  28. return FindRoot(x1) == FindRoot(x2);
  29. }
  30. size_t setSize()
  31. {
  32. size_t size=0;
  33. for(int i =0;i<_ufs.size();i++)
  34. {
  35. if (_ufs[i]<0)
  36. ++size;
  37. }
  38. return size;
  39. }
  40. private:
  41. vector<int> _ufs;
  42. };
  43. class Solution {
  44. public:
  45. int findCircleNum(vector<vector<int>>& isConnected) {
  46. UnionFindSet ufs(isConnected.size());
  47. for(size_t i = 0;i<isConnected.size();i++)
  48. for(size_t j = 0;j<isConnected[i].size();j++)
  49. if (isConnected[i][j] == 1)
  50. ufs.Union(i,j);
  51. return ufs.setSize();
  52. }
  53. };

直接使用并查集思想一样可以写出来

  1. class Solution {
  2. public:
  3. int findCircleNum(vector<vector<int>>& isConnected) {
  4. vector<int> ufs(isConnected.size(),-1);
  5. auto findRoot = [&ufs](int x)
  6. {
  7. while(ufs[x]>=0)
  8. x=ufs[x];
  9. return x;
  10. };
  11. for(size_t i = 0;i<isConnected.size();i++)
  12. {
  13. for(size_t j = 0;j<isConnected[i].size();j++)
  14. {
  15. if (isConnected[i][j]==1)
  16. {
  17. int root1 =findRoot(i);
  18. int root2 =findRoot(j);
  19. if (root1!=root2)
  20. {
  21. ufs[root1] += ufs[root2];
  22. ufs[root2] = root1;
  23. }
  24. }
  25. }
  26. }
  27. size_t size=0;
  28. for(int i =0;i<ufs.size();i++)
  29. {
  30. if (ufs[i]<0)
  31. ++size;
  32. }
  33. return size;
  34. }
  35. };

4.2 等式方程的可满足性

等式方程的可满足性

这一题我们首先处理相等

遍历数组,如果是相等,我们就将其归到同一个集合中,则表示该等式中的变量都是相等的。

然后我们再遍历数组中的不相等方程,找到两个变量的根,因为是不相等,则这两个变量肯定不在一个集合中,通过 find 如果发现两个变量是同一个根,则表示在同一个集合中,即方程不正确。

  1. class Solution {
  2. public:
  3. bool equationsPossible(vector<string>& equations) {
  4. vector<int> ufs(26,-1);
  5. auto findRoot = [&ufs](int x)
  6. {
  7. while(ufs[x]>=0)
  8. x=ufs[x];
  9. return x;
  10. };
  11. //处理相等的公式,相等则放到同一个集合中。
  12. for(auto str:equations)
  13. {
  14. if (str[1]=='=')
  15. {
  16. int root1 = findRoot(str[0]-'a');
  17. int root2 = findRoot(str[3]-'a');
  18. if (root1 !=root2)
  19. {
  20. ufs[root1] += ufs[root2];
  21. //指向左集合
  22. ufs[root2] = root1;
  23. }
  24. }
  25. }
  26. //不相等公式,如果两个根在同一个集合则返回false
  27. for(auto str:equations)
  28. {
  29. if (str[1]=='!')
  30. {
  31. int root1 = findRoot(str[0]-'a');
  32. int root2 = findRoot(str[3]-'a');
  33. if (root1 ==root2)
  34. return false;
  35. }
  36. }
  37. return true;
  38. }
  39. };

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/知新_RL/article/detail/792340
推荐阅读
相关标签
  

闽ICP备14008679号