C++并查集数据结构实现

什么是并查集?

N个不同元素,按照某些相似特征划分到不同的M(M<=N)个集合中

实现对不同元素的合并,查找某个元素属于哪个集合,描述这类问题的抽象数据类型就称之为并查集

并差集有哪些常见使用场景?

  • 实现等价类的划分

  • 构造最小生成树的Kruskal算法通过并查集判断最小生成树的连通性

  • 不同集合中判断任意两个元素是否同属于一个集合

数据结构如何实现?

  • 对于N个元素,构造一个能表示N个元素的索引数组parent[N],初始时初始化parent[i]值为-1,`表示只有一个元素的集合
  • 把具有相似特征的元素的索引,合并为同一个集合,并用在数组parent[i]表示出来
  • parent[i]值为负数,表示为根节点,且其绝对值为当前集合元素的个数;parent[i]值为正数,表示的是指向根节点的索引
  • 同一个集合中的各个元素,指向唯一的根索引;不同集合中元素,指向不同的根索引

实现过程

#include <cstdlib>
#include <ctime>
#include <iostream>
#include <string>

using namespace std;
// 并查集
class UFSet {
private:
  // parent[] 并不保存元素的值,只保存元素索引,并表示元素的关联关系
  int *parent;
  //集合中元素的个数
  int size;

public:
  UFSet(int sz);
  ~UFSet() { delete[] parent; }
  bool Union(int rootpos, int childpos);
  //查找值等于 pos 的根索引,如果索引为pos的元素是孤立的元素,则返回本身的索引;
  //如果不是孤立的,跟其他元素共M个元素处在一个集合中,那么返回根元素的索引
  int Find(int pos);
  UFSet &operator=(UFSet &R);
  void weightUnion(int rootpos, int childpos);
  //折叠压缩规则压缩路径算法
  int CollapsingFind(int pos);
  void printUFSet();
};
// 初始化为单个元素
UFSet::UFSet(int sz) {
  size = sz;
  parent = new int[size];
  for (int i = 0; i < size; i++) {
    parent[i] = -1;
  }
}
UFSet &UFSet::operator=(UFSet &R) {
  size = R.size;
  parent = new int[size];
  for (int i = 0; i < size; i++) {
    parent[i] = R.parent[i];
  }
  return *this;
}

// 查找 pos 的根
// Find性能:搜索时间不会超过树的高度加1
int UFSet::Find(int pos) {
  if (pos < 0 || pos >= size) {
    cout << "pos索引非法" << endl;
    return -1;
  }
  while (parent[pos] >= 0) {
    pos = parent[pos];
  }
  return pos;
}
// 把两个不相交的集合合并 root指示 pos,执行一次Union时间为O(1),N次为O(N)
// 性能:并查集的Union合并与Find查找实现虽然简单,但性能不好
//      当N个元素自成一个单元素的集合S[i],且树结构为N个棵树组成森林,有parent[k]=-1,其中i,k取值[0,n)
//      执行一次Find(i)要从被搜索元素出发,沿父指针链逐个走到根,时间为O(i),N次搜索需要时间为O(N^2),因此产生了退化的树
bool UFSet::Union(int rootpos, int childpos) {
  //如果 childpos 已合并过,则不合并
  if ((rootpos >= 0 && rootpos < size) && (childpos >= 0 && childpos < size) &&
      (parent[childpos] == -1)) {
    parent[rootpos] += parent[childpos];
    parent[childpos] = rootpos;
    return true;
  }
  return false;
}

// 加权规则改进 Union,可减少路径长度,把节点少的树合并到节点多的树作为子树
// 性能:执行一次weightUnion的时间比执行一次Union多,但仍然在O(1)范围内
//      归纳法证明,设置T为一系列合并操作weightUnion建立的M个节点的树,
//      则T的高度不会超过log2M(以2为底M的对数,向下取整)
void UFSet::weightUnion(int rootpos, int childpos) {
  int r1 = Find(rootpos), r2 = Find(childpos);
  int temp;
  //根索引不等,说明不属于同一棵树(同一集合)
  if (r1 != r2) {
    temp = parent[r1] + parent[r2];
  }
  //因为parent[r2]、parent[r1]均小于0,所以当parent[r2] < parent[r1]时,节点r2数量大于节点r1的数量
  //此时可将r1合并到节点r2下面,让r1指向r2
  if (parent[r2] < parent[r1]) {
    parent[r2] = temp;
    parent[r1] = r2;
  } else {
    parent[r2] = r1;
    parent[r1] = temp;
  }
}

//折叠压缩规则,完成单个搜索,所需时间大约增加一倍,但这能减少在最坏情况下执行一些列操作所需时间
int UFSet::CollapsingFind(int pos) {
  int j;
  for (j = pos; parent[j] >= 0; j = parent[j])
    ;
  while (pos != j) {
    int temp = parent[pos];
    parent[pos] = j;
    pos = temp;
  }
  return j;
}
//输出并查集元素索引
void UFSet::printUFSet() {
  for (int i = 0; i < size; i++) {
    cout<<"根索引:"<<i<<", 包含元素索引:";
    for(int j=0;j<size;j++){
      if(i!=j && Find(j)==i){
        cout<<j<<",";
      }
    }
    cout<<endl;
  }
}
int main() {
  //这里有多个元素,每个元素都是独立的,这些元素不重复
  int arr[] = {8, 6, 4, 2, 0, 1, 3, 5, 7, 9};
  int n = sizeof(arr) / sizeof(int);
  UFSet p(n);
  //将arr中独立的元素合并,合并索引为0,1,2,3对应元素为{8,6,4,2}为一个集合
  //合并索引5,6,7,8对应元素为{1,3,5,7}为一个集合
  //其他索引4,9对应孤立的元素0,9不构成集合
  p.Union(0, 1);
  p.Union(0, 2);
  p.Union(0, 3);

  p.Union(5, 6);
  p.Union(5, 7);
  p.Union(5, 8);

  cout << "查找索引为3的根元素索引:" << p.Find(3) << endl;
  cout << "查找索引为8的根元素索引:" << p.Find(8) << endl;
  cout << "查找索引为4的根索引(孤立元素本身):" << p.Find(0) << endl;
  int pos = p.Find(7);
  cout << "查找索引为7的根索引:" << pos << ",元素值为:" << arr[pos] << endl;
  p.printUFSet();
  return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值