(进阶数据结构)并查集

主要内容

        假设有一群人,给出所有的交友关系(即哪两个人要交朋友),且朋友的朋友可以作为盟友,据此将这群人划分为不同的团体。

        核心思想是用数字给这些人编号,然后将一个团体中的任意一人看作老大,其他人看作他的下属,检查两个人是否属于一个团体就看最上面的老大是不是相同的人。如果出现两个团体要合并时,就把其中一个团体的老大改成另一个老大的下属,如下图,2和4要交朋友,就让3变成0的下属,或者让0变成3的下属。下图是让0变成3的下属

图1

        虽然是树状结构,但是由于上司不需要知道自己的下属是谁,只要让下属找得到自己的上司就行,所以用数组存储即可。下标表示对应编号的人,数组中存储每个人的上司是谁,如果要找老大,就一层一层地向上找。

        

        我们用负数表示此人是一个团体的老大,所以数组初始化为-1,当两人要交友时,例如3和4要交友,将3视作老大,把下标4对应的元素-1加到3对应的元素上,然后再把下标4的元素改成3,这样不仅让4能找到自己的上司,还能统计小团体的人数。合并团体也是类似,不管是谁要交友,都看成两个老大在交友,然后还是同样的操作,如下。

图1在编号2与编号4交友前的数组:

交友后:

下面是对应的树状图

不难看出如果数据量大的话树可能会堆得很高,这样处于底层的人查找老大的效率就比较低下了。因此我们要将结构进行优化,尽量让所有的下属直接连接老大,如下。

只需在查找老大是谁时将下属连接到老大即可,因为每次输入交友关系时都会调用查老大的函数,而查找老大是通过递归查找的,将老大的编号返回时可以沿途将各个下属的上司都改成老大,尽管不能保证一定能变成上面这样的最优结构,但是已经非常简洁高效了。

参考代码

#include<vector>
using namespace std;

class Unionfindset{
public:
	Unionfindset(size_t n)
	: _ufs(n,-1)
	{}

	int Findroot(int x) {//找老大,返回老大的编号
		if (_ufs[x] < 0) return x;
		else  return _ufs[x] = Findroot(_ufs[x]);//直接让下属连接老大,提高找老大的效率
	}

	void Union(int a, int b) {//交友、联合,将a看作上司
		int ar = Findroot(a);
		int br = Findroot(b);
		if (ar != br) {
			_ufs[ar] += _ufs[br];//算人数
			_ufs[br] = ar;//认老大
		}
	}

	size_t Setsize(int x) {//返回x所在团体的大小
		return -_ufs[Findroot(x)];
	}

	size_t count() {//返回团体个数
		size_t ans = 0;
		for (auto e : _ufs) {
			if (e < 0) ans++;
		}
		return ans;
	}
private:
	vector<int> _ufs;
};

如果只是写算法题,只写有用的功能即可

例题

1.合根植物 - 蓝桥云课

LCR 116. 省份数量 - 力扣(LeetCode)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值