【问题标题】:Topological Sorting of a directed acyclic graph有向无环图的拓扑排序
【发布时间】:2014-07-16 20:10:09
【问题描述】:

如何为有向无环图输出所有可能的拓扑排序?例如,给定一个图,其中 V 指向 W 和 X,W 指向 Y 和 Z,X 指向 Z:

V --> W --> Y
      W --> Z
V --> X --> Z

您如何对该图进行拓扑排序以产生所有可能的结果?我能够使用广度优先搜索来获得 V、W、X、Y、Z,并使用深度优先搜索来获得 V、W、Y、Z、X。但无法输出任何其他类型.

【问题讨论】:

  • 做一个正常的拓扑排序。每次迭代都会抓取您可以输出的所有项目,并以所有组合输出它们,然后继续进行下一次迭代。
  • @LasseV.Karlsen:这是一个开始,但比这更复杂。假设在第一次迭代中有 2 个没有入边的顶点 A 和 B。 A 和 B 都对 C 有边,但只有 A 对 D 有边。C 和 D 都没有任何出边。那么 A、D、B、C 是有效的拓扑排序,即使 D 出现在 B 之前,并且无法通过您的方法生成。
  • 请检查我发布的答案,我上面的评论太简单了,我意识到,也意识到我无法通过评论发布足够好的答案。
  • 简而言之,我的评论应该更像:做一个正常的拓扑排序。每次迭代都会抓取您可以输出的所有项目并将它们放入一个列表中,然后将该列表放入一个列表(迭代列表列表)中。然后在完成正常拓扑排序的所有迭代后,对这些列表进行所有排列。但这更难理解,我无法在评论中做到公正,因此我的回答是。
  • @j_random_hacker 没错,我的算法,即使是我在答案中写得更完整的算法,也不适用于该示例,我不知道如何/是否可以修复它。正如我在下面对我的答案的评论中发布的那样,我的算法关注的是找到 a 工作排序顺序,并且不能(很容易)被强制找到 all 排序排列。不确定我的答案是否可以挽救。我会暂时搁置它,但它显然不适用于所有示例,因此我什至不确定它是否在问题本身中找到了示例的所有正确排列。

标签: algorithm sorting graph-algorithm topological-sort


【解决方案1】:

论文"Generating Linear Extensions Fast" by Pruesse and Ruskey 中给出了一种为给定 DAG 生成所有拓扑排序的算法(也就是生成偏序的所有线性扩展)。该算法在输出中有一个线性的摊销运行时间(例如:如果它输出 M 个拓扑排序,它在时间 O(M) 中运行)。

请注意,一般情况下,您无法真正拥有任何运行时相对于输入大小有效的东西,因为输出的大小可能会比输入大得多。例如,一个完全断开的 N 个节点的 DAG 有 N!可能的拓扑排序。

