给定一组N个对象,支持两个操做:java
数据结构:int id[].
解释:在同一连通份量的id[i],赋相同的值
下面的数组表示:0,5和6链接 ;1,2和7链接 ;3,4,8和9链接。web
\ | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
---|---|---|---|---|---|---|---|---|---|---|
id[] | 0 | 1 | 1 | 8 | 8 | 0 | 0 | 1 | 8 | 8 |
算法思路:算法
函数(方法) | 解释 |
---|---|
void union(int p, int q) | 在p与q之间添加一条链接 |
int find(int p) | p所在的连通份量的标识符 |
boolean connected(int p, int q) | 若是p和q存在同一个份量中则返回true |
int count() | 连通份量的数量 |
public int count() {return count;} public boolean connected(int p, int q) {return find(p) == find(q);} public int find(int p) {return id[p];} public void union(int p, int q) { //将p和q归并到相同的份量中 int pID = find(p);//访问数组一次 int qID = find(q);//访问数组一次 //若是p和q处在同个连通份量里面,直接返回 if(pID == qID)return; //遍历数组,合并连通份量 for(int i = 0; i < id.length; i++) { if(id[i] == pID)//访问n次数组 id[i] = qID;//最好的状况下执行1次,最坏的状况下(n-1)次 } count--;//连通份量的数量减一 }
在quick-find算法中,每次find()调用只须要访问数组一次,而归并两个份量的union()操做访问数组的次数在(N+3)~(2N+1)次
证实:每次connected()调用都会检查id[]数组中的两个元素是否相等,即调用两次find()方法。归并两个份量的union()操做会调用两次find(),检查id[]数组中的所有N个元素并改变它们中1~N-1个的元素的值数组
目的:提升union()方法的速度
数据结构:int id[].
解释:id[i]存放i的双亲结点.即把全部的对象看做森林,每个连通份量为一棵树。初始化时,共有n个结点,即有n棵树。每一个结点为本身所在的树的根节点。
数据结构
\ | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
---|---|---|---|---|---|---|---|---|---|---|
id[] | 0 | 1 | 9 | 4 | 9 | 6 | 6 | 7 | 8 | 9 |
id[] 的值规定:svg
算法思路:改进union(p, q)方法
由p和q的连接分别找到它们的根触点,而后只需将一个根触点连接到另外一个便可将一个份量重命名为另外一个份量函数
\ | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
---|---|---|---|---|---|---|---|---|---|---|
id[] | 0 | 1 | 9 | 4 | 9 | 6 | 6 | 7 | 8 | 6 |
public class QuickUnionUF { private int[] id; public QuickUnionUF(int N) { id = new int[N]; for (int i = 0; i < N; i++) id[i] = i;//现将双亲结点(根结点)设置为本身 } //查找份量名称 public int find(int i) { while (i != id[i]) //经过回溯寻找根节。若是是父节点,return;不然,将i在树中上移一层,直到找到根节点。 i = id[i]; return i; } //合并操做 public void union(int p, int q) { int i = find(p); int j = find(q); id[i] = j;//将第一个根节点id记录值设为第二个根节点id记录值 } }
缺点flex
目的:避免回溯一棵瘦长的树,咱们但愿在合并一大一小的树的时候,避免将大树放在小树下。
优化
所以咱们使用一个数组存储每棵树的的结点数。在合并两棵树时,比较两棵树的结点数,用把结点多的树链接到结点少的树上。这样就确保了小树的根节点做为大树的根节点的子节点(大树的孩子)。ui
思路:在原先基础上,新添加的一组数组sz[]存放树的结点数。数组的下标对应根结点,下标对应的值为树的结点数。
if (sz[i] < sz[j]) { id[i] = j; sz[j] += sz[i]; } else { id[j] = i; sz[i] += sz[j]; }
时间复杂度
时间与节点在树中的深度成正比。树中任意节点的深度是lg n
假设t1中有i个元素,t2中有j个元素。x属于t1,而x所在集合的位置最大为n。
当t1与t2合并时(i ≤ j),t1加到t2根结点下。此时x的深度+1,而合并后的树容量至少扩大2倍(由于p ≤ q,因此t1和t2合并后的集合大小至少是t1的两倍)。
设一开始x所在位置是1,要到达位置为n,就须要树的大小就要翻lg n次倍(每次翻倍,深度都会增长1,因此 2的lgn次方 = x的位置,即 2lgn = n)
路径压缩的weighted quick-union是最优化算法。
在检查结点的同时将它们直接链接到根结点上。
找出p的根结点,将id[p]设置为指向该根结点。
x为下一将与根结点链接的结点。
思路:要实现路径压缩,能够为find()添加一个循环,将在路径上遇到的全部结点都直接链接到根结点上。这样就能获得一棵几乎扁平化的树了。
public int find(int i) { while (i != id[i]) { id[i] = id[id[i]]; i = id[i]; } return i; }