当前位置:  开发笔记 > 编程语言 > 正文

用于检测有向图中的循环的最佳算法

如何解决《用于检测有向图中的循环的最佳算法》经验,为你挑选了8个好方法。

检测有向图中所有周期的最有效算法是什么?

我有一个有向图表示需要执行的作业计划,作业是节点,依赖是边缘.我需要检测此图中循环的错误情况,从而导致循环依赖.



1> aku..:

Tarjan的强连通组件算法具有O(|E| + |V|)时间复杂性.

对于其他算法,请参阅Wikipedia上的强连接组件.


查找强连接组件如何告诉您图中存在的周期?
@Cedrik对,不是直接的.这不是Tarjan算法的一个缺陷,而是它用于这个问题的方式.Tarjan没有直接找到*cycles*,它找到了强连接的组件.当然,任何大小超过1的SCC都意味着一个循环.非循环组件本身具有单独的SCC.问题是自循环本身也会进入SCC.因此,您需要单独检查自循环,这非常简单.
(图中所有强连接的组件)!=(图中的所有循环)
@Peter,http://stackoverflow.com/questions/546655/finding-all-cycles-in-graph
可能有人可以确认,但Tarjan算法不支持直接指向自身的节点循环,如A-> A.
@ aku:三色DFS也有相同的运行时`O(| E | + | V |)`.使用白色(从未访问过),灰色(访问当前节点但尚未访问所有可到达节点)和黑色(所有可到达节点与当前节点一起访问)颜色编码,如果灰色节点找到另一个灰色节点,那么我们'一个循环.[几乎就是我们在Cormen的算法书中所做的].想知道"Tarjan算法"是否比这样的DFS有任何好处!
Pleasre避免仅限链接的答案.
来自维基百科:"如果每个强连通分量都收缩到一个顶点,则得到的图形是有向无环图,G的凝聚.有向图是非循环的,当且仅当它没有具有多个顶点的强连通子图时,因为有向循环是强连接的,每个非平凡的强连通分量都包含至少一个有向循环."

2> Steve Jessop..:

鉴于这是一份工作计划,我怀疑在某些时候你会将它们分类为一个建议的执行顺序.

如果是这种情况,那么拓扑排序实现可以在任何情况下检测周期.UNIX tsort确实如此.因此,我认为在tsorting的同时检测周期更有效,而不是在单独的步骤中检测周期.

因此,问题可能变成"我如何最有效地进行切割",而不是"我如何最有效地检测循环".答案可能是"使用库",但未能遵循以下维基百科文章:

http://en.wikipedia.org/wiki/Topological_sorting

具有一种算法的伪代码,以及来自Tarjan的另一种算法的简要描述.两者都有O(|V| + |E|)时间复杂性.



3> 小智..:

从DFS开始:当且仅当在DFS期间发现后沿时,才存在循环.这被证明是白路理论的结果.


是的,我认为是一样的,但这还不够,我发布了自己的方式cs.stackexchange.com/questions/7216/find-the-simple-cycles-in-a-directed-graph

4> 小智..:

最简单的方法是对图形进行深度优先遍历(DFT).

如果图形具有n顶点,则这是O(n)时间复杂度算法.由于您可能必须从每个顶点开始执行DFT,因此总复杂度变为O(n^2).

您必须维护一个堆栈,其中包含当前深度首次遍历中的所有顶点,其第一个元素是根节点.如果你在DFT期间遇到一个已经在堆栈中的元素,那么你有一个循环.


对于"常规"图形,这是正确的,但对于*定向*图形则为假.例如,考虑具有四个节点的"菱形依赖图":A的边缘指向B和C,每个边缘都指向D.您从A的DFT遍历此图将错误地断定"循环"是实际上是一个循环 - 虽然有一个循环,但它不是循环,因为它不能通过跟随箭头遍历.
@Deepak--事实上,我误解了"物理向导"的答案:他写的"在堆栈中"我认为"已经找到了".在执行DFT期间检查"堆栈中"的欺骗行为确实足够(用于检测有向循环).每个人都有一个赞成.
@peter你可以解释一下A的DFT如何错误地断定有一个循环吗?

5> 小智..:

在我看来,用于在有向图中检测周期的最易理解的算法是图着色算法.

基本上,图形着色算法以DFS方式遍历图形(深度优先搜索,这意味着它在探索另一条路径之前完全探索了一条路径).当它找到后边缘时,它会将图形标记为包含循环.

