【C++高阶数据结构】并查集

🏆个人主页企鹅不叫的博客

​ 🌈专栏

⭐️ 博主码云gitee链接:代码仓库地址

⚡若有帮助可以【关注+点赞+收藏】,大家一起进步!

💙系列文章💙



💎一、概念

并查集是多个独立集合的合集,用于表示数据之间的关系,并查集中的每一个集合是用多叉树来表示的。

举例:现在有十个元素,每个元素的值都初始化为-1

在这里插入图片描述

现在将0,3,4这三个元素构成一个集合,我们让0为根,将根放到3和4下标对应的元素上,同时将3和4原本对应的元素加给0下标对应的元素

在这里插入图片描述

同理将2,5,7,8和1,6,9构成一个集合,默认2和1为根

在这里插入图片描述

同理将0集合和1集合合并,默认0是根

在这里插入图片描述
总结:

  1. 数组的下标对应集合中元素的编号
  2. 数组中如果为负数,负号代表根,数字的绝对值代表该集合中元素个数
  3. 数组中如果为非负数,代表该元素双亲在数组中的下标

💎二、实现

🏆1.框架

底层使用vector实现,并且全部初始化为-1

class UnionFindSet {
public:
	UnionFindSet(int sz)
		:_ufs(sz, -1) {
	}
private:
	vector<int> _ufs;
};

🏆2.查找元素属于哪个集合

  1. 当元素对应的下标对应的内容为正数时(也就是该元素不为根),我们需要更新该下标,也就是index=_ufs[index],让自己成为双亲
  2. 继续判断,如果对应内容为负数,说明该下标是根,直接返回
int FindRoot(int x){
    while (_ufs[x] >= 0) {
        //更新亲人
        x = _ufs[x];
    }
    return x;
}

压缩路径

并查集的访问是依靠数组下标实现的随机访问,时间复杂度为O(1),只有数据样本量极大的时候,可以考虑路劲压缩

在这里插入图片描述

	//查找元素属于哪个集合
	int FindRoot(int x){
		int root = x;
		while (_ufs[root] >= 0) {
			//更新亲人
			root = _ufs[root];
		}
		//压缩路径
		while (_ufs[x] > 0) {
			int parent = _ufs[x];
			_ufs[x] = root;
			x = parent; 
		}
		return root;
	}

🏆3.合并两个集合

  1. 找到两个集合对应的根
  2. 如果两个根相等说明两个集合相等,如果两个根不相等则让其中一个根归并到一个根的下面,更新新的双亲的内容和被并入的根的内容
void Union(int x, int y) {
    int root1 = FindRoot(x);
    int root2 = FindRoot(y);
    if (root1 != root2) {
        _ufs[root1] += _ufs[root2];
        _ufs[root2] = root1;
    }
}

🏆4.判断两个元素是否在同一个集合当中

如果两个集合根相等说明在同一个集合当中

	bool isUnion(int x, int y) {
		return FindRoot(x) == FindRoot(y);
	}

🏆5.统计集合个数

遍历一遍数组,统计内容为负数的下标的个数,即统计了集合个数

	int size() {
		int count = 0;
		for (auto e : _ufs) {
			if (e < 0) {
				++count;
			}
		}
		return count;
	}

💎三、练习

🏆1.剑指 Offer II 116. 省份数量

传送门

在这里插入图片描述

思路:遍历所有元素,将相关联的数放到同一个集合当中,最后统计集合数量即可

class Solution {
public:
 int FindRoot(const vector<int>& v, int x){
     while(v[x] > 0){
         x = v[x];
     }
     return x;
 }
 int findCircleNum(vector<vector<int>>& isConnected) {
     vector<int> v(isConnected.size(), -1);
     for(int i = 0; i < isConnected.size(); ++i){
         for(int j = 0; j < isConnected[0].size(); ++j){
             if(isConnected[i][j] == 1){//1表示该元素在同一个元素中
                 int root1 = FindRoot(v, i);
                 int root2 = FindRoot(v, j);
                 if(root1!=root2){
                     v[root1] += v[root2];
                     v[root2] = root1;
                 }
             }
         }
     }
     int count = 0;
     for(auto e : v){
         if(e < 0){
             ++count;
         }
     }
     return count;
 }
};

🏆2.990. 等式方程的可满足性

传送门

在这里插入图片描述

思路:如果两个字符相等,则放入同一个集合之中。
如果两个字符不相等,它们应当在不同的集合中,此时查找它们所在集合的根,如果相同则说明这两个字符在同一个集合中,返回false。

class Solution {
public:
int FindRoot(const vector<int>& v, int x){
  while(v[x] >= 0){
      x = v[x];
  }
  return x;
}
bool equationsPossible(vector<string>& equations){
vector<int> v(26, -1);
//第一遍遍历,将相同的值放到同一个集合当中
for(auto& e : equations){
  if(e[1] == '='){
      int root1 = FindRoot(v, e[0]-'a');
      int root2 = FindRoot(v, e[3]-'a');
      if(root1 != root2){
          v[root1]+=v[root2];
          v[root2] = root1;
      }
  }
}
//第二遍遍历,讲不同的值比较,如果相同说明悖论了,返回false
for(auto& e : equations){
  if(e[1] == '!'){
      int root1 = FindRoot(v, e[0]-'a');
      int root2 = FindRoot(v, e[3]-'a');
      if(root1 == root2){
          return false;
      }
  }
}
return true;
}
};

评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

penguin_bark

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值