并查集

并查集主要用来解决:
(1)一群节点有分为几个集合问题。(朋友圈)
(2)判断两个点是不是在一个集合里。
(3)判断一个图里是不是有环

并查集思路:

(参考bilibili的博主例子,为了查阅方便,记录下来。)

背景:我们想要判断以下图中是不是有环的问题。先构造出以下场景。

int connected[6][2] = {{0, 1}, {1, 2}, {1, 3},
					   {2, 3}, {3, 4}, {2, 5}};

其他场景的连线问题也是万变不离其宗。
可能会遇见不是整型,而是字符串的形式,比如:

string connected[N][2] = {{john, lisa}, 
						{lisa, cale},
						{lisa, jobe},
						{laowang, xiaomei}
						...};

遇见这种情况怎么办?
采用map或者unordered_map进行字符串和int整型对应

unordered_map<string, int> map;
int j = 0;
for(int ci : connected){
	//一个字符对应一个数字编号
	if(map.count(ci[0])==0) map.insert(make_pair(ci[0], j++));
	if(map.count(ci[1])==0) map.insert(make_pair(ci[1], j++));
	auto it1 = map.find(ci[0]);
	auto it2 = map.find(ci[1]);
	int x = it1->second;
	int y = it2->Second;
	...
}

上面的场景都可以 转化成下面的这个图的形式
在这里插入图片描述

(1)最初0和1没有连线的时候,0和1处于两个部分(不同集合)。
在这里插入图片描述
(2)一旦连接我们就将他们连成树的形式(同一个集合)。
也即是读取{0, 1}
在这里插入图片描述
0和1连接,我们人为的给它一个指向,意思是1为根节点,0为子节点

(3)现在继续将{1, 2}加入。
在这里插入图片描述

可以继续将1作为根节点。(也可以将2作为根节点)。

(4)就这样一直将所有的连线加入。

有了场景,下面就开始一步步具体运用并查集算法解决问题了,采用代码进行实现。

步骤一:初始化一个parent数组。初始parent数组里都为-1,也可以都为对应的下标值。(数组长度也就是节点个数)。

为什么采用parent数组?parent[0] = 1表示节点0的根节点为1parent[2] = 1表示节点1的根节点为2,如此你会发现,如果所有节点都连起来的话,那么parent数组最后只会有一个值还为-1,其他都不为-1了(其他都有依赖了)。这也可以看成有几个-1,最后就有几个集合。

//初始化数组
void infinite(int parent[], int n){
	for(int i = 0; i < n; i++) parent[i] = -1;
}
int *parent = new int[N];
infinite(parent, N);

步骤二:寻找根节点。在加入节点的过程中,如果节点x和节点y本身就是在同一个根节点了,那么{x, y}的连线必定会形成一个环。

正如上图加入{0,1},{1,2}后,现在加入{0, 2}。因为节点0和节点2有公共的根节点, 因此再加入{0, 2}就会形成一个环。 当然这也能判断两个节点是不是在同一个集合里。

//循环判断,返回节点的根节点。
int find_root(int x, int parent[]){
	int x_root = x;
	while(parent[x_root] != -1) x_root = parent[x_root];
	return x_root;
}

步骤三:将两个树(集合)拼接在一起。

//可以返回为bool或者int或者void
//返回为bool时候,返回false表示拼接失败,节点本身有共同的根节点,返回true表示拼接成功
bool union_gather(int x, int y, int parent[]){
	int x_root = find_root(x, parent);
	int y_root = find_root(y, parent);
	//节点x和节点y有公共的根节点。
	if(x_root == y_root) return false;
	else {
		parent[x_root] = y_root;  //强行将y的根节点作为新的根节点
		return true;	
	}
}

当然步骤三的一个问题就是强行将y节点的根节点作为新的根节点。这样有一定的缺点,就是如果是一个长链的时候,就是很长。
因此需要压缩路径,引入rank数组。rank数组初始化为0。表示两个树的高度,将高的树的根节点作为新的根节点

步骤四

最后就是在main函数中进行判断了

if(union_gather(x, y, parent)) cout<<没有环<<endl;
else cout<<"有环"<<endl;

现在依次总结开篇的三个问题:
(1)一群节点有分为几个集合问题。(朋友圈)
(2)判断两个点是不是在一个集合里。
(3)判断一个图里是不是有环
多加一个,还要一个问题4
(4)统计某个节点所在集合的总数

解决(1):在前面说过,只需要在所有连线连接完成后,统计parent数组里面的-1的个数,有几个-1,也就是有几个根节点,也就是有几个集合。

解决(2)(3):在union_gather的返回值为bool的时候,当x_root == y_root的时候,就是这两个点在同一个集合,就是这个图有环。

解决(4):这个稍微复杂一点点,因为需要引入一个新的数组people,并初始化为1,长度也是节点个数。

void union_gather(int x, int y, int parent[], int people[]){
	int x_root = find_root(x, parent);
	int y_root = find_root(y, parent);
	//节点x和节点y有公共的根节点。
	if(x_root != y_root) {
		parent[x_root] = y_root;  //y_root为根节点
		people[y_root] += people[x_root];  //加到y_root上
	}
}

//最后直接找某个节点z的根节点在people数组中的数值就行了
int num = people[find_root(z, parent)];

以上
主要用来快速复习查看

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值