并查集
并查集对我们来说并不陌生,已经学过了,进行一个复习
并查集是一种可以动态维护若干个不重叠的集合,并且支持合并和查询操作的数据结构,他有两种操作:
1.查询,查询这个元素属于那个集合
2.合并,把两个集合合并成一个集合
用于实现这种数据结构,我们通常使用代表元,也就是找一个固定的元素来代表一个集合
接下来我们就得来研究归属关系的表示方法,这里可以使用f[x]数组(全称father)来表示x的代表元素,其实并查集就是一个森林之间的操作,f数组也可以表示一个点的父亲,代表元素可以理解成根元素,合并就是将两个森林合并成一棵树。那么在查询的时候不断地递归f[x],直至到达树根
但是我们可以明白,这种递归方法比较的浪费时间,可以采用路径压缩,路径压缩就是说,把每次访问过的每个节点直接指向树根(也即是代表元素),也就是下一次查找直接就获取了代表元素了,不用再重新查找了,来节省时间
1.一开始存储的时候,将f数组赋值成自己,也就是自己的单独的集合
2.不断地往上寻找,如果找到代表元素就返回,否则路径压缩将代表元素存到父亲点
3.合并的话,直接将两个元素的代表元素合并,节省空间,让x的根节点做y的子节点
路径压缩的时间复杂度是O(nlogn)级别的
int get(int x)
{
if(x==f[x]) return x;
return f[x]=get(f[x]);//路径压缩
}
void un(int x,int y)
{
f[get(x)]=get(y);//代表元素进行合并
}
启发式合并
路径压缩好简单,但是启发式合并就…
我们对每一个集合维护大小两个值,每次把小的集合的代表元素父亲设为大的集合的代表元素
这样每次跳一步,子树的大小会翻倍,这样每个点任意时刻到根的路径都是O(lgn)
按秩合并
路径压缩好简单,但是按秩合并就…
我们对每一个点的深度维护大小两个值,每次把深度小的挂在深度大的上边
显然,新的集合的深度变大1当且 仅当原先两个集合深度相等,因此可以归纳证明每次最大深度+1,集合大小也会翻倍,复杂度也和启发式合并一样
关于这两个算法的思想很相似,但是让人感到难理解,就是这么做的原理是什么?
因为把小的集合或者深度合并到大的集合或者深度中,这样节点会被拓大,也就是1次合并会得到2次的结果,那肯定就会快了,这个思路很像是受了启发一样,拍案称奇,写到这里,我不禁笑出来了,啊哈哈
int dfn[SIZE];//深度or集合大小
void merge(int u,int v)
{
int t1=find(u);
int t2=find(v);
if(t1!=t2)//需要合并啦
{
if(dfn[t1]>dfn[t2])
{
fa[t2]=t1;
}
else if(dfn[t2]<dfn[t1])
{
fa[t1]=t2;
}
else
{
fa[t2]=t1;//深度一样, 怎么合并都可以
dfn[t1]++;
}
}
}