📜  不相交集(或并集)| Set 1(检测无向图中的循环)

📅  最后修改于: 2022-05-13 01:57:54.056000             🧑  作者: Mango

不相交集(或并集)| Set 1(检测无向图中的循环)

不相交集数据结构是跟踪一组元素的数据结构,这些元素被划分为多个不相交(非重叠)子集。 union-find 算法是一种对此类数据结构执行两个有用操作的算法:

查找:确定特定元素在哪个子集中。这可用于确定两个元素是否在同一个子集中。

联合:将两个子集连接成一个子集。这里首先我们必须检查两个子集是否属于同一个集合。如果不是,那么我们不能执行联合。

在这篇文章中,我们将讨论不相交集数据结构的应用。该应用程序是检查给定图形是否包含循环。

Union-Find 算法可用于检查无向图是否包含循环。请注意,我们已经讨论了一种检测循环的算法。这是基于Union-Find的另一种方法。此方法假定图形不包含任何自环。

我们可以跟踪一维数组中的子集,我们称之为 parent[]。
让我们考虑下图:

图中的循环

对于每条边,使用边的两个顶点制作子集。如果两个顶点都在同一个子集中,则找到一个循环。

最初,父数组的所有槽都初始化为-1(意味着每个子集中只有一项)。

0   1   2
-1 -1  -1

现在一一处理所有边。
边 0-1:找到顶点 0 和 1 所在的子集。由于它们在不同的子集中,我们采用它们的并集。要采用联合,请将节点 0 作为节点 1 的父节点,反之亦然。

0   1   2    <----- 1 is made parent of 0 (1 is now representative of subset {0, 1})
1  -1  -1

边 1-2: 1 在子集 1 中,2 在子集 2 中。所以,取并集。

0   1   2    <----- 2 is made parent of 1 (2 is now representative of subset {0, 1, 2})
1   2  -1

边 0-2: 0 在子集 2 中,2 也在子集 2 中。因此,包括这条边形成一个循环。
0 的子集如何与 2 相同?
0->1->2 // 1 是 0 的父级, 2 是 1 的父级

基于上面的解释,下面是实现:

C++
// A union-find algorithm to detect cycle in a graph
#include 
using namespace std;
 
// a structure to represent an edge in graph
class Edge
{
public:
    int src, dest;
};
 
// a structure to represent a graph
class Graph
{
public:
    // V-> Number of vertices, E-> Number of edges
    int V, E;
 
    // graph is represented as an array of edges
    Edge* edge;
};
 
// Creates a graph with V vertices and E edges
Graph* createGraph(int V, int E)
{
    Graph* graph = new Graph();
    graph->V = V;
    graph->E = E;
 
    graph->edge = new Edge[graph->E * sizeof(Edge)];
 
    return graph;
}
 
// A utility function to find the subset of an element i
int find(int parent[], int i)
{
    if (parent[i] == -1)
        return i;
    return find(parent, parent[i]);
}
 
// A utility function to do union of two subsets
void Union(int parent[], int x, int y)
{
    parent[x] = y;
}
 
// The main function to check whether a given graph contains
// cycle or not
int isCycle(Graph* graph)
{
    // Allocate memory for creating V subsets
    int* parent = new int[graph->V * sizeof(int)];
 
    // Initialize all subsets as single element sets
    memset(parent, -1, sizeof(int) * graph->V);
 
    // Iterate through all edges of graph, find subset of
    // both vertices of every edge, if both subsets are
    // same, then there is cycle in graph.
    for (int i = 0; i < graph->E; ++i) {
        int x = find(parent, graph->edge[i].src);
        int y = find(parent, graph->edge[i].dest);
 
        if (x == y)
            return 1;
 
        Union(parent, x, y);
    }
    return 0;
}
 
