解析并查集算法

并查集

并查集是一种树状的数据结构,其特点是各元素间可连接不同元素或都连接同一元素,常用于图的连通分量的查找。

英文。。。DisjointSet,UnionFind?随你怎么叫吧!

实现(无向图)

union的三种实现方法

quickFind

并查集最简单的实现,查找直接返回结点的上一级,每次查找只需要访问一次数组,缺点是每次union操作都需要遍历数组去查找参数的根节点,最坏情况达到平方级别,只适用于小规模的问题

【关于复杂度的问题以后有兴趣再深入探究】

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

    public QuickFindUF(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){
        int pId = find(p);
        int qId = find(q);
    };

[图示]

quickUnion

对find的算法做了改进,使得find能逐链接索引直到根节点,union时只需要连接两个根节点即可,但是没能解决递归树倾斜问题,最坏输入情况时并查集将沦为链表,届时查找时间复杂度为O( N 2 N^2 N2)

【关于复杂度的问题以后有兴趣再深入探究】

package Chapter1.UnionFind;

public class QuickUnionUF {
 	/*除下面的函数改动外其他函数代码完全一致*/
    
    //由只索引上级改为链式索引到根
    public int find(int p){
        while(id[p] != p)
            p = id[p];
        return id[p];
    };

    //直接连接根节点,不判断两个根树各自的结点数,易造成树倾斜
    public void union(int p, int q){
        int pRoot = find(p);
        int qRoot = find(q);
        if(pRoot == qRoot) return;
        id[pRoot] = qRoot;
        count--;
    };
}

解析并查集算法_第1张图片

加权quickUnion(按秩压缩)

将结点少的根树连接到结点多的根树下,有效防止了树倾斜

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

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

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

    public void union(int p, int q){
        int pRoot = find(p);
        int qRoot = find(q);
        if(pRoot == qRoot) return;
//        结点数小的数连到结点大的树下
        if(sz[pRoot] > sz[qRoot]){
            id[qRoot] = id[pRoot];
            sz[pRoot] += sz[qRoot];
        }
        else{
            id[pRoot] = id[qRoot];
            sz[qRoot] += sz[pRoot];
        }
        count--;
    };

}

解析并查集算法_第2张图片

优化

路径压缩

会将查询路径上遇到的所有节点都连接到根节点上,使得查询效率接近常数

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

    public PathHalvingUF(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);
    }

    public int find(int p){
        int root = p;
        while(id[root] != root) {
            root = id[root];
        }
        //查找到根节点后,从所查节点向上索引将结点一个个改连到根节点上
        while(p != root){
            int orgFather = id[p];
            id[p] = root;
            p = orgFather;
        }
        return root;
    };

    public void union(int p, int q){
        int pRoot = find(p);
        int qRoot = find(q);
        if(pRoot == qRoot) return;
//        结点数小的数连到结点大的树下
        if(sz[pRoot] > sz[qRoot]){
            id[qRoot] = id[pRoot];
            sz[pRoot] += sz[qRoot];
        }
        else{
            id[pRoot] = id[qRoot];
            sz[qRoot] += sz[pRoot];
        }

        count--;
    };

性能一览

解析并查集算法_第3张图片

扩展参考

《算法4》 1.5

你可能感兴趣的:(算法,图论,数据结构)