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;
}