并查集之实现

介绍并查集

并查集的两个主要功能

  1. 两个数据是否属于一个集合
  2. 把两个数据合并成为一个集合

背景介绍

我们现在有一个数组,如图:

在这里插入图片描述

我们把这个数组的每一个元素都看成是一个集合

在这里插入图片描述

并查集就是对这些集合做操作的,目前每一个集合只有一个数字。如果我们要判断这个数组里面的两个数组是否属于一个集合的话,我们的思路是:使用哈希列表,把每一个数值和集合的对应关系用哈希列表记录好,这样的话我们的查询速度非常的快,判断两个数据是否属于一个集合的时间复杂度是O(1)级别。但是我们还有第二个操作,我们还需要实现把两个集合的数据合并到一个集合的功能,那么也就是说我们要通过**循环枚举,把一张哈希表里的内容全部写入到另外一个地方,时间复杂度是O(N)。太慢了。

接下来我们换一个思路,我们不使用哈希列表了,我们使用链表,把这些数据全部串在一起,这样把两个集合合并成一个集合的时间复杂度是O(1)。我们变快了,但是事情真的解决了吗??答案是没有,因为这个时候我们查询两个数据是否属于一个集合的时候我们需要遍历链表,时间复杂度是O(n)

因此这些基础的数据结构是无法做到我们追求的效率的,我们理想的效率是,当数据结构建立之后,我们的查询和合并的时间复杂度全部都是O(1)。因此我们提出了并查集这个概念

并查集大概思路:

并查集初始化

在这里插入图片描述

每一个元素有一个指针,这个指针指向自己,我们把指针指向自己的节点称作代表节点,也就是说我们寻找两个数据是否在同一个集合的时候,我们就观察代表节点就行了,如果在同一个集合,那么他们的代表节点一定也是同一个。如图,目前所有的节点都是代表节点,所以所有的数字都在不同的集合。这个时候我们把数字4和数字7合并成一个集合。

在这里插入图片描述

我们把这些指针的指向关系使用数组来代替。

这里我们需要的东西是:

vector<int> parent;//这个parent记录的是当前节点的下标所对应的组的代表节点,代表节点就是自己指向自己的节点。
vector<int> size;//用来记录我当前组(集合)底下连了多少个节点,我们当然是数量小的移动到数量大的地方去以减小开销。
vector<int> help;//模拟栈,用来压缩路径

压缩路径

在这里插入图片描述

如何让节点直接指向4呢??我们使用栈来模拟,具体看代码。

用数组模拟

//我这个并查集里面全部放的是下标
class Union {
private:
	int sets;//用来记录目前有多少集合个数
	vector<int> parent;//用来记录此节点下标的上级节点下标是什么
	vector<int> size;//用来记录关键节点底下有多少个节点连着在
	vector<int> help;//用来压缩路径的数组,这里我们把这个数组当作stack来使用

	int findfather(int i)//这个传的是下标
	{
		int sakura = 0;
		while (i != parent[i])
		{
			i = parent[i];
			help[sakura++] = i;
		}//最后流的下来的就是根节点了
		sakura--;
		for (; sakura >= 0; sakura--)
		{
			parent[help[sakura]] = i;
			help[sakura] = 0;
		}
		return i;
	}
public:
	//构造函数
	Union(vector<int>& nums)
	{
		int n = nums.size();
		parent.resize(n);
		size.resize(n);
		help.resize(n);//这样的话可以确保一次性把内存分配够了
		sets = n;//初始化集合个数为n个
		for (int i = 0; i < n; i++)
		{
			parent[i] = i;//目前所有节点的上层节点都是它本身
			size[i] = 1;//所有代表节点底下都有1个兵
		}
	}

	bool isSameFather(int i, int j)
	{
		return findfather(i) == findfather(j);
	}

	int getsets()
	{
		return sets;
	}

	void together(int i, int j)
	{
		//先找到这两个下标的根节点
		int A = findfather(i);
		int B = findfather(j);
		if (A != B)//只有这两个节点不属于同一个集合才有必要进行合并
		{
			int big = size[A] > size[B] ? A : B;
			int small = big == A ? B : A;
			parent[small] = big;
             size[big] += size[small];
			size[small] = 0;
			sets--;
		}
	}
};

用节点封装

class Node {//把这个节点封装一下
private:
	int val;
public:
	Node(int x)
		:val(x)
	{}
};

class Union {
private:
	unordered_map<int, Node*> node;//我的值直接对应我封装好的一个节点
	unordered_map<Node*, Node*> parent;//节点和其代表节点
	unordered_map<Node*, int> size;//代表节点和对应集合的大小

	Node* findfather(Node* n)
	{
		stack<Node*> path;
		Node* cur = n;
		while (parent[cur] != cur)
		{
			cur = parent[cur];
			path.push(cur);
		}

		if (!path.empty())
		{
			Node* now = path.top();
			parent[now] = cur;
			path.pop();
		}
		return cur;
	}
public:
	Union(vector<int>& nums)
	{
		for (auto e : nums)
		{
			node* n = new Node(e);//这里我直接拿数组里面的值做初始化
			node[e] = n;//这里表达的是我的值直接对应了一个节点,进行初始化
			parent[n] = n;//目前我的根节点,也就是代表节点都是自己
			size[n] = 1;
		}
	}

	bool isSameParent(int a, int b)
	{
		return findfather(node[a]) == findfather(node[b]);
	}

	int setsize()
	{
		return size.size();
	}

	void together(int a, int b)
	{
		Node* A = findfather(node[a]);
		Node* B = findfather(node[b]);
		if (A != B)
		{
			Node* big = size[A] > size[B] ? A : B;
			Node* small = big == A ? B : A;
			parent[small] = big;
			size[big] += size[small];
			size.erase(small);
		}
	}
};
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

胡桃姓胡,蝴蝶也姓胡

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

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

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

打赏作者

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

抵扣说明:

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

余额充值