每日算法学习之并查集(全网最详细)

本文深入介绍了并查集的基本概念、作用原理及实现方法,并详细探讨了如何通过优化提高其效率,包括路径压缩技术的应用。

并查集

作用

并查集能够解决图论中的动态连通性的问题

什么是动态连通性满足下面三个特点

  • 自反性 节点p 节点q连通
  • 对称性 如果节点p节点q连通那么节点 q和节点p也连通
  • 传递性 节点a 节点b连通 节点b 节点c连通 那么 节点a 节点c连通

原理

视频链接

文章链接

其实原理也是很简单的

  • 首先维护一个parent[]的数组,数组的含义就是parent[i]代表 i的父节点是parent[i]

  • 初始化的时候大家都各自为政,所有人都是自己的父节点parent[i]=-1

  • 当发现有一条边的时候,进行连接parent[x]=y:将x挂在y上面

  • 如果有两个区域要进行连接只需要找到他们对应的老大进行连接就可以,因为上面的传递性

实现

class Union_Find {
 public:
  Union_Find() {}
  explicit Union_Find(int size) : size_(size), parent_(size, -1) {}

  // return 1-链接成功  0-链接失败,有环
  int Union(int x, int y) {  // 作用就是连接 x y这两个节点
    int parent_x = Find(x);
    int parent_y = Find(y);

    if (parent_x == parent_y) {
      cout << "Have Cycle" << endl;
      return 0;
    }
    parent_[x] = parent_y;
    cout << "Sucess" << endl;
    return 1;
  }

  int Find(int x) {  // 寻找x节点的父节点
    while (parent_[x] != -1) {
      x = parent_[x];
    }
    return x;
  }

 private:
  int size_;            // 节点数
  vector<int> parent_;  // 记录父节点 i的父节点就是parent[i]
};

优化

为了防止随便的添加,我们维护一个数组保存子树下面的个数,每次连接的时候都把小的连到大的下面

class Union_Find {
 public:
  Union_Find() {}
  explicit Union_Find(int size)
      : size_(size), count_(size_), parent_(size, -1), weight_(1) {}

  // return 1-链接成功  0-链接失败,有环
  int Union(int x, int y) {  // 作用就是连接 x y这两个节点
    int parent_x = Find(x);
    int parent_y = Find(y);

    if (parent_x == parent_y) {
      cout << "Have Cycle" << endl;
      return 0;
    }
    if (weight_[parent_x] > weight_[parent_y]) {
      parent_[parent_y] = parent_x;
      weight_[parent_x] += weight_[parent_y];
    } else {
      parent_[parent_x] = parent_y;
      weight_[parent_y] += weight_[parent_x];
    }

    cout << "Sucess" << endl;
    count_--;
    return 1;
  }

  int Find(int x) {  // 寻找x节点的父节点
    while (parent_[x] != -1) {
      x = parent_[x];
    }
    return x;
  }

  int GetCount() { return count_; }

 private:
  int size_;            // 节点数
  vector<int> parent_;  // 记录父节点 i的父节点就是parent[i]
  vector<int> weight_;  // 记录每一棵树的节点数量(重量)
  int count_;           // 记录连通分量的个数
};

路径压缩

就是其实把树的高度维持在3

#include <bits/stdc++.h>
using namespace std;

class Union_Find {
 public:
  Union_Find() {}
  explicit Union_Find(int size)
      : size_(size), count_(size), parent_(size), weight_(size, 1) {
    for (int i = 0; i < size_; i++) {
      parent_[i] = i;
    }
  }

  // return 1-链接成功  0-链接失败,有环
  int Union(int x, int y) {  // 作用就是连接 x y这两个节点
    int parent_x = Find(x);
    int parent_y = Find(y);

    if (parent_x == parent_y) {
      cout << "Have Cycle" << endl;
      return 0;
    }
    if (weight_[parent_x] > weight_[parent_y]) {
      parent_[parent_y] = parent_x;
      weight_[parent_x] += weight_[parent_y];
    } else {
      parent_[parent_x] = parent_y;
      weight_[parent_y] += weight_[parent_x];
    }

    cout << "Sucess" << endl;
    count_--;
    return 1;
  }

  int Find(int x) {  // 寻找x节点的父节点
    while (parent_[x] != x) {
      // 路劲压缩
      // 条件表明当前节点有父节点,不是自己
      parent_[x] = parent_[parent_[x]];  // 把自己指向爷爷
      x = parent_[x];
    }
    return x;
  }

  int GetCount() { return count_; }

 private:
  int size_;            // 节点数
  vector<int> parent_;  // 记录父节点 i的父节点就是parent[i]
  vector<int> weight_;  // 记录每一棵树的节点数量(重量)
  int count_;           // 记录连通分量的个数
};

int main() {
  Union_Find instance(4);
  instance.Union(0, 1);
  //   instance.Union(2, 2);
  instance.Union(2, 3);
  instance.Union(3, 2);
  cout << instance.GetCount() << endl;
}

如果不初始化父节点是自己,那么路径压缩会出现内存泄露

valgrind进行查看,编译的时候请加入-g参数

==594== Invalid write of size 4
==594==    at 0x400F84: Union_Find::Find(int) (union_find.cc:36)
==594==    by 0x400DA5: Union_Find::Union(int, int) (union_find.cc:13)
==594==    by 0x400BA0: main (union_find.cc:57)
==594==  Address 0x5ab6c7c is 4 bytes before a block of size 16 alloc'd
==594==    at 0x4C2E0EF: operator new(unsigned long) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==594==    by 0x4015A1: __gnu_cxx::new_allocator<int>::allocate(unsigned long, void const*) (new_allocator.h:104)
==594==    by 0x401532: std::allocator_traits<std::allocator<int> >::allocate(std::allocator<int>&, unsigned long) (alloc_traits.h:491)
==594==    by 0x40149D: std::_Vector_base<int, std::allocator<int> >::_M_allocate(unsigned long) (stl_vector.h:170)
==594==    by 0x4013A8: std::_Vector_base<int, std::allocator<int> >::_M_create_storage(unsigned long) (stl_vector.h:185)
==594==    by 0x401274: std::_Vector_base<int, std::allocator<int> >::_Vector_base(unsigned long, std::allocator<int> const&) (stl_vector.h:136)
==594==    by 0x4010FD: std::vector<int, std::allocator<int> >::vector(unsigned long, int const&, std::allocator<int> const&) (stl_vector.h:291)
==594==    by 0x400CB9: Union_Find::Union_Find(int) (in /home/zhongsy/Documents/算法代码/图算法代码/a.out)
==594==    by 0x400B5E: main (union_find.cc:53)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值