并查集概述

主要用于解决一些元素分组问题,管理一系列不相交的集合,支持两种操作
合并(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、付费专栏及课程。

余额充值