有关图形着色算法的深入解释,请阅读本文:http://www.geeksforgeeks.org/detect-cycle-direct-graph-using-colors/

另外,我在JavaScript中提供了图着色的实现https://github.com/dexcodeinc/graph_algorithm.js/blob/master/graph_algorithm.js


为什么这会有投票?如果没有别的,引用是关于主题的并且可能是有用的.

6> Aaron Digull..:

如果无法向节点添加"已访问"属性,请使用集合(或映射),并将所有已访问的节点添加到集合,除非它们已在集合中.使用唯一键或对象的地址作为"键".

这也为您提供了有关循环依赖关系的"根"节点的信息,当用户必须解决问题时,它将派上用场.

另一种解决方案是尝试查找要执行的下一个依赖项.为此,您必须有一些堆栈,您可以记住您现在的位置以及下一步需要做的事情.在执行之前检查该堆栈上是否已存在依赖项.如果是,你找到了一个循环.

虽然这似乎具有O(N*M)的复杂性,但您必须记住堆栈的深度非常有限(因此N很小)并且M变小,每个依赖关系都可以检查为"已执行"加上当你找到一片叶子时你可以停止搜索(所以你永远不必检查每个节点 - > M也会很小).

在MetaMake中,我将图形创建为列表列表,然后在执行时删除每个节点,这自然会减少搜索量.我从来没有必要进行独立检查,这一切都是在正常执行期间自动发生的.

如果您需要"仅测试"模式,只需添加一个"干运行"标志,该标志禁用实际作业的执行.



7> Yuwen..:

没有算法可以在多项式时间内找到有向图中的所有周期.假设有向图有n个节点,每对节点都有相互连接,这意味着你有一个完整的图.因此,这n个节点的任何非空子集指示一个周期,并且存在2 ^ n-1个这样的子集.因此不存在多项式时间算法.因此,假设您有一个有效的(非愚蠢的)算法,可以告诉您图中的定向循环数,您可以先找到强连接组件,然后在这些连接的组件上应用算法.由于循环仅存在于组件内而不存在于组件之间.



8> Kurt Peek..:

根据Cormen等人的引理22.11,算法简介(CLRS):

有向图G仅在深度优先搜索G不产生后边缘时才是非循环的。

在几个答案中已经提到了这一点。在这里,我还将基于CLRS的第22章提供一个代码示例。示例图如下所示。

CLRS的深度优先搜索伪代码为:

在CLRS图22.4中的示例中,该图由两个DFS树组成:一个由节点uvxy组成,另一个由节点wz组成。每棵树都包含一个后边缘:一个从xv,另一个从zz(自环)。

关键的实现是,当在DFS-VISIT函数中遍历的邻居vu,遇到带有GRAY颜色的节点时会遇到后边缘。

以下Python代码是对CLRS伪代码的改编,if其中添加了用于检测周期的子句:

import collections


class Graph(object):
    def __init__(self, edges):
        self.edges = edges
        self.adj = Graph._build_adjacency_list(edges)

    @staticmethod
    def _build_adjacency_list(edges):
        adj = collections.defaultdict(list)
        for edge in edges:
            adj[edge[0]].append(edge[1])
        return adj


def dfs(G):
    discovered = set()
    finished = set()

    for u in G.adj:
        if u not in discovered and u not in finished:
            discovered, finished = dfs_visit(G, u, discovered, finished)


def dfs_visit(G, u, discovered, finished):
    discovered.add(u)

    for v in G.adj[u]:
        # Detect cycles
        if v in discovered:
            print(f"Cycle detected: found a back edge from {u} to {v}.")

        # Recurse into DFS tree
        if v not in discovered and v not in finished:
            dfs_visit(G, v, discovered, finished)

    discovered.remove(u)
    finished.add(u)

    return discovered, finished


if __name__ == "__main__":
    G = Graph([
        ('u', 'v'),
        ('u', 'x'),
        ('v', 'y'),
        ('w', 'y'),
        ('w', 'z'),
        ('x', 'v'),
        ('y', 'x'),
        ('z', 'z')])

    dfs(G)

请注意,在此示例中,time未捕获in CLRS的伪代码,因为我们仅对检测周期感兴趣。还有一些样板代码,用于从边列表中构建图的邻接列表表示。

执行此脚本后,将输出以下输出:

Cycle detected: found a back edge from x to v.
Cycle detected: found a back edge from z to z.

这些正是CLRS图22.4中示例的后边缘。

推荐阅读
手机用户2402851155
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有