概念
实现思路
成本模型
quick-find算法
分析
quick-union算法
分析
缺陷
加权quick-union
实现
概念
简单点理解就是连接两点,如果已经是连通的则忽略,比如下图:
应用
网络连通性:
判断大型计算机网络中,两台计算机是否需要新连接才能通信
变量名等价性:
能判断两变量名是否等价
数学集合:
两个数是否在一个集合中
实现思路
要实现这样的算法需要下面方法:
初始化N个点,给它们标号
得到一个点所属的标号
连接两个点
判断两个点是否连通
连通分量的数量
于是得到下面的一份API:
成本模型
各操作所需访问数组的次数
quick-find算法
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;
// 将p分量重命名为q的名字
for (int i = 0; iif (id[i] == pID) id[i] = qID;
count--;
}
如图,find(5,9)检查到 id[5] 和 id[9] 的标号不一样,那union操作就将5的标号1全部改成8,这样5和9的标号都一样,就全部连通了。
分析
每次find()调用只需要访问数组一次
每次connect() 操作调用2次find,即访问数组两次
每次union()算法访问数组次数:
2(两次find获得标号)+N(检查N个数是否有相同的标号)+1(改变1次) = N + 3
2(两次find获得标号)+N(检查N个数是否有相同的标号)+N-1(改变N-1次) = 2*N + 1
显然,要让所有连通,则需要N-1次union,即至少(N-1)*(n+3)次访问数组,所以quick-finds是平方级别。
quick-union算法
private int find(int p)
{ // 找出分量的名称
while (p != id[p]) p = id[p] ;
return p;
}
public void union(int p, int q)
{ // 将p和q的根节点统一
int pRoot = find(p);
int qRoot = find(q);
if (pRoot == qRoot) return;
id[pRoot] = qRoot;
count--;
}
5-0 0-1 1-1 这样就找到了5的根节点1
9-8 8-8 这样就找到了9的根节点8
连接5和9只要连接它们的根节点1和8就行
分析
quick-union算法看作是quick-find的一次改良,因为它解决了quick-find的一个最重要的问题(union()操作总是线性级别的)。
缺陷
比如说下面的这种情况
可以看到这种情况下树的高度会非常高,这显然不是我们想要的结果
那怎么解决这种问题呢?
加权quick-union
总是选择将小树连接到大树就可以解决上面的问题
还是那个问题,上图右边是加权的结果,可以直观的看到,加权后树的高度变为2,大大优化树的结构。
实现
要实现小树连到大树上,首先要判断小树大树,于是引进权的概念。先把所有节点的权重设为1,相连后权重相加,然后通过比较权重来判断大树小树即可。
public WeightedQuickUnionUF(int n) {
count = n;
parent = new int[n];
size = new int[n];
for (int i = 0; i < n; i++) {
parent[i] = i;
size[i] = 1;
}
}
public void union(int p, int q) {
int rootP = find(p);
int rootQ = find(q);
if (rootP == rootQ) return;
// make smaller root point to larger one
if (size[rootP] < size[rootQ]) {
parent[rootP] = rootQ;
size[rootQ] += size[rootP];
}
else {
parent[rootQ] = rootP;
size[rootP] += size[rootQ];
}
count--;
}