普林斯頓算法課Part 1 Week 1 Union−Find

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

  1. 通過標準輸入讀取integer對
  2. 如果他們還沒有被連接,則將他們連接並打印這個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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章