[原创] union-find算法中的quick-find算法的复杂度


union-find算法用于检测动态连通性,例如计算机网络中的两个节点是否连通,在一个特定圈子里的两个人是否有间接的朋友关系,等等。

quick-find算法是union-find算法的众多实现中最简单也最没有效率的一种,它的主要实现如下:

public int find(int p) {
  return id[p];
}

public void union(int p, int q) {
  // 查找点 p 和点 q 在id数组中的值,从而可以接着判断它们是否在相同的连通分量中
  int pID = find(p);
  int qID = find(q);

  // p, q已经在相同的分量中,无需任何操作
  if (pID == qID) {
    return;
  }

  // 执行到这里,说明 p 和 q 不在相同的分量中,因此需要把它们进行归并,在这里,是把 p 所在的分量里的所有元素全部重命名为 q 所在的分量里的名称(唯一)
  for (int i = 0; i < id.length; i++) {
    if (id[i] == pID) {
      id[i] = qID;
    }
  }
  count--;
}

find() 函数用于查找一个点 p 的名称;union() 函数用于归并两个点 p 和 q,如果它们已经在同一个连通分量里,那么不会产生任何归并效果。

文章来源:http://www.codelast.com/

之所以把这种算法叫quick-find,是因为它的 find() 操作很快,只需要访问id数组一次;但是quick-find的 union() 操作却很慢,因为它需要访问整个id数组。

事实上,union() 访问数组的次数,至少是 N+3,至多是 2N+1,其中N是点的个数。

这是怎么算出来的呢?下面来看一下。

union() 函数一开始的两个 find() 操作无论如何是逃不掉的,所以这里至少就访问了 2 次id数组。

if (pID == qID) 这一句,没有对数组进行任何访问,因此,最后的for循环应该是至少访问了 N+1 次数组,但这又是怎么算出来的呢?

i 从 0 循环到 id.length(即N),共执行了N次,所以for循环里的if语句一定会执行N次,所以数组访问至少会有N次,那么还差1次,是怎么来的呢?

由于一定有一个 i 使得 id[i] == pID 成立(也就是p所在的那个分量),所以 id[i] == qID 至少会被执行一次,所以for循环访问数组的次数就至少是 N+1 次了。在这种情况下,所有id数组中,只有一个元素是与p处于同一连通分量的(其实也就是p这一个,或者说,p很“孤独”),它会被合并到q所在的连通分量中。

文章来源:http://www.codelast.com/

那么union() 访问数组的次数,最多是 2N+1,这又是怎么算出来的呢?

当id数组中,除了q之外,其他所有元素都与p处于同一连通分量中的话,那么,if (id[i] == pID) 这个条件就会成立 N-1 次,这意味着 id[i] = qID 会被执行 N-1 次,而N次 if 判断无论如何都是会被执行的,所以for循环里访问数组的次数是 N+N-1=2N-1 次,另外前面说了,union()函数中两个find()操作是免不了的,所以还要再加2次,总共是 2N-1+2=2N+1 次。
 

因此,union()访问数组的次数在 N+3 到 2N+1 之间。

 

如果所有的 N 个分量其实都在一个连通分量中,那么在解这个union-find问题的时候,就要调用 N-1 次union()函数,则需要访问数组的次数至少是 (N+3)(N-1) 次,即等价于 N^2 次的复杂度。

于是得证。

文章来源:https://www.codelast.com/
➤➤ 版权声明 ➤➤ 
转载需注明出处:codelast.com 
感谢关注我的微信公众号(微信扫一扫):

wechat qrcode of codelast

发表评论