作用:
合并两个集合,查询两个元素是否在同一个集合中。
代表元:一个结合中的某个元素,此元素可以代表这个集合。
大概实现:
把集合想象成一棵树,集合中的代表元是根节点,根节点的父亲是自身,根节点有孩子,孙子...,而且我们还有一个变量维护了当前树的节点个数,而且这个值只有根节点有权访问。
形象原理:
之前学习的时候查看别人博客,觉得有一段解释非常形象,在这里借用一下。
(原博文地址: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值,因为此时原来的小集合的代表元已经变更了,这样就合并了两个集合。