并查集

并查集_第1张图片
并查集_第2张图片


用数组表示,
0和0的相联,1和1的相联

package bobo.algo;

// 我们的第一版Union-Find

public class UnionFind1 {

private int[] id;    // 我们的第一版Union-Find本质就是一个数组

    private int count;  // 数据个数

    public UnionFind1(int n) {

count = n;

        id =new int[n];

        // 初始化, 每一个id[i]指向自己, 没有合并的元素

        for (int i =0; i < n; i++)

id[i] = i;

    }

// 查找过程, 查找元素p所对应的集合编号

    // O(1)复杂度

    private int find(int p) {

assert p >=0 && p < count;

        return id[p];

    }

// 查看元素p和元素q是否所属一个集合

    // O(1)复杂度

    public boolean isConnected(int p, int q) {

return find(p) == find(q);

    }

// 合并元素p和元素q所属的集合

    // O(n) 复杂度

    public void unionElements(int p, int q) {

int pID = find(p);

        int qID = find(q);

        if (pID == qID)

return;

        // 合并过程需要遍历一遍所有元素, 将两个元素的所属集合编号合并

        for (int i =0; i < count; i++)

if (id[i] == pID)

id[i] = qID;

    }

}


并查集_第3张图片
将union连接的两个数的所在组的所有元素都相同,所以要遍历所有两个组的元素

Quick union
另一种解法,union将元素的父指针指向要连接的元素的根节点,根节点的父亲指针指向自己

可以用数组表示,其值是父亲节点的索引,数组初始值是自己



并查集_第4张图片


并查集_第5张图片

联合算法:

找到p的根

找到q的根

判断是否相等

不等则将p的根指向q的根就好了

缺点有可能多的节点指向短的节点的根,线长,效率也差,应该让短的指向长的根节点,形成层数较低的树。


package bobo.algo;

// 我们的第二版Union-Find

public class UnionFind2 {

// 我们的第二版Union-Find, 使用一个数组构建一棵指向父节点的树

    // parent[i]表示第一个元素所指向的父节点

    private int[] parent;

    private int count;  // 数据个数

    // 构造函数

    public UnionFind2(int count){

parent =new int[count];

        this.count = count;

        // 初始化, 每一个parent[i]指向自己, 表示每一个元素自己自成一个集合

        for(int i =0 ; i < count; i ++ )

parent[i] = i;

    }

// 查找过程, 查找元素p所对应的集合编号

    // O(h)复杂度, h为树的高度

    private int find(int p){

assert( p >=0 && p < count );

        // 不断去查询自己的父亲节点, 直到到达根节点

        // 根节点的特点: parent[p] == p

        while( p != parent[p] )

p = parent[p];

        return p;

    }

// 查看元素p和元素q是否所属一个集合

    // O(h)复杂度, h为树的高度

    public boolean isConnected(int p, int q ){

return find(p) == find(q);

    }

// 合并元素p和元素q所属的集合

    // O(h)复杂度, h为树的高度

    public void unionElements(int p, int q){

int pRoot = find(p);

        int qRoot = find(q);

        if( pRoot == qRoot )

return;

        parent[pRoot] = qRoot;

    }

}




并查集_第6张图片

更好的优化,层数低的树连上层数高的树,这样更好优化

并查集_第7张图片

package bobo.algo;

// 我们的第三版Union-Find

public class UnionFind3 {

private int[] parent; // parent[i]表示第一个元素所指向的父节点

    private int[] sz;    // sz[i]表示以i为根的集合中元素个数

    private int count;    // 数据个数

    // 构造函数

    public UnionFind3(int count){

parent =new int[count];

        sz =new int[count];

        this.count = count;

        // 初始化, 每一个parent[i]指向自己, 表示每一个元素自己自成一个集合

        for(int i =0 ; i < count; i ++ ){

parent[i] = i;

            sz[i] =1;

        }

}

// 查找过程, 查找元素p所对应的集合编号

    // O(h)复杂度, h为树的高度

    private int find(int p){

assert( p >=0 && p < count );

        // 不断去查询自己的父亲节点, 直到到达根节点

        // 根节点的特点: parent[p] == p

        while( p != parent[p] )

p = parent[p];

        return p;

    }

// 查看元素p和元素q是否所属一个集合

    // O(h)复杂度, h为树的高度

    public boolean isConnected(int p, int q ){

return find(p) == find(q);

    }

// 合并元素p和元素q所属的集合

    // O(h)复杂度, h为树的高度

    public void unionElements(int p, int q){

int pRoot = find(p);

        int qRoot = find(q);

        if( pRoot == qRoot )

return;

        // 根据两个元素所在树的元素个数不同判断合并方向

        // 将元素个数少的集合合并到元素个数多的集合上

        if( sz[pRoot] < sz[qRoot] ){

parent[pRoot] = qRoot;

            sz[qRoot] += sz[pRoot];

        }

else{

parent[qRoot] = pRoot;

            sz[pRoot] += sz[qRoot];

        }

}

}



