java 并查集算法中的按秩联合和路径压缩(Union By Rank and Path Compression in Union-Find Algorithm)

在以下文章中,我们介绍了并查集算法;
javascript 不相交集简介(并查集算法):https://blog.csdn.net/hefeng_aspnet/article/details/142524938

C# 不相交集简介(并查集算法):https://blog.csdn.net/hefeng_aspnet/article/details/142524824

python 不相交集简介(并查集算法):https://blog.csdn.net/hefeng_aspnet/article/details/142524698

java 不相交集简介(并查集算法):https://blog.csdn.net/hefeng_aspnet/article/details/142524698

c++ 不相交集简介(并查集算法):https://blog.csdn.net/hefeng_aspnet/article/details/142523025

并用它来检测图中的循环。我们对子集使用了以下union()和find()操作。

// 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;
}
 
// This code is contributed by divyesh072019 

上面的union()和find()很简单,最坏情况下的时间复杂度是线性的。为表示子集而创建的树可能会歪斜,并且可能变得像链表一样。以下是一个最坏情况的示例。 

假设有 4 个元素 0、1、2、3

最初,所有元素都是单元素子集。
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(Log n) 。其思想是始终将深度较小的树附加在较深树的根下。此​​技术称为按等级联合。术语“等级”优于“高度”,因为如果使用路径压缩技术(我们在下面讨论过),则等级并不总是等于高度。此外,树的大小(代替高度)也可以用作等级。使用大小作为等级也会导致最坏情况的时间复杂度为 O(Logn)。

让我们看一下上面按等级合并的示例

最初,所有元素都是单元素子集。
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

图示: 

    对简单方法的第二个优化是路径压缩。其思想是在调用find()时使树变平。当为元素 x 调用find()时,将返回树的根。find ()操作从 x 向上遍历以找到根。路径压缩的思想是将找到的根作为 x 的父节点,这样我们就不必再次遍历所有中间节点。如果 x 是子树的根,则 x 下所有节点的路径(到根)也会压缩。

假设子集 {0, 1, .. 9} 如下所示,并对
元素 3调用 find()。 

             9
         /   |   \  
        4    5    6
       /         /  \
      0         7    8
     /        
    3
   / \         
  1   2
 图示:


当为 3 调用find()时,我们向上遍历并找到 9 作为
此子集的代表。通过路径压缩,我们还将 3 和 0 作为 9 的子元素,以便
下次为 0、1、2 或 3 调用 find() 时,到根的路径会减少。

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

图示:

这两种技术 - 路径压缩和按等级/大小合并,时间复杂度将达到接近常数时间。事实证明,最终的摊销时间复杂度为 O(α(n)),其中 α(n) 是逆阿克曼函数,其增长非常稳定(当 n<10 600   时,它甚至不会超过)。

以下是按秩联合和基于路径压缩的实现来在图中查找循环:  

// A union by rank and path compression
// based program to detect cycle in a graph
class Graph 
{
    int V, E;
    Edge[] edge;
 
    Graph(int nV, int nE)
    {
        V = nV;
        E = nE;
        edge = new Edge[E];
        for (int i = 0; i < E; i++) 
        {
            edge[i] = new Edge();
        }
    }
 
    // class to represent edge
    class Edge 
    {
        int src, dest;
    }
 
    // class to represent Subset
    class subset 
    {
        int parent;
        int rank;
    }
 
    // A utility function to find
    // set of an element i (uses
    // path compression technique)
    int find(subset[] subsets, int i)
    {
        if (subsets[i].parent != i)
            subsets[i].parent
                = find(subsets, subsets[i].parent);
        return subsets[i].parent;
    }
 
    // A function that does union
    // of two sets of x and y
    // (uses union by rank)
    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[yroot].rank < subsets[xroot].rank)
            subsets[yroot].parent = xroot;
        else {
            subsets[xroot].parent = yroot;
            subsets[yroot].rank++;
        }
    }
 
    // The main function to check whether
    // a given graph contains cycle or not
    int isCycle(Graph graph)
    {
        int V = graph.V;
        int 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;
        }
 
        for (int e = 0; e < E; e++) {
            int x = find(subsets, graph.edge[e].src);
            int y = find(subsets, graph.edge[e].dest);
            if (x == y)
                return 1;
            Union(subsets, x, y);
        }
        return 0;
    }
 
    // Driver Code
    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.edge[0].src = 0;
        graph.edge[0].dest = 1;
 
        // add edge 1-2
        graph.edge[1].src = 1;
        graph.edge[1].dest = 2;
 
        // add edge 0-2
        graph.edge[2].src = 0;
        graph.edge[2].dest = 2;
 
        if (graph.isCycle(graph) == 1)
            System.out.println("Graph contains cycle");
        else
            System.out.println(
                "Graph doesn't contain cycle");
    }
}
 
// This code is contributed
// by ashwani khemani 

输出

Graph contains cycle

时间复杂度: O(ElogV),其中E是图中的边数,V是顶点数。 

空间复杂度: O(V),其中 V 是顶点的数量。这是因为我们使用子集数组来存储每个顶点的代表元素,并且该数组的大小与顶点数量成正比。

猜你喜欢

转载自blog.csdn.net/hefeng_aspnet/article/details/142526336