周六愉快
在上一篇[文章](CS61B: 并查集(不相交集)及其简单实现(二)——加权合并算法 - 掘金 (juejin.cn))中,我们实现了简单的加权合并算法。引进了一个find函数,代码片段如下:
public int find(int criminal) {
int root = this.criminalNumbers[criminal];
if ( root < 0) {
return criminal;
}
return find(root);
}
我们又遇见了新的问题,那就是路径有可能过长,比如上篇文章末提到的为7寻找root:
7 -> 0 -> 4 -> 5 -> 2
这时候的数组组成为:
index[7] = 0; index[0] = 4; index[4] = 5; index[5] = 2; index[2] = -8 (Got it!)
在这个过程中,反正7,0,4,5,2都是2的成员,而2是顶层,而我们只要对比顶层,所以为什么不在寻找的过程中,一步一步的把7,0,4,5的上级替换掉呢?
find(7)的过程将会像这样:
[在这里我将简写criminalNumbers数组为C]
- C[7] = 0,而C[0] = 4为正数,说明0还不是root,将C[0]的值赋予C[7],即C[7] = C[0],此时C[7]=4;
We have problem here.
根据我贴的代码,按照未压缩的时候,此时递归参数,应该是0,即return find(0);
现在我们要压缩路径了,此时C[7]有新的值了,即4,我们这时候是要把新值4作为递归参数么?
Negative!
路径压缩,是要针对一整条链路上的非根元素进行优化,如果跳过了4,那0怎么办?
如果0极少被调用,那从0溯根,花销还是不变。
我们要做到公平公正:只要是查找链路上的某个元素X,那么针对X的上层节点,都要压缩,一个都不能落下!
- C[0] = 4, C[4] = 5, C[0] = C[4], 即C[0] = 5;
- C[4] = 5, c[5] = 2, C[4] = C[5], 即C[4] = 2;
- C[5] = 2, C[2] = -8, 哒哒~!
经过这一轮的路径优化,我们得到新的数组组成:
index[7] = 4, index[0] = 5, index[4] = 2, index[5] = 2, index[2] = -8;
发现没,已经有一个成员,直面上级2了,那就是4号!
未压缩的路径:7 -> 0 -> 4 -> 5 -> 2
压缩后的路径:7 -> 4 -> 2
根据我上述推算,再经过一轮find(7),你将会得到:
index[7] = 2
最终,经过反复调用,你将会得到这样的数组组成:
index[7] = 2 , index[0] = 2, index[4] =2 ,index[5] =2, index[2] = -8
以上,就是所谓的路径压缩!经过有限次数的调用后,时间复杂度,最终会由O(logN)降为O(1)。不过考虑到,复杂度是用来形容最坏情况的,所以我们不能说加权合并的复杂度是O(1),他仍然是O(logN)。
让我们来实现路径压缩
好吧,真的超级简单:
public int find(int criminal) {
int root = this.criminalNumbers[criminal];
if ( root < 0) {
return criminal;
} else {
if (this.criminalNumbers[root] > 0 ) {
this.criminalNumbers[criminal] = this.criminalNumbers[root];
}
}
return find(root);
}
最后对比一下结果:
这是优化前的数组,我直接用上一篇文章的图:
这是优化后的数组:
最终的结果,依旧不变:
好了,并查集就完结了!