【讨论】:

    【解决方案2】:

    也许可以更快地计算订单数量,但真正生成我能想到的所有订单的唯一方法是使用完整的蛮力递归。 (我说的是“蛮力”,但这仍然比测试所有可能排列的蛮力方法要好得多:)

    基本上,在每一步都有一组顶点剩余(即尚未添加到订单中),并且可以在下一步中安全地添加其中的子集 X。这个子集 X 正是没有来自 S 中顶点的入边的顶点集。

    对于一个给定的部分解 L,它包含一些已经在顺序中的顶点、剩余顶点的集合 S,以及 S 中没有来自 S 中其他顶点的入边的顶点集合 X,调用 Generate(L, X, S) 将生成所有以 L 开头的有效拓扑顺序。

    生成(L,X,S):

    • 如果 X 为空:
      • 要么 L 已经是一个完整的解决方案,在这种情况下它包含所有 n 个顶点并且 S 也是空的,要么原始图包含一个循环。
      • 如果 S 为空:
        • 输出 L 作为解。
      • 否则:
        • 报告存在循环。 (实际上,S 中的所有个顶点都参与了某个循环,尽管可能不止一个。)
    • 否则:
      • 对于 X 中的每个 x:
        • 令 L' 为 L,并在末尾添加 x。
        • 令 X' 为 X\{x} 加上任何在 S 中的顶点中唯一入边来自 x 的顶点。
        • 令 S' = S\{x}。
        • 生成(L',X',S')

    首先,找到所有没有入边的顶点的集合 X 并调用 Generate((), X, V)。因为在“For each”循环中选择的每个 x 都是不同的,所以由该循环的迭代生成的每个部分解 L' 也必须是不同的,因此任何对 Generate() 的调用都不会生成多于一次的解,包括顶部级调用。

    实际上,形成 X' 可以比上面的伪代码更有效地完成:当我们选择 x 时,我们可以从 x 中删除所有出边,但也可以将它们添加到边的临时列表中,并通过跟踪每个顶点的入边总数(例如,在由顶点编号索引的数组中)我们可以有效地检测哪些顶点现在有 0 个入边,因此应该添加到 X'。然后在循环迭代结束时,我们删除的所有边都可以从临时列表中恢复。

    【讨论】:

    • 我参加了一次聚会,并与一位 Google 软件开发人员交谈。他们一致认为,暴力递归是获得所有可能结果的唯一方法。
    【解决方案3】:

    所以这种方法是有缺陷的!不确定它是否可以挽救,我会留下一段时间,如果有人知道如何解决它,要么抓住你能做的,然后发布一个新的回答或编辑我的。

    具体来说,我在评论中的例子上使用了下面的算法,它不会输出给出的例子,所以它显然是有缺陷的。


    我学会了进行拓扑排序的方法如下:

    • 创建一个没有箭头指向的所有元素的列表
    • 创建一个元素字典 -> 数字,这里的元素是原始集合中的任何元素,其中有一个箭头,数字是指向它的元素的数量。
    • 创建一个元素字典 -> 列表,这里的元素是原始集合中带有箭头的任何元素,列表是这些箭头指向的所有元素

    在您的示例中,两个字典和列表将如下所示:

    D1      D2         List
    W: 1    V: W, X    V
    Y: 1    W: Y, Z
    Z: 2    X: Z
    X: 1
    

    然后,开始一个循环,在每次迭代中执行以下操作:

    • 输出列表中的所有元素,这些元素目前没有指向它们的箭头。制作列表的临时副本,并清除列表,为下一次迭代做准备
    • 遍历临时副本,并在字典中查找每个元素(如果存在)元素 -> 列表
    • 对于这些列表中的每个元素,将元素 -> 数字字典中的相应数字减 1(删除 1 箭头)。一旦此处元素的数字达到 0,将该元素添加到列表中(它没有左箭头)
    • 如果列表非空,重做迭代循环

    如果你达到了这一点,并且带有 element -> number 的字典中仍然有任何数字大于 0 的元素(如果你愿意,你可以在上面的迭代中删除元素,一旦它们的数字达到零以使这部分更容易),然后你有一个循环,因为上面的循环不应该在所有箭头都被移除之前终止。

    对于您的示例,每次迭代都会输出以下内容:

    1. V
    2. W, X(第二次迭代输出 W 和 X)
    3. Y, Z

    如果你想知道我是如何得出这个解决方案的,只需使用上面的字典和列表作为起点,一步一步地阅读我的迭代描述。


    现在,具体回答您的问题,如何输出所有组合。 “组合”发挥作用的唯一地方是每次迭代。基本上,您在迭代第一步中输出的所有元素(您制作临时副本的元素)都被认为是“等效的”,并且它们之间的任何内部排序都不会影响拓扑排序。

    所以,这样做:

    • 在迭代的第一点,将这些元素放入一个列表中,然后将其添加到另一个列表中,从而为您提供一个列表列表
    • 这个列表列表现在将包含每个迭代作为一个元素,并且一个元素将是另一个列表,其中包含该迭代中的元素输出
    • 现在,将第一个列表的所有排列与第二个列表的所有排列与第三个列表的所有排列组合起来,以此类推

    这意味着获取这个输出:

    1. V
    2. W,X
    3. Y, Z

    总共给您 1 * 2 * 2 = 4 个排列,您可以将第 1 次迭代的所有排列(即 1)与第 2 次迭代的所有排列(即 2、W、X 和 X, W) 具有第 3 次迭代的所有排列(即 2、Y、Z 和 Z、Y)。

    有效拓扑排序的最终排列列表如下:

    1. V、W、X、Y、Z
    2. V、X、W、Y、Z
    3. V、W、X、Z、Y
    4. V、X、W、Z、Y

    这是评论中的示例:

    A 和 B 没有入边。 A 和 B 都对 C 有边,但只有 A 对 D 有边。C 和 D 都没有出边。

    这给出了:

    A --> C
    A --> D
    B --> C
    

    字典和列表:

    D1     D2        List
    C: 2   A: C, D   A
    D: 1   B: C      B
    

    迭代会输出:

    1. A,B
    2. D,C

    所有排列(2 * 2 = 4):

    1. A、B、D、C
    2. A、B、C、D
    3. B、A、D、C
    4. B、A、C、D

    【讨论】:

    • 我不能指望它,但似乎我错过了一步或忘记了什么,请仔细阅读以上内容,看看是否有缺陷,希望是否可以纠正。
    • 好的,所以我的算法无法输出A, D, B, C。我想我的算法对于查找 a 拓扑排序输出是可行的,而不是 all
    • @lasse-v-karlsen 感谢您的努力,看起来 90% 正确!我知道这可能没有简单的解决方案。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-12-29
    • 2021-07-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多