1. Dynamic connectivity
给定N个objects
- Union command:连接两个objects
- Find/connected query:是否存在一条路径连接两个objects
- Connected components:objects互相之间都存在连接的最大集合
1.1 Union-find data type (API)
UF(int N) //initialize union-find data structure with N objects (0 to N – 1)
void union(int p, int q) //add connection between p and q
boolean connected(int p, int q) //are p and q in the same component?
int find(int p) //component identifier for p (0 to N – 1)
int count() //number of components
1.2 Dynamic-connectivity client
- 通过标准输入读取integer对
- 如果他们还没有被连接,则将他们连接并打印这个integer对
public static void main(String[] args)
{
int N = StdIn.readInt();
UF uf = new UF(N);
while (!StdIn.isEmpty()){
int p = StdIn.readInt();
int q = StdIn.readInt();
if (!uf.connected(p, q)){
uf.union(p, q);
StdOut.println(p + " " + q);
}
}
}
2 Quick find算法
2.1 数据结构
长度为N的integer array,用每个index对应每个object,如果两个index存储的数字相同,则代表他们相连,即在同一个connected components中。
2.2 Java实现
public class QuickFindUF{
private int[] id;
//Constructor, set id of each object to itself (N array accesses)
public QuickFindUF(int N){
id = new int[N];
for (int i = 0; i < N; i++)
id[i] = i;
}
//check whether p and q are in the same component (2 array accesses)
public boolean connected(int p, int q){
return id[p] == id[q];
}
//change all entries with id[p] to id[q] (at most 2N + 2 array accesses)
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;
}
}
}
2.3 复杂度
每次union命令需要,先访问index p和q保存的数pid,qid,然后对于array里面的每个数逐次判断是否等于pid,如果等于,则需要将这个数改为qid。在最坏的情况下,array里只有q一个index存储的为qid,其他全部都等与pid,则需要进行2+N+N-1=2N+1次存取。因此,对N个objects进行N次union操作是N^2的复杂度。
3. Quick union算法
3.1 数据结构
长度为N的integer array,每个index代表一个object,每个index位置上的数字不再像quick find一样代表connected components,而是代表这个object的parent,一个object i的root即为id[id[id[…id[i]…]]],直到不再变化,因为如果一个object没有parent的时候,这个object r对应的index保存的数字就是初始化时候的数字r。两个object的root相同时,则代表他们是相连的。
3.2 Java实现
public class QuickUnionUF{
private int[] id;
// Constructor, set id of each object to itself (N array accesses)
public QuickUnionUF(int N){
id = new int[N];
for (int i = 0; i < N; i++) id[i] = i;
}
// chase parent pointers until reach root (depth of i array accesses)
private int root(int i){
while (i != id[i]){
i = id[i];
}
return i;
}
// check if p and q have same root (depth of p and q array accesses)
public boolean connected(int p, int q){
return root(p) == root(q);
}
// change root of p to point to root of q (depth of p and q array accesses)
public void union(int p, int q){
int i = root(p);
int j = root(q);
id[i] = j;
}
}
3.3 复杂度
每次判断object p和q是否连接,需要首先得到p和q的root,root操作的复杂度取决于object的depth,在最坏情况下,root操作需要N-1次array读取。
4 对Quick-union的改进:Weighted quick-union
quick-union的问题是每次union操作是把第一个object的root指向第二个object的root,如果第一个object所在的depth已经很深了的话,这样会再让depth加一。如果depth太深,后面进行root操作的时候,就要从树的末端逐次向root进行读取,操作次数比较多,如果能够限制树的深度就可以减少root需要的操作次数。
Weight quick-union就是通过每次union操作都让深度较小的树连接到深度较深的树的root,这样就不会增加树的深度。
4.1 数据结构
和quick-union相比,增加了一个数组sz[i]用来保存以object i为root的树所含有的object的数量。在进行union操作的时候,首先判断两个object所在的树的大小,并进行比较,较小的树将被连接到较大树的root上,并更新较大树的大小(原来的大小加上较小树的大小)。
4.2 Java实现
public class WeightedQuickUnionUF{
private int[] id;
private int[] sz;
// Constructor, set id of each object to itself (N array accesses)
public WeightedQuickUnionUF(int N){
id = new int[N];
sz = new int[N]
for (int i = 0; i < N; i++){
id[i] = i;
sz[i] = 1;
}
}
// chase parent pointers until reach root (depth of i array accesses)
private int root(int i){
while (i != id[i]){
i = id[i];
}
return i;
}
// check if p and q have same root (depth of p and q array accesses)
public boolean connected(int p, int q){
return root(p) == root(q);
}
// change root of smaller tree to point to root of bigger tree
public void union(int p, int q){
int i = root(p);
int j = root(q);
if (sz[i] < sz[j]){
id[i] = j;
sz[j] += sz[i];
}
else{
id[j] = i;
sz[i] += sz[j];
}
}
}
4.3 复杂度
对于N个object来讲,某个object的深度最多为lg2(N)。
因为每个object的深度增长只有在另一个树的大小大于这个object所在树时才会发生,即深度每加1,树的大小至少乘以2,树最大只能是N(所有object都在一个树里)。因此object的深度最多为lg2(N)。
那么find操作最多需要2lg2(N)次数组读取操作。
5. 对Quick-union的改进:Quick-union with path compression
在每次进行root操作时,把经过的每个object都重新指向树的root。
5.1 Java实现1:给root操作增加第二个循环,将经历的每个object都指向树的root
public class QuickUnionUFPathCompresion{
private int[] id;
// Constructor, set id of each object to itself (N array accesses)
public QuickUnionUFPathCompresion(int N){
id = new int[N];
for (int i = 0; i < N; i++) id[i] = i;
}
private int root(int i){
int begin = i
while (i != id[i]){
i = id[i];
}
int b = begin;
while (begin != id[begin]){
b = begin;
begin = id[begin];
id[b] = 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;
}
}
5.2 Java实现2:令root操作中经过的每个object都指向它的grandparent
private int root(int i){
while (i != id[i]){
id[i] = id[id[i]];
i = id[i];
}
return i;
}