首页 最新 热门 推荐

  • 首页
  • 最新
  • 热门
  • 推荐

【算法与数据结构】并查集详解+题目

  • 25-04-25 14:41
  • 4485
  • 10518
blog.csdn.net

目录

一,什么是并查集

二,并查集的结构 

三,并查集的代码实现 

1,并查集的大致结构和初始化

2,find操作 

3,Union操作

4,优化 

小结:

四,并查集的应用场景

省份数量[OJ题] 


一,什么是并查集

核心概念:并查集是一种 用于管理元素分组 的数据结构。

在一些应用问题中,需将n个不同的元素划分成一些不相交的集合,开始时,n个元素各自成一个集合,然后按照一定规律将部分集合合成一个集合,也就是集合合并。并查集(union-find)适合来描述这类问题。

对于并查集,我们可以将它看成是一个森林,森林是由多棵树组成的,并查集中的一个个集合就可以看作是树。

示例:

二,并查集的结构 

并查集的存储结构和树的双亲表示法相似。

所谓双亲表示法,就是在树的节点中,只存储父节点的指针,不存储孩子节点的指针。通过指针可以找到父节点。因为对于一颗树来说,可能有多个孩子 ,但只有一个父节点。

 

对于上图中:

节点0的数组值为-4,说明该节点为根节点。

节点6的数组值为0,说明该节点的父节点为0。

节点7的数组值为0,说明该节点的父节点为0。

节点8的数组值为0,说明该节点的父节点为0。

三,并查集的代码实现 

并查集主要支持一下操作:

  • 查询(find),查询一个元素在哪个集合中。
  • 合并(union),将两个集合合并为一个。

1,并查集的大致结构和初始化

class UnionFind
{
public:
    UnionFind(size_t n)
        :_ufs(n,-1)
    {}

    //......
private:
    vector _ufs;
};

2,find操作 

在并查集中找到包含x的根

int findRoot(int x)
{
    int root = x;

    while (_ufs[root] >= 0)
        root = _ufs[root];

    return root;
}
 

3,Union操作

合并两个集合

void Union(int x1, int x2)
{
    int root1 = findRoot(x1);
    int root2 = findRoot(x2);
    if (root1 == root2)
        return; //在同一个集合中

    //这里在合并的时候采用数据量小的向数据量大的合并
    //也就是小树向大树合并
    if (abs(_ufs[root1]) < abs(_ufs[root2]))//root1节点更少
    {
        _ufs[root2] += _ufs[root1];
        _ufs[root1] = root2;   //小树合并到大树
    }
    else
    {
        //root2节点更少
        _ufs[root1] += _ufs[root2];
        _ufs[root2] = root1;
    }
}

4,优化 

当树比较高时,我们在反复查某个节点的根节点时,每次都会花费大量时间。

优化方法:路径压缩,只要查找某个节点一次,就将查找路径上的所有节点挂到根节点下面。

如图:查找D的根A,查找路径上包含节点B,将节点D和节点B直接挂在根节点A的下面。

  1. //路径压缩
  2. int findRoot(int x)
  3. {
  4.     int root = x;
  5.     while (_ufs[root] >= 0)
  6.         root = _ufs[root];
  7.     //路径压缩
  8.     while (_ufs[x] >= 0)
  9.     {
  10.         int parent = _ufs[x];
  11.         _ufs[x] = root;   //挂在根节点的下面
  12.         x = parent;
  13.     }
  14.     return root;
  15. }

小结:

上述实现的并查集,支持连续元素。如果是处理非连续元素,需要使用哈希表代替数组(需额处理元素与索引的映射)。

核心思路:

  • 哈希映射:用unordered_map将任意类型元素映射为连续整数ID,内部用数组管理父节点
  • 动态扩容:自动添加新元素,无需预先指定规模。

  • 模板化:支持泛型数据类型(如string等)。

四,并查集的应用场景

  1. 连通性检测:判断网络中两个节点是否连通。

  2. 最小生成树(Kruskal算法):动态合并边,避免环。

  3. 社交网络分组:快速合并好友关系,查询是否属于同一社交圈。

总结:

并查集通过高效的查找与合并操作,成为处理动态连通性问题的核心数据结构。其优化方法(路径压缩、按秩合并)确保了接近常数的单次操作时间复杂度,适用于大规模数据场景。

