包含路径秩和压缩的并查集算法

在之前的文章中,讨论了并查集的朴素版本

// Naive implementation of find
static int find(int parent[], int i)
{
    if (parent[i] == -1)
        return i;
    return find(parent, parent[i]);
}
   
// Naive implementation of union()
static void Union(int parent[], int x, int y)
{
    int xset = find(parent, x);
    int yset = find(parent, y);
    parent[xset] = yset;
}

上面的 u n i o n ( ) union() union() f i n d ( ) find() find()是朴素的,最坏情况下的时间复杂度是线性的。为表示子集而创建的树可以倾斜,并且可以变得像一个链表。以下是最坏情况的示例

Let there be 4 elements 0, 1, 2, 3

Initially, all elements are single element subsets.
0 1 2 3 

Do Union(0, 1)
   1   2   3  
  /
 0

Do Union(1, 2)
     2   3   
    /
   1
 /
0

Do Union(2, 3)
         3    
        /
      2
     /
   1
 /
0
  • 上述操作在最坏情况下可以优化为 O ( L o g n ) O(Log n) O(Logn)​。这个想法是总是在更深的树的根下附加更小的深度树。这种技术称为union by rank
  • 首选术语秩rank而不是高度height,因为如果使用路径压缩技术(我们在下面讨论),那么秩rank并不总是等于高度height
  • 此外,树的大小size(代替高度height)也可以用作秩rank。使用大小size作为秩rank也会产生 O(Logn) 的最坏情况时间复杂度。
Let us see the above example with union by rank
Initially, all elements are single element subsets.
0 1 2 3 

Do Union(0, 1)
   1   2   3  
  /
 0

Do Union(1, 2)
   1    3
 /  \
0    2

Do Union(2, 3)
    1    
 /  |  \
0   2   3
  • 对朴素方法的第二个优化是Path Compression。这个想法是在调用**find()**时压平树。当为元素 x x x调用find(),返回树的根。在find() 操作从 x x x出发找到根。
  • 路径压缩Path Compression的想法是将找到的根作为 x x x​ 的父节点,这样我们就不必再次遍历所有中间节点。如果 x 是子树的根,那么 x 下所有节点的路径(到根)也会压缩。
Let the subset {0, 1, .. 9} be represented as below and find() is called
for element 3.
              9
         /    |    \  
        4     5      6
     /     \        /  \
    0        3     7    8
            /  \
           1    2  

When find() is called for 3, we traverse up and find 9 as representative
of this subset. With path compression, we also make 3 as the child of 9 so 
that when find() is called next time for 1, 2 or 3, the path to root is reduced.

              9 
         /    /  \    \
        4    5    6     3 
     /           /  \   /  \
  0            7    8  1   2

秩rank与路径压缩Path Compression带来的好处也很明显,每个操作的时间复杂度变得甚至小于 O(Logn),摊销的时间复杂度甚至能达到常数级别

Code:

static class Graph {
    int V, E;//顶点与边的数量
    Edge[] edges;//边

    //初始化
    public Graph(int V, int E) {
        this.V = V;
        this.E = E;
        edges = new Edge[E];
        for (int i = 0; i < E; i++) {
            edges[i] = new Edge();
        }
    }

    class Edge {
        int u, v;//边的两个点
    }

    class Subset {
        int parent;
        int rank;
    }

    //路径压缩找到i的子集中的根节点
    int find(Subset[] subsets, int i) {
        if (subsets[i].parent != i) {
            subsets[i].parent = find(subsets, subsets[i].parent);
        }
        return subsets[i].parent;
    }

    //按秩进行合并
    void union(Subset[] subsets, int x, int y) {
        int xroot = find(subsets, x);
        int yroot = find(subsets, y);
        if (subsets[xroot].rank < subsets[yroot].rank) subsets[xroot].parent = yroot;
        else if (subsets[xroot].rank > subsets[yroot].rank) subsets[yroot].parent = xroot;
        else {
            subsets[xroot].parent = yroot;
            subsets[yroot].rank++;
        }
    }

    int isCycle(Graph graph) {
        //初始化subsets
        int V = graph.V, E = graph.E;
        Subset[] subsets = new Subset[V];
        for (int v = 0; v < V; v++) {
            subsets[v] = new Subset();
            subsets[v].parent = v;//根节点是其自身
            subsets[v].rank = 0;//初始化时秩为0
        }
        //遍历每一条边
        for (int e = 0; e < E; e++) {//边的索引
            Edge edge = graph.edges[e];//当前的边
            int x = edge.u, y = edge.v;//分别找到x y 的根节点
            int xroot = find(subsets, x);
            int yroot = find(subsets, y);
            if (xroot == yroot) return 1;//出现根一样的,出现了环
            union(subsets, xroot, yroot);
        }
        return 0;
    }

    public static void main(String[] args) {
    /* Let us create the following graph
        0
        | \
        | \
        1-----2 */

        int V = 3, E = 3;
        Graph graph = new Graph(V, E);

        // add edge 0-1
        graph.edges[0].u = 0;
        graph.edges[0].v = 1;

        // add edge 1-2
        graph.edges[1].u = 1;
        graph.edges[1].v = 2;

        // add edge 0-2
        graph.edges[2].u = 0;
        graph.edges[2].v = 2;

        if (graph.isCycle(graph) == 1)
            System.out.println("Graph contains cycle");
        else
            System.out.println("Graph doesn't contain cycle");
    }
}

Reference

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值