// Driver code
int main()
{
    /* Let us create the following graph
        0
        | \
        |  \
        1---2 */
    int V = 3, E = 3;
    Graph* graph = createGraph(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 (isCycle(graph))
        cout << "graph contains cycle";
    else
        cout << "graph doesn't contain cycle";
 
    return 0;
}
 
// This code is contributed by rathbhupendra


C
// A union-find algorithm to detect cycle in a graph
#include 
#include 
#include 
 
// a structure to represent an edge in graph
struct Edge
{
    int src, dest;
};
 
// a structure to represent a graph
struct Graph
{
    // V-> Number of vertices, E-> Number of edges
    int V, E;
 
    // graph is represented as an array of edges
    struct Edge* edge;
};
 
// Creates a graph with V vertices and E edges
struct Graph* createGraph(int V, int E)
{
    struct Graph* graph =
           (struct Graph*) malloc( sizeof(struct Graph) );
    graph->V = V;
    graph->E = E;
 
    graph->edge =
        (struct Edge*) malloc( graph->E * sizeof( struct Edge ) );
 
    return graph;
}
 
// A utility function to find the subset of an element i
int find(int parent[], int i)
{
    if (parent[i] == -1)
        return i;
    return find(parent, parent[i]);
}
 
// A utility function to do union of two subsets
void Union(int parent[], int x, int y)
{
    parent[x] = y;
}
 
// The main function to check whether a given graph contains
// cycle or not
int isCycle( struct Graph* graph )
{
    // Allocate memory for creating V subsets
    int *parent = (int*) malloc( graph->V * sizeof(int) );
 
    // Initialize all subsets as single element sets
    memset(parent, -1, sizeof(int) * graph->V);
 
    // Iterate through all edges of graph, find subset of both
    // vertices of every edge, if both subsets are same, then
    // there is cycle in graph.
    for(int i = 0; i < graph->E; ++i)
    {
        int x = find(parent, graph->edge[i].src);
        int y = find(parent, graph->edge[i].dest);
 
        if (x == y)
            return 1;
 
        Union(parent, x, y);
    }
    return 0;
}
 
// Driver program to test above functions
int main()
{
    /* Let us create the following graph
        0
        | \
        |  \
        1---2 */ 
    int V = 3, E = 3;
    struct Graph* graph = createGraph(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 (isCycle(graph))
        printf( "graph contains cycle" );
    else
        printf( "graph doesn't contain cycle" );
 
    return 0;
}


Java
// Java Program for union-find algorithm to detect cycle in a graph
import java.util.*;
import java.lang.*;
import java.io.*;
 
class Graph
{
    int V, E;    // V-> no. of vertices & E->no.of edges
    Edge edge[]; // /collection of all edges
 
    class Edge
    {
        int src, dest;
    };
 
    // Creates a graph with V vertices and E edges
    Graph(int v,int e)
    {
        V = v;
        E = e;
        edge = new Edge[E];
        for (int i=0; i


Python3
# Python Program for union-find algorithm to detect cycle in a undirected graph
# we have one egde for any two vertex i.e 1-2 is either 1-2 or 2-1 but not both
  
from collections import defaultdict
  
#This class represents a undirected graph using adjacency list representation
class Graph:
  
    def __init__(self,vertices):
        self.V= vertices #No. of vertices
        self.graph = defaultdict(list) # default dictionary to store graph
  
 
    # function to add an edge to graph
    def addEdge(self,u,v):
        self.graph[u].append(v)
  
    # A utility function to find the subset of an element i
    def find_parent(self, parent,i):
        if parent[i] == -1:
            return i
        if parent[i]!= -1:
             return self.find_parent(parent,parent[i])
 
    # A utility function to do union of two subsets
    def union(self,parent,x,y):
        parent[x] = y
 
  
  
    # The main function to check whether a given graph
    # contains cycle or not
    def isCyclic(self):
         
        # Allocate memory for creating V subsets and
        # Initialize all subsets as single element sets
        parent = [-1]*(self.V)
 
        # Iterate through all edges of graph, find subset of both
        # vertices of every edge, if both subsets are same, then
        # there is cycle in graph.
        for i in self.graph:
            for j in self.graph[i]:
                x = self.find_parent(parent, i)
                y = self.find_parent(parent, j)
                if x == y:
                    return True
                self.union(parent,x,y)
 
 
# Create a graph given in the above diagram
g = Graph(3)
g.addEdge(0, 1)
g.addEdge(1, 2)
g.addEdge(2, 0)
 
if g.isCyclic():
    print ("Graph contains cycle")
else :
    print ("Graph does not contain cycle ")
  
#This code is contributed by Neelam Yadav


C#
// C# Program for union-find
// algorithm to detect cycle
// in a graph
using System;
class Graph{
 
// V-> no. of vertices &
// E->no.of edges 
public int V, E;   
 
// collection of all edges
public Edge []edge;
 
class Edge
{
  public int src, dest;
};
 
// Creates a graph with V
// vertices and E edges
public  Graph(int v,int e)
{
  V = v;
  E = e;
  edge = new Edge[E];
   
  for (int i = 0; i < e; ++i)
    edge[i] = new Edge();
}
 
// A utility function to find
// the subset of an element i
int find(int []parent, int i)
{
  if (parent[i] == -1)
    return i;
  return find(parent,
              parent[i]);
}
 
// A utility function to do
// union of two subsets
void Union(int []parent,
           int x, int y)
{
  parent[x] = y;
}
 
// The main function to check
// whether a given graph
// contains cycle or not
int isCycle(Graph graph)
{
  // Allocate memory for
  // creating V subsets
  int []parent =
        new int[graph.V];
 
  // Initialize all subsets as
  // single element sets
  for (int i = 0; i < graph.V; ++i)
    parent[i] =- 1;
 
  // Iterate through all edges of graph,
  // find subset of both vertices of every
  // edge, if both subsets are same, then
  // there is cycle in graph.
  for (int i = 0; i < graph.E; ++i)
  {
    int x = graph.find(parent,
                       graph.edge[i].src);
    int y = graph.find(parent,
                       graph.edge[i].dest);
 
    if (x == y)
      return 1;
 
    graph.Union(parent, 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)
    Console.WriteLine("graph contains cycle");
  else
    Console.WriteLine("graph doesn't contain cycle");
}
}
 
// This code is contributed by Princi Singh


Javascript


输出:

graph contains cycle

请注意, union()find()的实现是幼稚的,在最坏的情况下需要 O(n) 时间。可以使用Union by Rank 或 Height将这些方法改进为 O(Logn)。我们很快将在另一篇文章中讨论按等级联合
https://youtu.be/mHz-mx-8lJ8?list=PLqM7alHXFySEaZgcg7uRYJFBnYMLti-nh