并查集_第8张图片
rank慢一点但是,特殊情况会稳定


package bobo.algo;
路径压缩,将子节点连接到其父亲的父亲的节点, 从而降低路径的层数。

// 我们的第四版Union-Find

public class UnionFind4 {

private int[] rank;  // rank[i]表示以i为根的集合所表示的树的层数

    private int[] parent; // parent[i]表示第i个元素所指向的父节点

    private int count;    // 数据个数

    // 构造函数

    public UnionFind4(int count){

rank =new int[count];

        parent =new int[count];

        this.count = count;

        // 初始化, 每一个parent[i]指向自己, 表示每一个元素自己自成一个集合

        for(int i =0 ; i < count; i ++ ){

parent[i] = i;

            rank[i] =1;

        }

}

// 查找过程, 查找元素p所对应的集合编号

    // O(h)复杂度, h为树的高度

    private int find(int p){

assert( p >=0 && p < count );

        // 不断去查询自己的父亲节点, 直到到达根节点

        // 根节点的特点: parent[p] == p

        while( p != parent[p] )

p = parent[p];

        return p;

    }

// 查看元素p和元素q是否所属一个集合

    // O(h)复杂度, h为树的高度

    public boolean isConnected(int p, int q ){

return find(p) == find(q);

    }

// 合并元素p和元素q所属的集合

    // O(h)复杂度, h为树的高度

    public void unionElements(int p, int q){

int pRoot = find(p);

        int qRoot = find(q);

        if( pRoot == qRoot )

return;

        // 根据两个元素所在树的元素个数不同判断合并方向

        // 将元素个数少的集合合并到元素个数多的集合上

        if( rank[pRoot] < rank[qRoot] ){

parent[pRoot] = qRoot;

        }

else if( rank[qRoot] < rank[pRoot]){

parent[qRoot] = pRoot;

        }

else{// rank[pRoot] == rank[qRoot]

            parent[pRoot] = qRoot;

            rank[qRoot] +=1;  // 此时, 我维护rank的值

        }

}

}



并查集_第9张图片

public int find(int p){

assert( p >=0 && p < count );

    // path compression 1

    while( p != parent[p] ){

parent[p] = parent[parent[p]];

        p = parent[p];

    }

return p;

}

最优的路径压缩是压缩成2层


并查集_第10张图片


package bobo.algo;

// 我们的第六版Union-Find, 路径压缩使用递归实现

public class UnionFind6implements UF {

// rank[i]表示以i为根的集合所表示的树的层数

    // 在后续的代码中, 我们并不会维护rank的语意, 也就是rank的值在路径压缩的过程中, 有可能不在是树的层数值

    // 这也是我们的rank不叫height或者depth的原因, 他只是作为比较的一个标准

    // 关于这个问题,可以参考问答区:http://coding.imooc.com/learn/questiondetail/7287.html

    private int[] rank;

    public int[] parent; // parent[i]表示第i个元素所指向的父节点

                        // 后续, 我们要在外部操控并查集的数据, 在这里使用public

    private int count;  // 数据个数

    // 构造函数

    public UnionFind6(int count){

rank =new int[count];

        parent =new int[count];

        this.count = count;

        // 初始化, 每一个parent[i]指向自己, 表示每一个元素自己自成一个集合

        for(int i =0 ; i < count; i ++ ){

parent[i] = i;

            rank[i] =1;

        }

}

// 查找过程, 查找元素p所对应的集合编号

    // O(h)复杂度, h为树的高度

    public int find(int p){

assert( p >=0 && p < count );

        // path compression 2, 递归算法

        if( p != parent[p] )

parent[p] = find( parent[p] );

        return parent[p];

    }

// 查看元素p和元素q是否所属一个集合

    // O(h)复杂度, h为树的高度

    public boolean isConnected(int p, int q ){

return find(p) == find(q);

    }

// 合并元素p和元素q所属的集合

    // O(h)复杂度, h为树的高度

    public void unionElements(int p, int q){

int pRoot = find(p);

        int qRoot = find(q);

        if( pRoot == qRoot )

return;

        // 根据两个元素所在树的元素个数不同判断合并方向

        // 将元素个数少的集合合并到元素个数多的集合上

        if( rank[pRoot] < rank[qRoot] ){

parent[pRoot] = qRoot;

        }

else if( rank[qRoot] < rank[pRoot]){

parent[qRoot] = pRoot;

        }

else{// rank[pRoot] == rank[qRoot]

            parent[pRoot] = qRoot;

            rank[qRoot] +=1;  // 此时, 我维护rank的值

        }

}

// 打印输出并查集中的parent数据

    public void show(){

for(int i =0 ; i < count; i ++ )

System.out.print( parent[i] +" ");

        System.out.println();

    }

}

你可能感兴趣的:(并查集)