目录
最近学习了并查集,和大家分享一下感触。
1.概念
在计算机科学中,并查集是一种树型的数据结构,用于处理一些不交集(Disjoint Sets)的合并及查询问题。有一个联合-查找算法(union-find algorithm)定义了两个用于此数据结构的操作:
Find:确定元素属于哪一个子集。它可以被用来确定两个元素是否属于同一子集。
Union:将两个子集合并成同一个集合。
并查集是为了判断某元素是否同属于一棵树,或者用来判断一个森林中有几棵树。
为了实现并查集,我们会用到
数组pre[ x ]来记录x的前驱节点是谁
数组find[ x ]来找到x的根节点是谁(可以用来判断两个节点是否属于同一棵树,因为若两个节点有同一个根节点,那么他们必定属于同一棵树)
Union(x,y)函数来合并x节点和y节点所存在的两棵树。
2.Union()函数的实现
并查集,并查集,顾名思义肯定要有“并(Union)”和“查(find)”,这两部分功能。下面就来介绍一下Union函数如何实现以及优化。
首先我们要对pre[ ]数组进行初始化,在各个节点没有连接在一起之前他们都是一个个孤立的点,因此他们每个点都是自己的根节点
接下来,如图,如果1和2这两个节点直接有联系,那么我们要对pre[ 1 ]或者pre[ 2 ]的值进行改变,那么我们到底选择谁当另一个节点的“根节点”呢?
其实这里pre[ 1 ]=2或者pre[ 2 ]=1都是正确的,意思是1节点的根节点是2(2节点的根节点是1)
这是初始化过后的pre[ ]数组
我们采用pre[ 2 ]=1之后的pre[ ]数组。
这时候我们再次在2和3节点之间连线
那么小伙伴们思考一下,我们这里可以像上一步那样直接令pre[ 2 ]=3吗?
显然不可以,因为你会发现如果直接令pre[ 2 ]=3的话,我们的结点1和2直接的关系就“消失了”。
那么我们要怎么做呢?
既然我们不能直接把结点2和3“绑”在一起,我们不妨试试通过结点1间接的完成这一项“使命”。
我们令pre[ 1 ]=3
这样我们通过pre[ 1 ]=3把结点2和3间接串联在了一起
总结下来,要想将x结点和y结点连接起来,pre[ ]数组赋值的方法是:
pre[ x的根结点 ]=y的根结点(3结点的根结点是3)
最后Union函数的代码实现如下
具体的find函数接下来会讲解。
2.find()函数
上面我们讲到了如何构建pre[ ]这个记录前驱节点的数组,其中我们最后总结了pre[ ]数组的赋值方法是:pre[ x的根结点 ]=y的根结点(3结点的根结点是3)
那么x的根节点我们要怎么去找呢?这棵树中的所有节点都直接或者间接的指向这个根节点。这便是我们find()函数的作用,我们以刚刚修改过的pre[ ]数组为例,1的根节点是3,2的根节点也是3,3的根节点是3
我们会发现这棵树的根节点x都满足pre[ x ]==x.由此我们就知道了find函数要怎么写:
这种find()函数可以帮助我们找到一个节点的根节点
但是我们要考虑到它的时间复杂度,如果这棵树上的节点全部呈现的是一条直线(比如一条直线上有1万个节点),那么当我们查询第一万个节点时需要耗费的时间就要比我们刚刚查询的三个结点的根节点要复杂得多,也要一次又一次的调用递归函数,所以我们想能不能将所有节点的前驱节点直接变成根节点,那样每次只用调用一次find函数就可以找到这个节点的根节点
所以就有了find ( ) 函数的优化
3.find ( ) 函数的优化
就像这样的树结构,我们要尝试把4,5,6等需要多次查询的结点的前驱结点直接设置为结点1,那么在以后查询过程中就可以避免许多麻烦
那么下面给出用递归实现的具体代码:
以上就是这次分享的内容
下面给出这几个部分的具体代码:
#define N 1e5+9; //pre[]数组的初始化
int pre[N];
void Init()
{
int i=0;
for(i=0;i<=n;i++){ //每个节点的前驱节点都设置为自身
pre[i]=i;
}
}
void Union(int x,int y){ //将结点x和结点y合并在一棵树内
int a=find(x),b=find(y); //分别用a和b表示x和y的根节点
if(a!=b){ //判断x和y是否同属一棵树
pre[a]=b; //将x所在的这棵树与y所在的这棵树合并
}
}
int find(int x) //查找结点 x的根结点
{
if(pre[x] == x)
return x; //递归出口:x的上级为 x本身,则 x为根结点
return find(pre[x]); //递归查找
}
int find(int x) //改进查找算法:完成路径压缩,将 x的上级直接变为根结点,那么树的高度就会大大降低
{
if(pre[x] == x)
return x; //递归出口:x的上级为 x本身,即 x为根结点
return pre[x] = find(pre[x]); //此代码相当于先找到根结点 rootx,然后 pre[x]=rootx
}
希望走在编程路上的小伙伴们都可以保持自己最初学习编程时的热爱,不忘初心,考到自己的目标学校和岗位!(毒液说行你就行)