【问题标题】:How do I check if a directed graph is acyclic?如何检查有向图是否是无环的?
【发布时间】:2010-10-09 16:36:15
【问题描述】:

如何检查有向图是否是无环的?算法是如何命名的?我将不胜感激。

【问题讨论】:

  • 另一种支持以某种方式“修复”SO 错误答案的案例。
  • 所以,嗯,我最感兴趣的是找到它所需的时间。所以,我只需要抽象算法。
  • 您必须遍历所有边并检查所有顶点,因此下限为 O(|V| + |E|)。 DFS 和 BFS 都具有相同的复杂性,但如果您有递归,则 DFS 更容易编码,因为它会为您管理堆栈...
  • DFS 的复杂性不同。考虑具有节点 { 1 .. N } 和形式为 { (a, b) | 的边的图。 a
  • DFS 永远不会是 O(n!)。它访问每个节点一次,每个边最多访问两次。所以 O(|V|+|E|) 或 O(n)。

标签: algorithm theory directed-graph directed-acyclic-graphs


【解决方案1】:

ShuggyCoUk 给出的解决方案是不完整的,因为它可能不会检查所有节点。


def isDAG(nodes V):
    while there is an unvisited node v in V:
        bool cycleFound = dfs(v)
        if cyclefound:
            return false
    return true

这具有 O(n+m) 或 O(n^2) 的时间复杂度

【讨论】:

  • 我的确实不正确 - 我删除了它,所以你的现在似乎有点脱离上下文
  • O(n+m)
  • @Artru O(n^2) 使用邻接矩阵时,O(n + m) 使用邻接列表表示图。
  • 嗯...m = O(n^2) 因为完整的图正好有m=n^2 边。这就是O(n+m) = O(n + n^2) = O(n^2)
【解决方案2】:

进行简单的深度优先搜索不够找到循环。在一个 DFS 中可以多次访问一个节点而不存在循环。根据您从哪里开始,您也可能不会访问整个图表。

您可以按如下方式检查图的连通分量中的循环。找到一个只有出边的节点。如果没有这样的节点,那么就有一个循环。在该节点上启动 DFS。遍历每条边时,检查边是否指向堆栈中已经存在的节点。这表明存在一个循环。如果您没有找到这样的边,则该连接组件中没有循环。

正如 Rutger Prins 指出的,如果你的图没有连接,你需要对每个连接的组件重复搜索。

作为参考,Tarjan's strongly connected component algorithm 密切相关。它还将帮助您找到周期,而不仅仅是报告它们是否存在。

【讨论】:

  • 顺便说一句:出于显而易见的原因,“指向已经在堆栈上的节点”的边在文献中通常称为“后边”。是的,这可能比对图进行拓扑排序更简单,尤其是在您无论如何都需要进行 DFS 时。
  • 为了使图是非循环的,您说每个连接的组件必须包含一个只有出边的节点。您能否推荐一种算法来查找有向图的连通分量(相对于“强”连通分量),以供您的主要算法使用?
  • @kostmo,如果图有多个连通分量,那么您将不会访问第一个 DFS 中的所有节点。跟踪您访问过的节点,并使用未访问过的节点重复该算法,直到您到达所有节点。这基本上就是连接组件算法的工作原理。
  • 虽然此答案的意图是正确的,但如果使用基于堆栈的 DFS 实现,答案会令人困惑:用于实现 DFS 的堆栈将不包含要测试的正确元素。有必要向用于跟踪祖先节点集的算法添加一个额外的堆栈。
  • 我对您的回答有多个问题。我把它们贴在这里:stackoverflow.com/questions/37582599/…
【解决方案3】:

我会尝试sort the graph topologically,如果你不能,那么它有循环。

【讨论】:

  • 这怎么没有票??它在节点 + 边上是线性的,远远优于 O(n^2) 解决方案!
  • 在许多情况下,DFS(参见 J.Conrod 的回答)可能更容易,尤其是在无论如何都需要执行 DFS 的情况下。但这当然取决于上下文。
  • 拓扑排序将处于无限循环中,但它不会告诉我们循环发生在哪里......
  • 也许它现在已经很老了,但是你标记在 DFS 期间访问的顶点的方式可以告诉你图形是否包含循环。如果顶点在自上而下被访问,则将已访问标记为打开,并在自下而上时将其标记为关闭。如果访问一个开放的顶点,则意味着该图包含一个循环,否则不。
【解决方案4】:

Introduction to Algorithms(第二版)一书上的引理 22.11 指出:

有向图 G 是非循环的当且仅当 G 的深度优先搜索没有产生后边

【讨论】:

【解决方案5】:

我知道这是一个老话题,但对于未来的搜索者来说,这里是我创建的 C# 实现(没有声称它是最有效的!)。这旨在使用一个简单的整数来标识每个节点。您可以根据自己的喜好进行装饰,只要您的节点对象正确地散列和等于。

对于非常深的图,这可能会有很高的开销,因为它会在每个深度节点处创建一个哈希集(它们在广度上被破坏)。

您输入要从中搜索的节点以及到达该节点的路径。

  • 对于具有单个根节点的图,您发送该节点和一个空哈希集
  • 对于具有多个根节点的图,您可以将其包装在这些节点上的 foreach 中,并为每次迭代传递一个新的空哈希集
  • 当检查任何给定节点下的循环时,只需将该节点与一个空哈希集一起传递

    private bool FindCycle(int node, HashSet<int> path)
    {
    
        if (path.Contains(node))
            return true;
    
        var extendedPath = new HashSet<int>(path) {node};
    
        foreach (var child in GetChildren(node))
        {
            if (FindCycle(child, extendedPath))
                return true;
        }
    
        return false;
    }
    

【讨论】:

    【解决方案6】:

    这是我对 peel off leaf node algorithm 的 ruby​​ 实现。

    def detect_cycles(initial_graph, number_of_iterations=-1)
        # If we keep peeling off leaf nodes, one of two things will happen
        # A) We will eventually peel off all nodes: The graph is acyclic.
        # B) We will get to a point where there is no leaf, yet the graph is not empty: The graph is cyclic.
        graph = initial_graph
        iteration = 0
        loop do
            iteration += 1
            if number_of_iterations > 0 && iteration > number_of_iterations
                raise "prevented infinite loop"
            end
    
            if graph.nodes.empty?
                #puts "the graph is without cycles"
                return false
            end
    
            leaf_nodes = graph.nodes.select { |node| node.leaving_edges.empty? }
    
            if leaf_nodes.empty?
                #puts "the graph contain cycles"
                return true
            end
    
            nodes2 = graph.nodes.reject { |node| leaf_nodes.member?(node) }
            edges2 = graph.edges.reject { |edge| leaf_nodes.member?(edge.destination) }
            graph = Graph.new(nodes2, edges2)
        end
        raise "should not happen"
    end
    

    【讨论】:

      【解决方案7】:

      Solution1卡恩算法检查循环。主要思想:维护一个队列,将零度数的节点添加到队列中。然后一个一个地剥离节点,直到队列为空。检查是否存在任何节点的入边。

      Solution2Tarjan 算法检查强连通分量。

      解决方案 3DFS。使用整数数组标记节点的当前状态: 即 0 - 表示该节点之前没有被访问过。 -1 - 表示该节点已被访问,并且其子节点正在被访问。 1 - 表示该节点已被访问,并且已完成。 所以如果一个节点在做DFS的时候状态为-1,就说明一定存在一个循环。

      【讨论】:

        【解决方案8】:

        做DFS时不应该有任何后边。做DFS时要跟踪已经访问过的节点,如果在当前节点和现有节点之间遇到边,则图有循环。

        【讨论】:

          【解决方案9】:

          这里有一个快速代码来查找图表是否有循环:

          func isCyclic(G : Dictionary<Int,Array<Int>>,root : Int , var visited : Array<Bool>,var breadCrumb : Array<Bool>)-> Bool
          {
          
              if(breadCrumb[root] == true)
              {
                  return true;
              }
          
              if(visited[root] == true)
              {
                  return false;
              }
          
              visited[root] = true;
          
              breadCrumb[root] = true;
          
              if(G[root] != nil)
              {
                  for child : Int in G[root]!
                  {
                      if(isCyclic(G,root : child,visited : visited,breadCrumb : breadCrumb))
                      {
                          return true;
                      }
                  }
              }
          
              breadCrumb[root] = false;
              return false;
          }
          
          
          let G = [0:[1,2,3],1:[4,5,6],2:[3,7,6],3:[5,7,8],5:[2]];
          
          var visited = [false,false,false,false,false,false,false,false,false];
          var breadCrumb = [false,false,false,false,false,false,false,false,false];
          
          
          
          
          var isthereCycles = isCyclic(G,root : 0, visited : visited, breadCrumb : breadCrumb)
          

          这个想法是这样的:一个普通的 dfs 算法,它有一个数组来跟踪访问的节点,还有一个额外的数组作为指向当前节点的节点的标记,这样当我们执行 dfs 时对于一个节点,我们将其在标记数组中的对应项设置为真,这样当遇到一个已经访问过的节点时,我们检查其在标记数组中的对应项是否为真,如果它为真,那么它就是让给自己的节点之一 (因此是一个循环),诀窍是每当一个节点的 dfs 返回时,我们将其相应的标记设置回 false ,这样如果我们从另一条路线再次访问它,我们就不会被愚弄。

          【讨论】:

            【解决方案10】:

            刚刚在 Google 面试中提出了这个问题。

            拓扑排序

            你可以尝试拓扑排序,即O(V + E),其中V是顶点数,E是边数。当且仅当可以做到这一点时,有向图才是无环的。

            递归叶删除

            递归删除叶节点,直到没有剩余,如果剩下的节点不止一个,你就有了一个循环。除非我弄错了,否则这是 O(V^2 + VE)。

            DFS 样式 ~ O(n + m)

            然而,一个有效的 DFS 式算法,最坏情况 O(V + E),是:

            function isAcyclic (root) {
                const previous = new Set();
            
                function DFS (node) {
                    previous.add(node);
            
                    let isAcyclic = true;
                    for (let child of children) {
                        if (previous.has(node) || DFS(child)) {
                            isAcyclic = false;
                            break;
                        }
                    }
            
                    previous.delete(node);
            
                    return isAcyclic;
                }
            
                return DFS(root);
            }
            

            【讨论】:

              【解决方案11】:

              您可以在这里https://stackoverflow.com/a/60196714/1763149https://stackoverflow.com/a/60196714/1763149使用我的回答中的查找周期的反转

              def is_acyclic(graph):
                  return not has_cycle(graph)
              

              【讨论】:

                【解决方案12】:

                这是我的伪代码实现:

                bool Acyclacity_Test
                   InitColor() //Sets to WHITE every vertex
                   while there is a node v in V:
                      if (v.color == WHITE) then
                         tmp = Aux_Acy(v);
                         if ( not tmp ) return false
                   return true
                END
                
                bool Aux_Acy(u)
                   u.color = GREY
                   for each node v in Adj(u)
                       if(v.color == GREY) return false
                       else if(v.color == WHITE) tmp = Aux_Acy(v)
                       if(!tmp) return false;
                   u.color = BLACK
                   return true
                END
                

                【讨论】:

                  猜你喜欢
                  • 2011-02-08
                  • 1970-01-01
                  • 1970-01-01
                  • 2021-07-24
                  • 2021-07-28
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多