如何为有向无环图输出所有可能的拓扑排序?例如,给定一个图表,其中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。但是无法输出任何其他种类。
答案 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开头的所有有效拓扑命令。
为了解决问题,找到没有入边的所有顶点的集合X,并调用Generate((),X,V)。因为每个x都选择了"对于每个"循环不同,每个部分解L'由此循环的迭代生成的也必须是不同的,因此任何对Generate()的调用都不会生成多次解决方案,包括顶级调用。
在实践中,形成X'可以比上面的伪代码更有效地完成:当我们选择x时,我们可以删除x中的所有外边缘,但也可以将它们添加到临时边缘列表中,并通过跟踪每个顶点的内边缘总数(例如,在由顶点编号索引的数组中),我们可以有效地检测哪些顶点现在具有0个边缘,因此应该添加到X'。然后在循环迭代结束时,我们删除的所有边都可以从临时列表中恢复。
答案 2 :(得分:1)
所以这种方法存在缺陷!不确定是否可以打捞,我会留下一段时间,如果有人能发现如何修复它,要么抓住你所能并发布一个新的答案或编辑我的。
具体来说,我在评论的例子中使用了以下算法,它不会输出给定的例子,因此显然存在缺陷。
我学会做拓扑排序的方法如下:
在您的示例中,两个词典和列表将如下所示:
D1 D2 List
W: 1 V: W, X V
Y: 1 W: Y, Z
Z: 2 X: Z
X: 1
然后,启动一个循环,在每次迭代中执行以下操作:
如果你达到这一点,那么带有元素的字典 - >数字仍然留有任何元素,数字大于0(如果你愿意,你可以删除元素,因为一旦它们的数字达到零,就会在上面的迭代中去除这些元素,这样你就有了一个循环,因为在删除所有箭头之前,上述循环不应终止。
对于您的示例,每次迭代都会输出以下内容:
如果您想知道我是如何得到这个解决方案的,只需使用上面的词典和列表作为起点,逐步完成我的迭代描述。
现在,要专门回答您的问题,如何输出所有组合。唯一的地方"组合"每次迭代都会发挥作用。基本上,您在迭代的第一步中输出的所有元素(您制作临时副本的元素)都被视为"等效的"并且这些之间的任何内部排序都不会对拓扑排序产生影响。
所以,这样做:
这意味着获取此输出:
总共给出1 * 2 * 2 = 4个排列,你将把第一次迭代(即1)的所有排列与第二次迭代的所有排列组合起来(即2,W,X和X, W)具有第3次迭代的所有排列(即2,Y,Z和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
迭代会输出:
所有排列(2 * 2 = 4):