并查集的实现以及优化(c++)

一、并查集 Union Find
1.不一样的树形结构
2.主要解决连接问题 Connectvity Problem
2.1 网络中结点间的连接状态 网络是个抽象概念,用户之间形成的网络
2.2 数学类中的集合类实现
2.3 比路径问题要回答的问题少,连接问题只用判断是否相连,路径问题还要求出路径
3. 对于一组数据,主要支持两个动作
3.1 union(p,q) 两个元素合并连接
3.2 find§
4.用来回答一个问题
4.1 isConnected(p,q) 是否连接
5. 并查集的基本数据表示
5.1 用数组表示每个元素的连接关系,称为id组,id号相同的为一个组,合并两个组就是把id号改成一样的
总结:其实通俗来看,并查集主要是用来在大数据的时候,判断两个节点是否连接,即例如在100000个人中判断任意两个人之间是否有关系。
二、并查集的实现以及优化
1.并查集的初步实现
由下面代码可知,私有成员变量主要有两个,count表示元素总数,id数组表示每一个元素是哪一个集合。构造函数中初始化id数组是其本身,公有类主要是三种方法,find查找某一个元素是哪一个类别号,isConnected判断两个元素是否相连,即判断两个元素的id号是否相同,unionElements用来合并两个元素,通过改变其中某一个类的类别号进行合并。

namespace UF1{
 class UnionFind {
 private:
  int count;
  int* id;
 public:
  UnionFind(int n) {
   this->count = n;
   id = new int[n];
   for (int i = 0; i < n; i++)
    id[i] = i;
  }
  ~UnionFind() {
   delete[] id;
  }
  int find(int p) {
   assert(p >= 0 && p < count);
   return id[p];
  }
  bool isConnected(int p, int q) {
   return find(p) == find(q);
  }
  void unionElements(int p, int q) {
   int pId = find(p);
   int qId = find(q);
   if (pId == qId)
    return;
   else {
    for (int i = 0; i < count; i++) {
     if (id[i] == pId)
      id[i] = qId;
    }
   }
    
  }
  void showUnion() {
   for (int i = 0; i < count; i++) {
    cout <<i<<": "<< id[i] << endl;
   }
  }
 };
}

2.优化1,对于每一次查找id号都要遍历元素,这里采用了一种链式存储的方式,采用了parent数组,用于存放某一个节点的父亲节点。

