Dynamic Connectivity:
本节讲的问题就是对一些元素进行并(union)的操作,并判断他们是否在一个集合(find)。介绍了几种算法并分析了他们的时空复杂度优劣。
本文用id[ ]数组表示每个元素的父节点
1.Quick -Find:
这是最朴素的算法,每个元素直接指向他的根节点,当连接p,q时,如果p,q的根节点不一样,就遍历整个id[ ]数组,将id[ ]==id[p]的元素的根节点全部改成id[q]。
这样并的复杂度是O(N)。 但是由于直接指向父节点,查的复杂度是O(1),所以算法叫Quick find。
2.Quick -Union:
这就是普通的并查集算法了。初始每个元素的id[ ]等于自己,将p和q合并时,找到p和q的根节点,令p的根节点指向q的根节点。
找根节点的过程像顺藤摸瓜一样,一直向上找路径上每个元素的父节点,直到一个元素的父节点等于自身,这就是根节点了。
查根节点代码实现:
int root(int i){ while(i != id[i]) i = id[i]; return i; }并:
<pre name="code" class="html">void union(int p,int q){ int i=root(p); int j=root(q); if(i!=j){ id[i]=j; } }
这个算法的最坏情况实际上find和union都是O(N)的,所以依然不算是个好算法。
3.Quick-Union improved:
因为每次查根节点要遍历一个树,所以一个优化的思路就是去降低整个并查集的最大深度。一个很方便的做法是每次把包含点数量少的树连到点数量多的树的根节点下。
注意这里维护的不是树的高度,而是树中包含点的数量。
void union(int p,int q){ int i=root(p); int j=root(q); if(i!=j){ if(size[i]>size[j]){ id[j]=i; size[i]+=size[j]; } else { id[i]=j; size[j]+=size[i]; } } }
可以证明这样维护后树的高度最大是log N的:
首先一次Union操作显然最多只会使并查集的最大深度+1,即两个相同大小的树合并。那么这种Union两个相同大小的树的操作最多发生log N次,所以最大深度为log N。
所以这个算法的查和并的复杂度都是log N。
再加上路径压缩后这个算法的时间复杂度在实际中可以视为线性的。
公开课教授说并查操作已经被证明没有线性的算法了。。
对于最差复杂度:
Quick-Find:N*M
Quick-Union:N * M
weighted QU : N+M*logN
对这一句话印象深刻:
Super Computer won't help much , good algorithm enables solution!