📜  在有向图中检测循环

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

在有向图中检测循环

给定一个有向图,检查该图是否包含循环。如果给定的图形包含至少一个循环,您的函数应该返回 true,否则返回 false。
例子,

Input: n = 4, e = 6
0 -> 1, 0 -> 2, 1 -> 2, 2 -> 0, 2 -> 3, 3 -> 3
Output: Yes
Explanation:
Diagram:

在有向图中检测循环 1

The diagram clearly shows a cycle 0 -> 2 -> 0


Input:n = 4, e = 4
0 -> 1, 0 -> 2, 1 -> 2, 2 -> 3
Output:No
Explanation:
Diagram:

检测有向图中的循环 2

The diagram clearly shows no cycle

使用深度优先搜索或 DFS的解决方案

  • 方法:深度优先遍历可用于检测图中的循环。连通图的 DFS 生成一棵树。仅当图中存在后边时,图中才存在循环。后边是从节点到自身(自循环)或其在 DFS 生成的树中的祖先之一的边。在下图中,有 3 个后边,用叉号标记。我们可以观察到这 3 个后边表示图中存在 3 个循环。

深度优先遍历来检测图中的循环

  • 对于断开连接的图,获取 DFS 森林作为输出。要检测循环,请通过检查后边缘来检查单个树中的循环。
    要检测后边缘,请跟踪当前在 DFS 遍历函数的递归堆栈中的顶点。如果到达已经在递归堆栈中的顶点,则树中有一个循环。将当前顶点连接到递归堆栈中的顶点的边是后边。使用recStack[]数组来跟踪递归堆栈中的顶点。
    上述方法的空运行:

深度优先遍历的空运行以检测图中的循环

在上图中有一个错误节点 1 正在向 2 做有向边而不是 0 请注意。

  • 算法:
    1. 使用给定数量的边和顶点创建图形。
    2. 创建一个递归函数,用于初始化当前索引或顶点、已访问和递归堆栈。
    3. 将当前节点标记为已访问,并在递归堆栈中标记索引。
    4. 找到所有未访问且与当前节点相邻的顶点。递归调用这些顶点的函数,如果递归函数返回true,则返回true。
    5. 如果相邻顶点已经在递归堆栈中被标记,则返回 true。
    6. 创建一个包装类,它为所有顶点调用递归函数,如果任何函数返回 true,则返回 true。否则,如果对于所有顶点,该函数返回 false,则返回 false。

执行:

C++
// A C++ Program to detect cycle in a graph
#include
 
using namespace std;
 
class Graph
{
    int V;    // No. of vertices
    list *adj;    // Pointer to an array containing adjacency lists
    bool isCyclicUtil(int v, bool visited[], bool *rs);  // used by isCyclic()
public:
    Graph(int V);   // Constructor
    void addEdge(int v, int w);   // to add an edge to graph
    bool isCyclic();    // returns true if there is a cycle in this graph
};
 
Graph::Graph(int V)
{
    this->V = V;
    adj = new list[V];
}
 
void Graph::addEdge(int v, int w)
{
    adj[v].push_back(w); // Add w to v’s list.
}
 
// This function is a variation of DFSUtil() in https://www.geeksforgeeks.org/archives/18212
bool Graph::isCyclicUtil(int v, bool visited[], bool *recStack)
{
    if(visited[v] == false)
    {
        // Mark the current node as visited and part of recursion stack
        visited[v] = true;
        recStack[v] = true;
 
        // Recur for all the vertices adjacent to this vertex
        list::iterator i;
        for(i = adj[v].begin(); i != adj[v].end(); ++i)
        {
            if ( !visited[*i] && isCyclicUtil(*i, visited, recStack) )
                return true;
            else if (recStack[*i])
                return true;
        }
 
    }
    recStack[v] = false;  // remove the vertex from recursion stack
    return false;
}
 
// Returns true if the graph contains a cycle, else false.
// This function is a variation of DFS() in https://www.geeksforgeeks.org/archives/18212
bool Graph::isCyclic()
{
    // Mark all the vertices as not visited and not part of recursion
    // stack
    bool *visited = new bool[V];
    bool *recStack = new bool[V];
    for(int i = 0; i < V; i++)
    {
        visited[i] = false;
        recStack[i] = false;
    }
 
    // Call the recursive helper function to detect cycle in different
    // DFS trees
    for(int i = 0; i < V; i++)
        if ( !visited[i] && isCyclicUtil(i, visited, recStack))
            return true;
 
    return false;
}
 
int main()
{
    // Create a graph given in the above diagram
    Graph g(4);
    g.addEdge(0, 1);
    g.addEdge(0, 2);
    g.addEdge(1, 2);
    g.addEdge(2, 0);
    g.addEdge(2, 3);
    g.addEdge(3, 3);
 
    if(g.isCyclic())
        cout << "Graph contains cycle";
    else
        cout << "Graph doesn't contain cycle";
    return 0;
}


Java
// A Java Program to detect cycle in a graph
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
 
class Graph {
     
    private final int V;
    private final List> adj;
 
    public Graph(int V)
    {
        this.V = V;
        adj = new ArrayList<>(V);
         
        for (int i = 0; i < V; i++)
            adj.add(new LinkedList<>());
    }
     