其中的按秩合并就是合并集合时小树向大树合并。

省份数量[OJ题] 

题目链接:LCR 116. 省份数量 - 力扣(LeetCode)

 isConnected[i][j]=1,表示城市i和j连通,连通的城市为一个省份。用并查集将连通的数据放入一个集合,再统计最后的集合个数即可。

  1. class Solution {
  2. public:
  3. int findCircleNum(vector<vector<int>>& isConnected) {
  4. int n=isConnected.size();
  5. vector<int> _ufs(n,-1);
  6. //查找根
  7. auto find=[&](int x)->int
  8. {
  9. int root=x;
  10. while(_ufs[root]>=0)
  11. root=_ufs[root];
  12. return root;
  13. };
  14. for(int i=0;i<n;i++)
  15. for(int j=0;j<n;j++)
  16. {
  17. if(isConnected[i][j]==1)
  18. {
  19. //合并i和j集合
  20. int rooti=find(i),rootj=find(j);
  21. if(rooti!=rootj)
  22. {
  23. _ufs[rooti]+=_ufs[rootj];
  24. _ufs[rootj]=rooti;
  25. }
  26. }
  27. }
  28. //统计集合数
  29. int ret=0;
  30. for(auto x:_ufs)
  31. {
  32. if(x<0)
  33. ret++;
  34. }
  35. return ret;
  36. }
  37. };

冗余连接[OJ题]

题目链接:684. 冗余连接 - 力扣(LeetCode)

  1. class Solution {
  2. public:
  3. vector<int> findRedundantConnection(vector<vector<int>>& edges) {
  4. //遍历edges数组
  5. //将在同一条边中的两个顶点放入一个集合
  6. //如果这条边的两个顶点已经在同一个集合中,加入这条边后,会出现环 ,返回这条边
  7. vector<int> ufs(1010);
  8. int sz=edges.size();
  9. //初始化时各元素自成一个集合,自己就是根
  10. for(int i=0;i<sz;i++)
  11. ufs[i]=i;
  12. for(int j=0;j<sz;j++)
  13. {
  14. //找到边的两个顶点所在的集合,也就是根节点
  15. int root1=find(edges[j][0],ufs);
  16. int root2=find(edges[j][1],ufs);
  17. //如果在一个集合,加入这条边后,会出现环
  18. if(root1==root2)
  19. return edges[j];
  20. else
  21. {
  22. //两个集合独立,合并两个集合
  23. ufs[root1]=root2;
  24. }
  25. }
  26. return {0,0};
  27. }
  28. int find(int num,vector<int>& ufs)
  29. {
  30. int root=num;
  31. while(ufs[root]!=root)
  32. root=ufs[root];
  33. return root;
  34. }
  35. };

等式方程的可满足性[OJ题]

本题链接:990. 等式方程的可满足性 - 力扣(LeetCode)

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

 

注:本文转载自blog.csdn.net的小鬼yalo的文章"https://blog.csdn.net/2401_82677021/article/details/145656330"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接
复制链接
相关推荐
发表评论
登录后才能发表评论和回复 注册

/ 登录

评论记录:

未查询到任何数据!
回复评论:

分类栏目

后端 (14832) 前端 (14280) 移动开发 (3760) 编程语言 (3851) Java (3904) Python (3298) 人工智能 (10119) AIGC (2810) 大数据 (3499) 数据库 (3945) 数据结构与算法 (3757) 音视频 (2669) 云原生 (3145) 云平台 (2965) 前沿技术 (2993) 开源 (2160) 小程序 (2860) 运维 (2533) 服务器 (2698) 操作系统 (2325) 硬件开发 (2492) 嵌入式 (2955) 微软技术 (2769) 软件工程 (2056) 测试 (2865) 网络空间安全 (2948) 网络与通信 (2797) 用户体验设计 (2592) 学习和成长 (2593) 搜索 (2744) 开发工具 (7108) 游戏 (2829) HarmonyOS (2935) 区块链 (2782) 数学 (3112) 3C硬件 (2759) 资讯 (2909) Android (4709) iOS (1850) 代码人生 (3043) 阅读 (2841)

热门文章

101
推荐
关于我们 隐私政策 免责声明 联系我们
Copyright © 2020-2024 蚁人论坛 (iYenn.com) All Rights Reserved.
Scroll to Top