观前提示:看这篇文章之前,需了解并查集基本使用
并查集基础:并查集基础部分
了解之后,我们就进入启发式合并的讲解
启发式合并,顾名思义是去优化合并
在并查集基础中,我们合并是这样的:
void join(int x,int y){//join x和y
if (find(x)==find(y)){
return ;//在一个集合里,直接结束函数
}else{
f[b]=a;
}
return ;
}
那我们来设想一下:
假设此时A集合有10个人,B集合中有5个人:
A集合:
B集合:
A和a1是两个集合的祖先
那么假如说,f[a]=a1;
就变成了这样(并不太准确):
此时A的父节点变为了a1,b,c,d,e,f,g,h,i这些结点的父节点还都是A
所以当b,c,d,e,f,g,h,i寻找祖先的时候需要搜索A和a1两个点
寻找祖先节点的时间:
节点编号 | 次数 |
b | 2 |
c | 2 |
d | 2 |
e | 2 |
f | 2 |
g | 2 |
h | 2 |
i | 2 |
b1 | 1 |
b2 | 1 |
c1 | 1 |
d1 | 1 |
一共要寻找20次(对于计算机来说小菜一碟,可还是要优化)
假设f[a1]=a呢?
那么就变成了这样:
并不美观
但是他的搜索次数比第一次会少,时间复杂度也跟着降低
节点编号 | 搜索次数 |
b | 1 |
c | 1 |
d | 1 |
e | 1 |
f | 1 |
g | 1 |
h | 1 |
i | 1 |
b1 | 2 |
b2 | 2 |
c1 | 2 |
d1 | 2 |
一共寻找16次,比上次少了
这只是一个较为简单的集合
当复杂起来 节省的时间就很多了
这就是启发式合并的思想:
如果将a和b集合合并,如果a集合节点数比b集合节点数多,那么就执行f[b]=a;
反之,就是f[a]=b;
剩下的就简单了
定义一个h数组,h[i]表示i的儿孙节点有多少,(仅限i是祖先节点,如果不是,h[i]为0)
那么初始化就要加上h的初始化:
void make(){
for (int i=1;i<=n;i++){
f[i]=i;
h[i]=1;//初始化时,每个节点自己成为一个集合,所以h[i]为1
}
}
join函数:
void join(){
if (find(f[x])==find(f[y])){
return ;
}
if (h[a]>h[b]){
f[b]=a;
h[a]+=h[b];
h[b]=0;
}else{
f[a]=b;
h[b]+=h[a];
h[a]=0;
}
}
总结:
启发式合并思想很简单,能够降低时间复杂度(真香) ,所以大家在使用并查集的时候,建议把启发式合并带上
图表制作不易
点个赞再走吧
OrzOrz