如何检查有向图是否是非循环的?

时间:2009-02-24 22:19:47

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

如何检查有向图是否是非循环的?算法如何调用?我很感激参考。

12 个答案:

答案 0 :(得分:88)

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

答案 1 :(得分:32)

进行简单的深度优先搜索足以找到一个循环。可以在DFS中多次访问节点而不存在循环。根据您的起点,您也可能无法访问整个图表。

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

正如Rutger Prins指出的那样,如果未连接图表,则需要在每个连接的组件上重复搜索。

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

答案 2 :(得分:13)

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

  

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

答案 3 :(得分:8)

解决方案1 ​​检查周期的Kahn算法。主要思想:维护一个队列,其中具有零度的节点将被添加到队列中。然后逐个剥离节点,直到队列为空。检查是否存在任何节点的边缘。

解决方案2 Tarjan算法,以检查强连接组件。

解决方案3 DFS 。使用整数数组标记节点的当前状态: 即0 - 意味着此节点之前未被访问过。      -1 - 表示已访问此节点,并且正在访问其子节点。      1 - 表示已访问此节点,并且已完成。 因此,如果在执行DFS时节点的状态为-1,则意味着必须存在循环。

答案 4 :(得分: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)

答案 5 :(得分:1)

我知道这是一个古老的话题,但对于未来的搜索者来说,这是我创建的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 :(得分:1)

在执行DFS时不应该有任何后沿。在执行DFS时跟踪已访问的节点,如果在当前节点和现有节点之间遇到边缘,则图形具有循环。

答案 7 :(得分:1)

这是一个快速代码,用于查找图表是否具有周期:

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对于一个节点,我们将标记数组中的相应项设置为true,这样当遇到已经访问过的节点时,我们检查标记数组中的相应项是否为真,如果它是真的那么它是其自身的一个节点 (因此是一个循环),诀窍是每当节点的dfs返回时我们将其相应的标记设置回false,这样如果我们从另一条路线再次访问它,我们就不会被愚弄。

答案 8 :(得分:0)

以下是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

答案 9 :(得分:0)

在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);
}

答案 10 :(得分:0)

您可以在这里https://stackoverflow.com/a/60196714/1763149

使用我的答案中的查找周期倒置
def is_acyclic(graph):
    return not has_cycle(graph)

答案 11 :(得分:0)

这里是我的伪代码实现:

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