并查集概述

主要用于解决一些元素分组问题,管理一系列不相交的集合,支持两种操作
合并(Union):把两个不相交的集合合并为一个集合
查询(Find):查询两个元素是否在同一个集合中
重要思想:用集合中的一个元素代表集合,就好比一个帮派用一个帮主来代表

并查集的基本思想非常简单,代码较为容易实现

//初始化,这里用father来保存每个元素的父节点,一开始,使每个元素的父节点都是自己本身
int[] father;
void init(int n){
	father = new int[n];
	for(int i=0;i<n;i++){
		father[i] = i;
	}
}
//查询,要判断两个节点是否属于同一个集合,只需要看他们的根节点是否相同即可,所以这里查询的时候只需要不断的向上一层一层的访文父节点,直至根节点(根节点的标志就是其父节点就是自己本身)
int find(int x){
	if(father[x] == x){
		return x;
	}else{
		return find(father[x]);
	}
}
//合并, 这里直接把i的根节点修改为j(反之将j的根节点修改为i也可以)
void merge(int i, int j){
	father[find[i]] = j;
}

并查集的基本功能以及得以实现,但是效率在某些情况下较为低下,比如现在有{1,2} 和 {3},现在希望merge(2,3),于是由2找到1,father[1] = 3,变成了{3,1,2},这时还有一个集合{4},希望能够merge(2,4),于是由2找到1找到3,然后father[3] = 4,变成了{4,3,1,2}
显然,随着元素的增多,这条链会变得越来越长,由一个任意节点找到对应的根节点会变得越来越慢,此时,就可以压缩路径了,也就是把每一个节点的父节点都直接设置为根节点,每一次的查询都会变得十分简单:

//压缩路径之后的查找
int find(int x){
	if(x==father[x]{
		return x;
	}else{
		//将路径上的每一个节点的父节点直接设置为根节点
		father[x] =find(father[x]);
		return father[x];
	}
}

此时,并查集的时间复杂度已经可以令人接受了,但是考虑以下的情况:
当希望合并{4,3,1,2} 和 {5}的时候,选择father[4] = 5更好,还是选择father[5] = 4更好呢?答案应该是显而易见的,选择father[5] = 4可以更大限度的减少树的深度,使用一个rank[]数组来记录每一个根节点所对应的树的深度,一开始,将每一个元素的rank都设置为1,在合并的时候,先比较两个根节点的rank,把rank较小的节点往rank较大的节点上合并

以下就是按秩合并和路径压缩后的代码

//初始化
int[] father;
int[] rank;
void init(int n){
	father = new int[n];
	rank = new int[n];
	for(int i=0;i<n;i++){
		father[i] = i;
		rank[i] = 1;
	}
}
//查找
int find(int x){
	if(x==father[x]){
		return x;
	}else{
		//将路径上的每一个节点的父节点直接设置为根节点
		father[x] =find(father[x]);
		return father[x];
	}
}
//合并
void merge(int i, int j){
	int x = find(i);
	int y = find(j);
	if(x!=y){
		//按秩合并
		if(rank[x]<rank[y]){
			//这里总是让x的rank保持较大,y的rank保持较小
			int temp = x;
			x = y;
			y = temp;
		}
		father[y] = x;
		if(rank[x]==rank[y]){
			//这里不用判断合并后到底是rankx[x]++还是rank[y]++就是因为在上面我们总是让y合并在x上
			rank[x]++;
		}
	}
}

这里需要注意,当路径压缩和按秩合并一起使用时,我们对于rank增加的更新总是能够及时的做出反应,但是rank的减少却不能很好的及时更新,但是实际上rank不能及时更新减少,并不会对结果造成太大影响,而且对rank保持持续的实时更新,代码会更加的复杂,所以这里不去更新rank的减少

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值