namespace UF2 {
 class UnionFind {
 private:
  int count;
  int* parent;
 public:
  UnionFind(int n) {
   this->count = n;
   parent = new int[n];
   for (int i = 0; i < count; i++)
    parent[i] = i;
  }
  ~UnionFind() {
   delete[] parent;
  }
  int find(int p) {
   assert(p >= 0 && p < count);
   while (p != parent[p]) {
    p = parent[p];
   }
   return p;
  }
  bool isConnected(int p, int q) {
   return find(p) == find(q);
  }
  void unionElements(int p, int q) {
   int pRoot = find(p);
   int qRoot = find(q);
   if (pRoot == qRoot)
    return;
   else
    parent[pRoot] = qRoot;
  }
 };

3.优化2:在优化1的基础上,增加一个判断以i为根的底下的元素个数,在合并两个集合的时候,通过把元素个数少的增加到元素个数多的集合中,节约查找时间。

namespace UF3 {
 class UnionFind {
 private:
  int count;
  int* parent;
  int* size;
 public:
  UnionFind(int n) {
   this->count = n;
   parent = new int[n];
   size = new int[n];
   for (int i = 0; i < count; i++) {
    parent[i] = i;
    size[i] = 1;
   }
  }
  ~UnionFind() {
   delete[] parent;
   delete[] size;
  }
  int find(int p) {
   assert(p >= 0 && p < count);
   while (p != parent[p]) {
    p = parent[p];
   }
   return p;
  }
  bool isConnected(int p, int q) {
   return find(p) == find(q);
  }
  void unionElements(int p, int q) {
   assert(p >= 0 && p < count);
   assert(p >= 0 && p < count);
   int pRoot = find(p);
   int qRoot = find(q);
   if (size[pRoot] > size[qRoot]) {
    parent[qRoot] = pRoot;
    size[pRoot] += size[qRoot];
   }
   else {
    parent[pRoot] = qRoot;
    size[qRoot] += size[pRoot];
   }
  }
 };
}

4.优化3:在优化1的基础上,再增加一个rank数组表示以i为根的层数,在合并两个集合的时候,通过把层数少的并入到层数多的树中,节约查找时间。

namespace UF4 {
 class UnionFind {
 private:
  int count;
  int* parent;
  int* rank;//表示以i为根的层数
 public:
  UnionFind(int n) {
   this->count = n;
   parent = new int[n];
   rank = new int[n];
   for (int i = 0; i < count; i++) {
    parent[i] = i;
    rank[i] = 1;
   }
  }
  ~UnionFind() {
   delete[] parent;
   delete[] rank;
  }
  int find(int p) {
   assert(p >= 0 && p < count);
   while (p != parent[p]) {
    p = parent[p];
   }
   return p;
  }
  bool isConnected(int p, int q) {
   return find(p) == find(q);
  }
  void unionElements(int p,int q) {
   assert(p >= 0 && p < count);
   assert(q >= 0 && q < count);
   int pRoot = find(p);
   int qRoot = find(q);
   if (rank[pRoot] > rank[qRoot])
    parent[qRoot] = pRoot;
   else if (rank[pRoot] < rank[qRoot])
    parent[pRoot] = qRoot;
   else {
    parent[qRoot] = pRoot;
    rank[pRoot] += 1;
   }
  }
 };
}

5.优化4:在优化3的基础上,对于查找某一个元素的类别号,即查找某一个元素的根,都是逐级遍历,由最底一层到倒数第二层,最后到根节点。为了节约时间这里采用了跳跃查询的方法,即不是逐级判断而是跳跃判断。

namespace UF5 {
 class UnionFind {
 private:
  int count;
  int* parent;
  int* rank;//表示以i为根的层数
 public:
  UnionFind(int n) {
   this->count = n;
   parent = new int[n];
   rank = new int[n];
   for (int i = 0; i < count; i++) {
    parent[i] = i;
    rank[i] = 1;
   }
  }
  ~UnionFind() {
   delete[] parent;
   delete[] rank;
  }
  int find(int p) {
   assert(p >= 0 && p < count);
   if (p != parent[p])
    parent[p] = find(parent[p]);
   return parent[p];
  }
  bool isConnected(int p, int q) {
   return find(p) == find(q);
  }
  void unionElements(int p, int q) {
   assert(p >= 0 && p < count);
   assert(q >= 0 && q < count);
   int pRoot = find(p);
   int qRoot = find(q);
   if (rank[pRoot] > rank[qRoot])
    parent[qRoot] = pRoot;
   else if (rank[pRoot] < rank[qRoot])
    parent[pRoot] = qRoot;
   else {
    parent[qRoot] = pRoot;
    rank[pRoot] += 1;
   }
  }
 };
}

三、并查集的测试
这里由于空间优先给出一种测试代码,具体实现时只需要更黄testUF5,还有两个UF5空间名称即可测试其他优化的并查集,这里为了体现优化的效果,随机抽取三组数,判断每一组中两个数是否相连,打印出判断时间,比较

void testUF5(int n) {
  srand(time(NULL));
  UF5::UnionFind uf = UF5::UnionFind(n);
  time_t stratTime = clock();
  for (int i = 0; i < n; i++) {
   int a = rand() % n;
   int b = rand() % n;
   uf.unionElements(a, b);
  }
  cout << endl;
  for (int i = 0; i < 3; i++) {
   int c = rand() % n;
   int d = rand() % n;
   cout << c << " and " << d << "   是否相连:" << uf.isConnected(c, d) << endl;
  }
  time_t endTime = clock();
  cout << "test5:" << double(endTime - stratTime) / CLOCKS_PER_SEC << "s" << endl;
 }

这里是主函数给定n的值,依次调用每一种并查集实现代码,时间比较如下图所示。

int n=100000;
 UnionTest::testUF1(n);

其中,test1是未优化的初始并查集,test2是优化1,test3是优化2,
test4是优化3,test5是优化4。
在这里插入图片描述

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值