并查集算法:quick-find、quick-union、优化的 quick-union

1. 问题描述

可以想象一张地图上有很多点,有些点之间是有道路相互联通的,而有些点则没有。如果我们现在要从点 A 走向点 B,那么一个关键的问题就是判断我们能否从 A 走到 B 呢?换句话说,A 和 B 是否是连通的。这是动态连通性最基本的诉求。现在给出一组数据,其中每个元素都是一对“点”,代表这对点之间是联通的,我们需要设计一个算法,让计算机依次读取这些数据,最后判断出其中任意两点是否连通。

2. quick-find

保证当且仅当 id[p] 等于 id[q] 时 p 和 q 是连通的。即在同一个分量中的所有触点在 id[ ] 中的值必须全部相同。

import java.util.Scanner;

public class UF {
    private int[] id;//分量 id(以触点为索引)
    private int count;//分量数量

    public UF(int N){
        //初始化分量 id 数组
        count = N;
        id = new int[N];
        for(int i = 0;i < N;i++){
            id[i] = i;
        }
    }

    public int count(){
        return count;
    }

    public boolean connected(int p,int q){
        return find(p) == find(q);
    }

    public int find(int p){
        return id[p];
    }

    public void union(int p,int q){
        //将q和p归并到相同的分量中
        int pID = find(p);
        int qID = find(q);

        //如果p和q已经在相同的分量之中则不需要采取任何行动
        if(pID == qID) return;

        //将p的分量重命名为q的名称
        for(int i = 0;i < id.length;i++){
            if(id[i] == pID) id[i] = qID;
        }
        count--;
    }

    //解决动态连通性问题
    /*测试数据:
    10
    4 3
    3 8
    6 5
    9 4
    2 1
    5 0
    7 2
    6 1
    1 0
    6 7
    输出: 2 components */
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        int N = in.nextInt();
        UF uf = new UF(N);
        for(int i = 0;i < N;i++){
            int p = in.nextInt();
            int q = in.nextInt();
            if(uf.connected(p,q)) continue;//已经连通则忽略
            uf.union(p,q);//归并分量
        }
        System.out.println(uf.count + " components");
    }
}

时间复杂度:find() 为 O(1),union() 为 O(N)。

3. quick-union

每个触点所对应的 id[ ] 元素都是同一个分量中的另一个触点的名称(也可能是他自己)。在实现 find() 方法时,从给定触点开始,由它链接得到另一个触点,再由这个触点的链接到达第三个触点,如此继续直到到达一个根触点,即链接指向自己的触点。

package fundamentals;

import java.util.Scanner;

public class UF {
    private int[] id;//分量 id(以触点为索引)
    private int count;//分量数量

    public UF(int N){
        //初始化分量 id 数组
        count = N;
        id = new int[N];
        for(int i = 0;i < N;i++){
            id[i] = i;
        }
    }

    public int count(){
        return count;
    }

    public boolean connected(int p,int q){
        return find(p) == find(q);
    }

    private int find(int p){
        //找出分量的名称
        while(p != id[p]) p = id[p];
        return p;
    }

    private void union(int p,int q){
        //将p和q的根节点统一
        int pRoot = find(p);
        int qRoot = find(q);
        if(pRoot == qRoot) return;
        id[pRoot] = qRoot;

        count--;
    }

    //解决动态连通性问题
    /*测试数据:
    10
    4 3
    3 8
    6 5
    9 4
    2 1
    5 0
    7 2
    6 1
    1 0
    6 7
    输出: 2 components */
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        int N = in.nextInt();
        UF uf = new UF(N);
        for(int i = 0;i < N;i++){
            int p = in.nextInt();
            int q = in.nextInt();
            if(uf.connected(p,q)) continue;//已经连通则忽略
            uf.union(p,q);//归并分量
        }
        System.out.println(uf.count + " components");
    }
}

时间复杂度取决于形成的树的高度。
在这里插入图片描述

4. 优化的 quick-union

与其在 union() 中随意将一棵树连接到另一颗树,现在记录每一颗树的大小并总是将较小的树连接到较大的树上。
在这里插入图片描述

import java.util.Scanner;

public class WeightedQuickUnionUF {
    private int[] id;//分量 id(以触点为索引)
    private int[] sz;//各个根节点所对应的分量的大小
    private int count;//分量数量

    public WeightedQuickUnionUF(int N){
        //初始化分量 id 数组
        count = N;
        id = new int[N];
        for(int i = 0;i < N;i++){
            id[i] = i;
        }
        sz = new int[N];
        for(int i = 0;i < N;i++){
            sz[i] = 1;
        }
    }

    public int count(){
        return count;
    }

    public boolean connected(int p,int q){
        return find(p) == find(q);
    }

    private int find(int p){
        //找出分量的名称
        while(p != id[p]) p = id[p];
        return p;
    }

    private void union(int p,int q){
        //将p和q的根节点统一
        int i = find(p);
        int j = find(q);
        if(i == j) return;
        //将小数的根节点连接到大树的根节点
        if(sz[i] < sz[j]){
            id[i] = j;
            sz[j] += sz[i];
        }else {
            id[j] = i;
            sz[i] += sz[j];
        }
        count--;
    }

    //解决动态连通性问题
    /*测试数据:
    10
    4 3
    3 8
    6 5
    9 4
    2 1
    5 0
    7 2
    6 1
    1 0
    6 7
    输出: 2 components */
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        int N = in.nextInt();
        WeightedQuickUnionUF uf = new WeightedQuickUnionUF(N);
        for(int i = 0;i < N;i++){
            int p = in.nextInt();
            int q = in.nextInt();
            if(uf.connected(p,q)) continue;//已经连通则忽略
            uf.union(p,q);//归并分量
        }
        System.out.println(uf.count + " components");
    }
}

时间复杂度:find() 和 union() 都为 O(logN)。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章