并查集

作用:

    合并两个集合,查询两个元素是否在同一个集合中。

代表元:一个结合中的某个元素,此元素可以代表这个集合。

大概实现:

    把集合想象成一棵树,集合中的代表元是根节点,根节点的父亲是自身,根节点有孩子,孙子...,而且我们还有一个变量维护了当前树的节点个数,而且这个值只有根节点有权访问。

形象原理:

    之前学习的时候查看别人博客,觉得有一段解释非常形象,在这里借用一下。

    (原博文地址:https://blog.csdn.net/u013546077/article/details/64509038)

    话说江湖上散落着各式各样的大侠,有上千个之多。他们没有什么正当职业,整天背着剑在外面走来走去,碰到和自己不是一路人的,就免不了要打一架。但大侠们有一个优点就是讲义气,绝对不打自己的朋友。而且他们信奉“朋友的朋友就是我的朋友”,只要是能通过朋友关系串联起来的,不管拐了多少个弯,都认为是自己人。这样一来,江湖上就形成了一个一个的群落,通过两两之间的朋友关系串联起来。而不在同一个群落的人,无论如何都无法通过朋友关系连起来,于是就可以放心往死了打。但是两个原本互不相识的人,如何判断是否属于一个朋友圈呢?

我们可以在每个朋友圈内推举出一个比较有名望的人,作为该圈子的代表人物,这样,每个圈子就可以这样命名“齐达内朋友之队”“罗纳尔多朋友之队”……两人只要互相对一下自己的队长是不是同一个人,就可以确定敌友关系了。

但是还有问题啊,大侠们只知道自己直接的朋友是谁,很多人压根就不认识队长,要判断自己的队长是谁,只能漫无目的的通过朋友的朋友关系问下去:“你是不是队长?你是不是队长?”这样一来,队长面子上挂不住了,而且效率太低,还有可能陷入无限循环中。于是队长下令,重新组队。队内所有人实行分等级制度,形成树状结构,我队长就是根节点,下面分别是二级队员、三级队员。每个人只要记住自己的上级是谁就行了。遇到判断敌友的时候,只要一层层向上问,直到最高层,就可以在短时间内确定队长是谁了。由于我们关心的只是两个人之间是否连通,至于他们是如何连通的,以及每个圈子内部的结构是怎样的,甚至队长是谁,并不重要。所以我们可以放任队长随意重新组队,只要不搞错敌友关系就好了。于是,门派产生了。

操作:

    初始化集合,合并集合,查询两个元素是否在同一个集合,查找一个元素所在集合的代表元素。前面三个操作是暴露给用户的接口,后面一个是代码服用私有的函数,无需给用户展示。

代码实现:

type Node struct {
	n interface{}
}

type UnionFind struct {
	fatherMap map[Node]Node
	sizeMap   map[Node]int32
}

Node是集合元素的类型,UnionFind是并查集的结构,我们采用哈希表实现。下面会说具体实现。

fatherMap:

    key:元素

    val:元素所在集合的代表元

sizeMap:

    key:代表元

    val:代表元所在集合的元素个数(包括代表元)

func (this *UnionFind) MakeSets(nodes []Node) {

	for key := range this.fatherMap {
		delete(this.fatherMap, key)
	}

	for key := range this.sizeMap {
		delete(this.sizeMap, key)
	}

	for _, opt := range nodes {
		this.fatherMap[opt] = opt
		this.sizeMap[opt] = 1
	}
}

函数功能:传入一系列元素,并且把每个元素初始化成一个集合。

函数实现:首先清空两个哈希表,确保恢复到初始化的状态,然后每个元素成为一个单独的集合。

func (this *UnionFind) findHead(node Node) Node {

	father, _ := this.fatherMap[node]

	if father != node {
		this.findHead(father)
		this.fatherMap[node] = father
	}

	return father
}

函数功能:查询一个元素的代表元并且返回。

函数实现:代表元就是fatherMap中以形参作为键的元素值,这个过程中有着压缩路径的优化。

压缩路径:就是在合并的过程中可能会出现树的深度增长,不利于findHead,此时我们在findHead的过程中,如果发现树的深度大于2,那么我们就将树的深度优化为2,也就是说这个集合中,代表元是根节点,其他元素都是根节点的直接孩子节点。这样便于查询。

func (this *UnionFind) IsSameSet(n1, n2 Node) bool {
	return this.findHead(n1) == this.findHead(n2)
}

函数功能:查询两个元素是否在同一个集合。

函数实现:如果两个集合的代表元相同则在一个集合。


func (this *UnionFind) Union(n1, n2 Node) {

	f1 := this.findHead(n1)
	f2 := this.findHead(n2)

	if f1 != f2 {
		s1 := this.sizeMap[f1]
		s2 := this.sizeMap[f2]

		if s1 <= s2 {
			this.fatherMap[f1] = f2
			this.sizeMap[f2] += this.sizeMap[f1]
			delete(this.sizeMap, f1)
		} else {
			this.fatherMap[f2] = f1
			this.sizeMap[f1] += this.sizeMap[f2]
			delete(this.sizeMap, f2)
		}
	}
}

函数功能:合并两个元素所在的集合

函数实现:如果两个元素在一个集合,什么也不做。如果不在一个集合,比较两个集合的个数,将小集合的代表元直接指向大集合的代表元,并且更新大集合的sizeMap值,同时删除原来小集合的sizeMap值,因为此时原来的小集合的代表元已经变更了,这样就合并了两个集合。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值