概述

开篇直接讲 Union-Find,然后以它为例,讲解如何使用科学方法研究算法性能。

编程作业是使用加权快速合并(Weighted Quick Union)解决渗滤(Percolation)问题。

建议阅读《算法 4th》的 1.4、1.5 两节。

开发高效算法的流程

  1. 建立问题模型

  2. 找出解决该问题大体上所需要的基本操作 ,据此找出算法解决该问题

  3. 算法是否足够快?是否满足对存储空间的要求 ?

  4. 如果不满足,我们就需要搞清楚 为什么会这样

  5. 想办法找出造成这些问题的源头 ,然后提出新的算法

  6. 如此循环直到满意为止

Quick-find

Union-Find - 图1

  1. class QuickFindUF
  2. {
  3. private int[] _id;
  4. public QuickFindUF(int n)
  5. {
  6. _id = new int[n];
  7. for (var i = 0; i < n; i++)
  8. {
  9. _id[i] = i;
  10. }
  11. }
  12. public bool Connected(int p, int q)
  13. {
  14. return _id[p] == _id[q];
  15. }
  16. public void Union(int p, int q)
  17. {
  18. var pId = _id[p];
  19. var qId = _id[q];
  20. for (var i = 0; i < _id.Length; i++)
  21. {
  22. if (_id[i] == pId)
  23. {
  24. _id[i] = qId;
  25. }
  26. }
  27. }
  28. }

Union-Find - 图2

合并操作代价太大,特别是如果你需要在 N 个对象上 进行 N 次合并操作时,单是判断它们是否连通 就需要正比于 N^2 的时间。

Union-Find - 图3

对于大型问题 ,我们不能接受需要平方时间的算法,因为它们无法成比例适应大规模问题。

Quick-union

Union-Find - 图4

  1. public class QuickUnionUF
  2. {
  3. private int[] _id;
  4. public QuickUnionUF(int n)
  5. {
  6. _id = new int[n];
  7. for (var i = 0; i < n; i++)
  8. {
  9. _id[i] = i;
  10. }
  11. }
  12. private int Root(int i)
  13. {
  14. while (i!=_id[i])
  15. {
  16. i = _id[i];
  17. }
  18. return i;
  19. }
  20. public bool Connected(int p, int q)
  21. {
  22. return Root(p) == Root(q);
  23. }
  24. public void Union(int p, int q)
  25. {
  26. var i = Root(p);
  27. var j = Root(q);
  28. _id[i] = j;
  29. }
  30. }

Union-Find - 图5

快速合并的缺点在于树可能太高了,导致查找操作代价太大。你可能需要回溯一棵瘦长的树,每个对象只是指向下一个节点,那么对叶子节点执行一次查找操作,需要回溯整棵树。只进行查找操作就需要花费 N 次数组访问。

改进 Quick-union

带权

Union-Find - 图6

Union-Find - 图7

带权的树中任意节点的深度是 lg2(N)。
Union-Find - 图8

路径压缩

将树展平,当我们找到P的根节点之后,可以回过头来将路径上每个节点都指向根节点。

Union-Find - 图9

实际实现时只需将路径上的每个节点指向它的祖父节点,这种实现效果不如完全展平,但实际应用中两者差不多一样好。

Union-find applications

Union-Find - 图10

Percolation

渗滤(Percolation)是一个被数学模型精确描述的问题,没有人知道这个数学问题的解,我们有的唯一的解来自计算模型。

Union-Find - 图11

渗滤问题的重点在于找到 N 极大时,引起渗滤系统“相变”的 p 值。
Union-Find - 图12

直接使用 Union-find 的问题在于,检查 top 行和 bottom 行的连通性时,它需要调用查找 N^2 次来依次检查 top 行每位,与 bottom 行每位的连通性。这是一个需要平方时间的暴力算法,太慢了。

解决方法时给 top 行和 bottom 行的分别引入一个虚拟位。
Union-Find - 图13

总结

  1. 我们选取了一个重要的问题——动态连通性问题

  2. 我们通过对问题建模试图精确理解,我们需要什么样的数据结构和算法来解决它

  3. 然后我们先提出了几个可以解决问题的简单算法

  4. 并很快发现它们不足以处理巨大问题

  5. 但之后我们发现了如何改进它们,得到了高效的算法

  6. 最后我们遇到了必须使用高效算法才能解决的应用问题

Union-Find - 图14