好好地做笔记, 将算法和数据结构这部分啃下来.
Dynamic connectivity
给定 N 个点, 通过连接命令将其中的几对点连接起来, 然后查询给出的两个点之间是否有连通的路径.
union(a, b) : 将 a 和 b 连接起来
connected(a, b) : a 和 b 之间是否有连通的路径
Modeling the connections
假设“连通” 是具有一下的性质
p 和自身 p 连接;
如果 p 和 q 连通, 那么 q 和 p 也是连通的;
如果 p 和 q 连通, 而且 q 和 r 连通, 那么 p 和 r 也是连通的;
Connected components : 相互连通的最大子集
Quick-find :
点的标号为 0 ~ N-1, 所以可以用数组来表示,
查找 : 如果数组的值相等, 那么下标对应的点就是连通的.
连接 : 需要将要连接的点的值和点所在的子集中的点的值都修改为一样的.
public class QuickFindUF {
private int[] id;
public QuickFindUF(int N) {
id = new int[N];
for(int i = 0; i < N; i++)
id[i] = i;
}
public boolean connected(int p, int q) {
return id[p] == id[q];
}
public void union(int p, int q) {
int pid = id[p];
int qid = id[q];
for(int i = 0; i< id.length; i++) {
if(id[i] == pid) id[i] = qid;
}
}
}
上面的代码的复杂度较高, 所以需要进行改进. 用树来表示连通子集, 数组的下标表示点, 同时数组的值表示其父节点.
所以对查找和连接操作进行改进 :
Find : 查看 p 和 q 是否有相同的根结点
Union : 将包含 p 和包含 q 的连通子集进行连接操作, 只需要将 p 的根结点的 id 设置为 q 的根节点 id.
public class QuickUnionUF {
private int[] id;
//initialization
public QuickUnionUF(int N) {
id = new int[N];
for(int i = 0; i < N; i++)
id[i] = i;
}
// chase parent pointers until reach root
private int root(int i) {
while(i != id[i])
i = id[i];
return i;
}
public boolean connected(int p, int q) {
return root(p) == root(q);
}
public void union(int p, int q) {
int i = root(p);
int j = root(q);
id[i] = j;
}
}
这个改进算法的缺点在于 : quick-find 的时间不稳定, 但是可能是为 N, 太长了. quick-union 的缺点在于树的深度可能太大, 导致寻根操作的时间过长.