    // This function is a variation of DFSUtil() in
    // https://www.geeksforgeeks.org/archives/18212
    private boolean isCyclicUtil(int i, boolean[] visited,
                                      boolean[] recStack)
    {
         
        // Mark the current node as visited and
        // part of recursion stack
        if (recStack[i])
            return true;
 
        if (visited[i])
            return false;
             
        visited[i] = true;
 
        recStack[i] = true;
        List children = adj.get(i);
         
        for (Integer c: children)
            if (isCyclicUtil(c, visited, recStack))
                return true;
                 
        recStack[i] = false;
 
        return false;
    }
 
    private void addEdge(int source, int dest) {
        adj.get(source).add(dest);
    }
 
    // Returns true if the graph contains a
    // cycle, else false.
    // This function is a variation of DFS() in
    // https://www.geeksforgeeks.org/archives/18212
    private boolean isCyclic()
    {
         
        // Mark all the vertices as not visited and
        // not part of recursion stack
        boolean[] visited = new boolean[V];
        boolean[] recStack = new boolean[V];
         
         
        // Call the recursive helper function to
        // detect cycle in different DFS trees
        for (int i = 0; i < V; i++)
            if (isCyclicUtil(i, visited, recStack))
                return true;
 
        return false;
    }
 
    // Driver code
    public static void main(String[] args)
    {
        Graph graph = new Graph(4);
        graph.addEdge(0, 1);
        graph.addEdge(0, 2);
        graph.addEdge(1, 2);
        graph.addEdge(2, 0);
        graph.addEdge(2, 3);
        graph.addEdge(3, 3);
         
        if(graph.isCyclic())
            System.out.println("Graph contains cycle");
        else
            System.out.println("Graph doesn't "
                                    + "contain cycle");
    }
}
 
// This code is contributed by Sagar Shah.


Python
# Python program to detect cycle
# in a graph
 
from collections import defaultdict
 
class Graph():
    def __init__(self,vertices):
        self.graph = defaultdict(list)
        self.V = vertices
 
    def addEdge(self,u,v):
        self.graph[u].append(v)
 
    def isCyclicUtil(self, v, visited, recStack):
 
        # Mark current node as visited and
        # adds to recursion stack
        visited[v] = True
        recStack[v] = True
 
        # Recur for all neighbours
        # if any neighbour is visited and in
        # recStack then graph is cyclic
        for neighbour in self.graph[v]:
            if visited[neighbour] == False:
                if self.isCyclicUtil(neighbour, visited, recStack) == True:
                    return True
            elif recStack[neighbour] == True:
                return True
 
        # The node needs to be popped from
        # recursion stack before function ends
        recStack[v] = False
        return False
 
    # Returns true if graph is cyclic else false
    def isCyclic(self):
        visited = [False] * (self.V + 1)
        recStack = [False] * (self.V + 1)
        for node in range(self.V):
            if visited[node] == False:
                if self.isCyclicUtil(node,visited,recStack) == True:
                    return True
        return False
 
g = Graph(4)
g.addEdge(0, 1)
g.addEdge(0, 2)
g.addEdge(1, 2)
g.addEdge(2, 0)
g.addEdge(2, 3)
g.addEdge(3, 3)
if g.isCyclic() == 1:
    print "Graph has a cycle"
else:
    print "Graph has no cycle"
 
# Thanks to Divyanshu Mehta for contributing this code


C#
// A C# Program to detect cycle in a graph
using System;
using System.Collections.Generic;
 
public class Graph {
     
    private readonly int V;
    private readonly List> adj;
 
    public Graph(int V)
    {
        this.V = V;
        adj = new List>(V);
         
        for (int i = 0; i < V; i++)
            adj.Add(new List());
    }
     
    // This function is a variation of DFSUtil() in
    // https://www.geeksforgeeks.org/archives/18212
    private bool isCyclicUtil(int i, bool[] visited,
                                    bool[] recStack)
    {
         
        // Mark the current node as visited and
        // part of recursion stack
        if (recStack[i])
            return true;
 
        if (visited[i])
            return false;
             
        visited[i] = true;
 
        recStack[i] = true;
        List children = adj[i];
         
        foreach (int c in children)
            if (isCyclicUtil(c, visited, recStack))
                return true;
                 
        recStack[i] = false;
 
        return false;
    }
 
    private void addEdge(int sou, int dest) {
        adj[sou].Add(dest);
    }
 
    // Returns true if the graph contains a
    // cycle, else false.
    // This function is a variation of DFS() in
    // https://www.geeksforgeeks.org/archives/18212
    private bool isCyclic()
    {
         
        // Mark all the vertices as not visited and
        // not part of recursion stack
        bool[] visited = new bool[V];
        bool[] recStack = new bool[V];
         
         
        // Call the recursive helper function to
        // detect cycle in different DFS trees
        for (int i = 0; i < V; i++)
            if (isCyclicUtil(i, visited, recStack))
                return true;
 
        return false;
    }
 
    // Driver code
    public static void Main(String[] args)
    {
        Graph graph = new Graph(4);
        graph.addEdge(0, 1);
        graph.addEdge(0, 2);
        graph.addEdge(1, 2);
        graph.addEdge(2, 0);
        graph.addEdge(2, 3);
        graph.addEdge(3, 3);
         
        if(graph.isCyclic())
            Console.WriteLine("Graph contains cycle");
        else
            Console.WriteLine("Graph doesn't "
                                    + "contain cycle");
    }
}
 
// This code contributed by Rajput-Ji


Javascript


输出:

Graph contains cycle
  • 复杂性分析:
    • 时间复杂度: O(V+E)。
      该方法的时间复杂度与 DFS 遍历的时间复杂度相同,为 O(V+E)。
    • 空间复杂度: O(V)。
      存储访问和递归堆栈需要 O(V) 空间。