拓扑排序有向无环图

时间:2014-07-16 20:10:09

标签: algorithm sorting graph-algorithm topological-sort

如何为有向无环图输出所有可能的拓扑排序?例如,给定一个图表,其中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。但是无法输出任何其他种类。

3 个答案:

答案 0 :(得分:3)

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

请注意,一般来说,由于输出的大小可能比输入大得多,因此实际上您无法拥有任何运行时相对于输入大小有效的内容。例如,N个节点的完全断开的DAG具有N!可能的拓扑排序。

答案 1 :(得分:2)

有可能计算订单的数量更快,但实际上生成我能想到的所有订单的唯一方法就是使用完整的蛮力递归。 (我说"蛮力",但这仍然比测试每种可能的排列的最蛮力的蛮力方法好得多:))

基本上,在每一步都有一组顶点S剩余(即尚未添加到顺序中),并且可以在下一步中安全地添加这些顶点的子集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}加上任何顶点,它们在S中的顶点之间只有来自x。
      • 让S' = S \ {x}。
      • 生成(L',X',S')

为了解决问题,找到没有入边的所有顶点的集合X,并调用Generate((),X,V)。因为每个x都选择了"对于每个"循环不同,每个部分解L'由此循环的迭代生成的也必须是不同的,因此任何对Generate()的调用都不会生成多次解决方案,包括顶级调用。

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

答案 2 :(得分:1)

所以这种方法存在缺陷!不确定是否可以打捞,我会留下一段时间,如果有人能发现如何修复它,要么抓住你所能并发布一个新的答案或编辑我的。

具体来说,我在评论的例子中使用了以下算法,它不会输出给定的例子,因此显然存在缺陷。


我学会做拓扑排序的方法如下:

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

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

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

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

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

如果你达到这一点,那么带有元素的字典 - >数字仍然留有任何元素,数字大于0(如果你愿意,你可以删除元素,因为一旦它们的数字达到零,就会在上面的迭代中去除这些元素,这样你就有了一个循环,因为在删除所有箭头之前,上述循环不应终止。

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

  1. V
  2. W,X(第二次迭代输出W和X)
  3. Y,Z
  4. 如果您想知道我是如何得到这个解决方案的,只需使用上面的词典和列表作为起点,逐步完成我的迭代描述。


    现在,要专门回答您的问题,如何输出所有组合。唯一的地方"组合"每次迭代都会发挥作用。基本上,您在迭代的第一步中输出的所有元素(您制作临时副本的元素)都被视为"等效的"并且这些之间的任何内部排序都不会对拓扑排序产生影响。

    所以,这样做:

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

    这意味着获取此输出:

    1. V
    2. W,X
    3. Y,Z
    4. 总共给出1 * 2 * 2 = 4个排列,你将把第一次迭代(即1)的所有排列与第二次迭代的所有排列组合起来(即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

      5. 以下是评论中的示例:

          

        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
        3. 所有排列(2 * 2 = 4):

          1. A,B,D,C
          2. A,B,C,D
          3. B,A,D,C
          4. B,A,C,D
相关问题