普林斯顿算法课Part 1 Week 1 Union−Find

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Cyril__Li/article/details/79631239

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

猜你喜欢

转载自blog.csdn.net/Cyril__Li/article/details/79631239