参考(部分内容和图片来源于以下参考,如有侵权,联系删除):
https://zhuanlan.zhihu.com/p/93647900/

什么是并查集?

就是一个集合。

有什么实际应用?

除了算法题中见到,好像其他地方还没用过。

并查集的基本结构

图例:

  1. 初始状态,共6个节点,也就是6个集合,每个集合中只有一个元素,并且每个元素的父节点是它本身:

image.png

  1. 现在,需要将两个集合合并(取并集),3合并到1中,原本独立的两个集合{1}{3}变成了{1,3},并且3的父节点变成了1,现在1,3是在同一个集合中的了。

image.png

  1. 同样的道理,2又合并到1所在的集合中,现在2的父节点变成了1,1原本在的集合变成了{1, 2, 3},2所在的单独集合已经没了(合并到了1中)。

image.png

  1. 同样的,任何两个节点直接都是可以合并的(任何两个集合都是能取并集的),{4},{5},{6}三个集合合并,就变成了了下面这个:

image.png

  1. 现在,就剩下两个集合了,我们要将这两个集合进行合并的话是怎么样的呢,可以选择把4的父节点指到1,也可以选择把1的父节点指到4(两个集合的根,谁作为子节点对于并查集的性能优化有用)。

image.png

之后,对并查集的使用也就两种:
1)两个集合进行合并
2)查找某个元素的根节点(这用于判断两个元素是否在同一个集合中)。

并查集的实现

以一道简单的例题来看(例题有测试用例,较大程度保证自己写错了能够发现):
https://leetcode-cn.com/problems/redundant-connection/

题意:
给你一个图,这个图是由一颗树加上一条边之后得到的,现在给你所有的边,让你找出去掉哪条边之后得到的还是一棵树(而不是没分成了两棵树)。

转变一下思路,去掉一条边还是树,实际上就是原本加上这条边之后就出现了环呗。那好办了,直接通过不断加入 边,通过并查集判断边上的两个顶点是不是同一个集合中,在同一个集合中就是环了,不在就不是环,加入到集合中,形成树。

  1. 数组实现(算法题中一般用的最多的就是数组实现,毕竟数据范围给定,一次开满空间,通过数组的映射进行查找是快速又简便的)

代码实现:这个长度数组是按秩合并进行优化用到的,在query方法中还有一个路径压缩。整体代码也比较固定,10几行。下面详细说说这个路径压缩和按秩合并。

  1. class Solution {
  2. public:
  3. int fa[1005];
  4. int length[1005];
  5. int max_node;
  6. void init(){
  7. for(int i = 1; i <= max_node; i++){
  8. fa[i] = i;
  9. }
  10. }
  11. int query(int x){
  12. if(fa[x] == x)return x;
  13. return fa[x] = query(fa[x]);
  14. }
  15. void merge(int p, int q){
  16. int a = query(p), b = query(q);
  17. if(a == b)return;
  18. if(length[a] > length[b]){
  19. fa[b] = a, length[a]++;
  20. }else{
  21. fa[a] = b, length[b]++;
  22. }
  23. }
  24. vector<int> findRedundantConnection(vector<vector<int>>& edges) {
  25. vector<int> ans(2);
  26. for(int i = 0; i < edges.size(); i++){
  27. max_node = max(max_node, max(edges[i][0], edges[i][1]));
  28. }
  29. init();
  30. for(int i = 0; i < edges.size(); i++){
  31. int a = edges[i][0], b = edges[i][1];
  32. if(query(a) == query(b)){
  33. ans[0] = a, ans[1] = b;
  34. }else{
  35. merge(a, b);
  36. }
  37. }
  38. return ans;
  39. }
  40. };

路径压缩:
就是节点指向的父节点,始终保持指向集合的根节点,保持树的高度最低。下面几个图进行理解(图片待补充)。

按秩合并:
就是在合并两个集合的时候,把高度低的树作为高度高的树的子节点,以保持整个树的高度更低。如下图(图片待补充):

面向对象封装

c++:

java:

应用例题:

acwing:

leetcode: