当前位置:   article > 正文

Union-Find 算法(并查集)_实现 union-find data structure matlab

实现 union-find data structure matlab
作者:disappearedgod
时间:2014-5-7

前言

参考《算法》第四版的内容,并融入了Algorithm,Part I(Princeton)的讲义

0. 介绍

这里讨论的是一个网络连接的问题。看图画中的两个点是否能够连接上。

1. 动态连通性

问题的输入是一列整数对,其中每个整数都表示一个某种类型的对象,一对整数p q可以被理解为"p和q是相连的 "。我们假设“相连”是一种对等的关系,也就意味着:
  • 自反性:p和p是相连的
  • 对称性:如果p和q是相连的,那么q和p也是相连的
  • 传递性:如果p和q是相连的且q和r是相连的,那么p和r也是相连的。
上述对等关系能够将对象分为多个等价类(当且仅当两个对象相连时它们才属于同一个等价类)。

也就是,当程序从输入中读取了整数对(p q)时,如果一直的所有整数对都不能说明p和q是相连的,那么则将这一对整数写到输出中。如果已知的数据可以说明p和q是相连的那么程序应该忽略(p q)这对整数并继续处理输入中的下一对整数。
我们设计了一份API来封装所需的基本操作:初始化、连接两个触点、判断包含某个触点的分量、判断两个触点是否存在于同一个分量之中以及返回所有分量的数量。
public class UF
UF(int N)                                                initialize N sites with integer names (0 to N-1)
void union(int p, int q)                             add connection between p and q
int find(int p)                                          component identifier for p (0 to N-1)
boolean connected(int p, int q)                return true if p and q are in the same component
int count()                                              number of components

所有的实现都应该:
定义一种数据结构表示一直的连接
基于此数据结构实现高效的union()、find()、connected()和count()方法

我们的目的是找到一条路径:

首先,先看一下UF的测试用例

  1. public static void main(String[] args) {
  2. int N = StdIn.readInt();//对象的个数
  3. UF uf = new UF(N);
  4. while (!StdIn.isEmpty()) {//输入整数对
  5. int p = StdIn.readInt();
  6. int q = StdIn.readInt();
  7. if (uf.connected(p, q)) continue;
  8. uf.union(p, q);
  9. StdOut.println(p + " " + q);
  10. }
  11. StdOut.println(uf.count() + " components");
  12. }



测试用例的一个输入是:

  1. 10
  2. 4 3
  3. 3 8
  4. 6 5
  5. 9 4
  6. 2 1
  7. 8 9
  8. 5 0
  9. 7 2
  10. 6 1
  11. 1 0
  12. 6 7


2. 实现

对于上述这样的动态连接问题,我们用下面几种方法进行数显。

2.1 quick-find 算法

2.1.1 算法介绍

我们想构造这样一种数据类型


其实,我们用了一个一下这个方法(例如添加一条边1-6)


我们举几个例子吧:

先从0-链接开始


链接操作union(4,3) 就是把id为4的数值改为id为3的数值





connected()判断是否有链接





如下图来做这个操作


2.1.2 算法实现

根据上述介绍,我们可以写这样段代码

  1. public class QuickFindUF {
  2. private int[] id; // id[i] = component identifier of i
  3. private int count; // number of components
  4. /**
  5. * Initializes an empty union-find data structure with N isolated components 0 through N-1.
  6. * @throws java.lang.IllegalArgumentException if N < 0
  7. * @param N the number of objects
  8. */
  9. public QuickFindUF(int N) {
  10. count = N;
  11. id = new int[N];
  12. for (int i = 0; i < N; i++)
  13. id[i] = i;
  14. }
  15. /**
  16. * Are the two sites <tt>p</tt> and <tt>q/tt> in the same component?
  17. * @param p the integer representing one site
  18. * @param q the integer representing the other site
  19. * @return <tt>true</tt> if the two sites <tt>p</tt> and <tt>q</tt> are in
  20. * the same component, and <tt>false</tt> otherwise
  21. * @throws java.lang.IndexOutOfBoundsException unless both 0 <= p < N and 0 <= q < N
  22. */
  23. public boolean connected(int p, int q) {
  24. return id[p] == id[q];
  25. }
  26. /**
  27. * Merges the component containing site<tt>p</tt> with the component
  28. * containing site <tt>q</tt>.
  29. * @param p the integer representing one site
  30. * @param q the integer representing the other site
  31. * @throws java.lang.IndexOutOfBoundsException unless both 0 <= p < N and 0 <= q < N
  32. */
  33. public void union(int p, int q) {
  34. if (connected(p, q)) return;
  35. int pid = id[p];
  36. for (int i = 0; i < id.length; i++)
  37. if (id[i] == pid) id[i] = id[q];
  38. count--;
  39. }
  40. /**
  41. * Returns the number of components.
  42. * @return the number of components (between 1 and N)
  43. */
  44. public int count() {
  45. return count;
  46. }
  47. /**
  48. * Returns the component identifier for the component containing site <tt>p</tt>.
  49. * @param p the integer representing one site
  50. * @return the component identifier for the component containing site <tt>p</tt>
  51. * @throws java.lang.IndexOutOfBoundsException unless 0 <= p < N
  52. */
  53. public int find(int p) {
  54. return id[p];
  55. }
  56. }

2.1.3 算法分析

在quick-find 算法中,每次find()调用只需要访问数组一次,而归并两个分量的union()操作访问数组的次数在(N+3)到(2N+1)之间。(uion中调用两次find(),jiancha id[]数组中全部N个元素并改变他们中的1到N-1个元素)。

如果最终结果是只得到一个连通分量(所有数组值都相同),只扫调用(N-1)次union,即至少需要N+3TimesN-1次——N^2次操作。


2.2 quick-union 算法

2.2.1 介绍

这个算法主要是提高了union()方法的速度,它和quick-find方法互补。

他也是基于相同的数据结构(id数组),单赋予了不同的意义。这里采用了树(根)的概念。

我们由p和q的链接分别找到它们的根触点,然后只需要将一个根触点链接到另一个即可,讲一个分量重命名为另一个分量,因此叫做quick-union。

我现在看一下是如何做Union() 和Connect()的


Union(a,b)是将b的根节点作为a的根节点的根节点








connect(a,b)是看他们的根节点是否相同




下面看一下这个例子中的树是如何建立的


2.2.2实现


  1. public class QuickUnionUF {
  2. private int[] id; // id[i] = parent of i
  3. private int count; // number of components
  4. /**
  5. * Initializes an empty union-find data structure with N isolated components 0 through N-1.
  6. * @throws java.lang.IllegalArgumentException if N < 0
  7. * @param N the number of objects
  8. */
  9. public QuickUnionUF(int N) {
  10. id = new int[N];
  11. count = N;
  12. for (int i = 0; i < N; i++) {
  13. id[i] = i;
  14. }
  15. }
  16. /**
  17. * Returns the component identifier for the component containing site <tt>p</tt>.
  18. * @param p the integer representing one site
  19. * @return the component identifier for the component containing site <tt>p</tt>
  20. * @throws java.lang.IndexOutOfBoundsException unless 0 <= p < N
  21. */
  22. public int find(int p) {
  23. while (p != id[p])
  24. p = id[p];
  25. return p;
  26. }
  27. /**
  28. * Are the two sites <tt>p</tt> and <tt>q</tt> in the same component?
  29. * @param p the integer representing one site
  30. * @param q the integer representing the other site
  31. * @return <tt>true</tt> if the sites <tt>p</tt> and <tt>q</tt> are in the same
  32. * component, and <tt>false</tt> otherwise
  33. * @throws java.lang.IndexOutOfBoundsException unless both 0 <= p < N and 0 <= q < N
  34. */
  35. public boolean connected(int p, int q) {
  36. return find(p) == find(q);
  37. }
  38. /**
  39. * Merges the component containing site<tt>p</tt> with the component
  40. * containing site <tt>q</tt>.
  41. * @param p the integer representing one site
  42. * @param q the integer representing the other site
  43. * @throws java.lang.IndexOutOfBoundsException unless both 0 <= p < N and 0 <= q < N
  44. */
  45. public void union(int p, int q) {
  46. int rootP = find(p);
  47. int rootQ = find(q);
  48. if (rootP == rootQ) return;
  49. id[rootP] = rootQ;
  50. count--;
  51. }
  52. /**
  53. * Returns the number of components.
  54. * @return the number of components (between 1 and N)
  55. */
  56. public int count() {
  57. return count;
  58. }
  59. }

2.2.3 评价

这个方法的最好的时间复杂度是线性的,最坏时间复杂度也还是平方的。


好处是:quick-union 算法看做是quick-find算法的一种改良,因为它解决了quick-find算法中最主要的问题(Union()操作总是线性级别的)。

我们发现最坏时间复杂度高是因为树的深度太深。

2.3 加权quick-union算法

2.3.1 介绍

简单的修改quick-union算法就能保证最坏情况不能发生。我们将记录每一棵树的大小并总是将较小的树链接到较大的树上。这项改动需要添加一个数组和一些代码在记录树中的节点数。


2.3.2 实现

  1. public class WeightedQuickUnionUF {
  2. private int[] id; // id[i] = parent of i
  3. private int[] sz; // sz[i] = number of objects in subtree rooted at i
  4. private int count; // number of components
  5. /**
  6. * Initializes an empty union-find data structure with N isolated components 0 through N-1.
  7. * @throws java.lang.IllegalArgumentException if N < 0
  8. * @param N the number of objects
  9. */
  10. public WeightedQuickUnionUF(int N) {
  11. count = N;
  12. id = new int[N];
  13. sz = new int[N];
  14. for (int i = 0; i < N; i++) {
  15. id[i] = i;
  16. sz[i] = 1;
  17. }
  18. }
  19. /**
  20. * Returns the number of components.
  21. * @return the number of components (between 1 and N)
  22. */
  23. public int count() {
  24. return count;
  25. }
  26. /**
  27. * Returns the component identifier for the component containing site <tt>p</tt>.
  28. * @param p the integer representing one site
  29. * @return the component identifier for the component containing site <tt>p</tt>
  30. * @throws java.lang.IndexOutOfBoundsException unless 0 <= p < N
  31. */
  32. public int find(int p) {
  33. while (p != id[p])
  34. p = id[p];
  35. return p;
  36. }
  37. /**
  38. * Are the two sites <tt>p</tt> and <tt>q</tt> in the same component?
  39. * @param p the integer representing one site
  40. * @param q the integer representing the other site
  41. * @return <tt>true</tt> if the two sites <tt>p</tt> and <tt>q</tt>
  42. * are in the same component, and <tt>false</tt> otherwise
  43. * @throws java.lang.IndexOutOfBoundsException unless both 0 <= p < N and 0 <= q < N
  44. */
  45. public boolean connected(int p, int q) {
  46. return find(p) == find(q);
  47. }
  48. /**
  49. * Merges the component containing site<tt>p</tt> with the component
  50. * containing site <tt>q</tt>.
  51. * @param p the integer representing one site
  52. * @param q the integer representing the other site
  53. * @throws java.lang.IndexOutOfBoundsException unless both 0 <= p < N and 0 <= q < N
  54. */
  55. public void union(int p, int q) {
  56. int rootP = find(p);
  57. int rootQ = find(q);
  58. if (rootP == rootQ) return;
  59. // make smaller root point to larger one
  60. if (sz[rootP] < sz[rootQ]) { id[rootP] = rootQ; sz[rootQ] += sz[rootP]; }
  61. else { id[rootQ] = rootP; sz[rootP] += sz[rootQ]; }
  62. count--;
  63. }
  64. }

2.3.3 评价

下图为例子输入构造和上述最坏情况


对于100个点做88次union方法来说


2.4 总结

各种UF的性能比较


所有操作的总成本(横轴为链接总数,纵轴为访问数组的次数)



声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/IT小白/article/detail/243568
推荐阅读
相关标签
  

闽ICP备14008679号