python 并查集算法中的按秩联合和路径压缩(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
def find(parent, i):
     
    if (parent[i] == -1):
        return i
     
    return find(parent, parent[i])
 
# Naive implementation of union()
def Union(parent, x, y):
 
    xset = find(parent, x)
    yset = find(parent, y)
    parent[xset] = yset
 
# This code is contributed by rutvik_56

上面的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
from collections import defaultdict
 
# a structure to represent a graph
 
 
class Graph:
 
    def __init__(self, num_of_v):
        self.num_of_v = num_of_v
        self.edges = defaultdict(list)
 
    # graph is represented as an
    # array of edges
    def add_edge(self, u, v):
        self.edges[u].append(v)
 
 
class Subset:
    def __init__(self, parent, rank):
        self.parent = parent
        self.rank = rank
 
# A utility function to find set of an element
# node(uses path compression technique)
 
 
def find(subsets, node):
    if subsets[node].parent != node:
        subsets[node].parent = find(subsets, subsets[node].parent)
    return subsets[node].parent
 
# A function that does union of two sets
# of u and v(uses union by rank)
 
 
def union(subsets, u, v):
 
    # Attach smaller rank tree under root
    # of high rank tree(Union by Rank)
    if subsets[u].rank > subsets[v].rank:
        subsets[v].parent = u
    elif subsets[v].rank > subsets[u].rank:
        subsets[u].parent = v
 
    # If ranks are same, then make one as
    # root and increment its rank by one
    else:
        subsets[v].parent = u
        subsets[u].rank += 1
 
# The main function to check whether a given
# graph contains cycle or not
 
 
def isCycle(graph):
 
    # Allocate memory for creating sets
    subsets = []
 
    for u in range(graph.num_of_v):
        subsets.append(Subset(u, 0))
 
    # Iterate through all edges of graph,
    # find sets of both vertices of every
    # edge, if sets are same, then there
    # is cycle in graph.
    for u in graph.edges:
        u_rep = find(subsets, u)
 
        for v in graph.edges[u]:
            v_rep = find(subsets, v)
 
            if u_rep == v_rep:
                return True
            else:
                union(subsets, u_rep, v_rep)
 
 
# Driver Code
g = Graph(3)
 
# add edge 0-1
g.add_edge(0, 1)
 
# add edge 1-2
g.add_edge(1, 2)
 
# add edge 0-2
g.add_edge(0, 2)
 
if isCycle(g):
    print('Graph contains cycle')
else:
    print('Graph does not contain cycle')
 
# This code is contributed by
# Sampath Kumar Surine 

输出

Graph contains cycle

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

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

猜你喜欢

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