合并两个 DAG 的高效算法

Posted

技术标签:

【中文标题】合并两个 DAG 的高效算法【英文标题】:Efficient algorithm for merging two DAGs 【发布时间】:2011-05-27 20:09:13 【问题描述】:

我有两个加权 DAG(有向无环图)并且需要将它们合并为一个,因此我可以获得拓扑排序(在某些情况下可能超过两个)。问题是这些图都是非循环的,但可以一起形成一个循环。此外,图表很大(100k+ 节点,500k+ 边)。 有没有一种巧妙的方法来合并图表?一种“一次”遍历所有图的算法同样好。

编辑:

“合并”是指将两个图的所有边和顶点组合在一起(当然保留权重),如果它们不创建循环的话。如果边缘已经存在,我想为它使用更大的权重。

这个想法是,从两个无环图开始应该比之后简单地“修复”结果更有优势(这意味着要找到 NP 难的反馈弧集,所以我想避免这种情况)。

感谢您的建议。

【问题讨论】:

合并是什么意思?请在数学上更加具体 感谢您的回答。我修改了问题以澄清。 创建循环后如何处理还不清楚。 【参考方案1】:

我有一个类似的问题,我是这样解决的:

我将我的 DAG 转换为一个包含节点图(以节点 ID 为键,值是一组节点,可能是一个开始)和边图(以源、目标对为键,值是一组边缘,可能是一个开始)。我称之为规范化。原始图是“根”的集合,每个节点都有一个子节点的集合

然后,我可以通过键合并节点和键合并边缘来将其中两个合并在一起。即:如果节点确实存在,则将新节点转换为现有节点值,如果节点不存在,则添加它。与边缘相同。

这很干净,但没有避免循环。

这是我使用的一些代码(clojure):

(def empty-graph
   :nodes 
    :edges )

(defn merge-graphs
  [a b]
  :nodes (merge-with concat (get a :nodes) (get b :nodes))
   :edges (merge-with concat (get a :edges) (get b :edges)))

(defn normalize-graph
  [graph]
  :nodes (->>
            graph
            (mapcat
              (fn [root]
                (->>
                  root
                  (tree-seq (fn [_] true) (fn [node] (get node :children)))
                  (mapcat
                    (fn [edge]
                      (concat
                        (if-let [source (get edge "source")]
                          [[source [source]]]
                          [])
                        (if-let [target (get edge "target")]
                          [[target [target]]]
                          [])))))))
            (into ))
   :edges (->>
            graph
            (mapcat
              (fn [root]
                (->>
                  root
                  (tree-seq (fn [_] true) (fn [node] (get node :children)))
                  (filter (fn [edge] (and (not (nil? (get edge "source"))) (not (nil? (get edge "target")))))) ;this sucks but is necessary
                  (map
                    (fn [edge]
                      (let [compact (dissoc edge :children)]
                        [[(get edge "source") (get edge "target")] [compact]]))))))
            (into )))

【讨论】:

【参考方案2】:

一个问题是可能有不止一种解决方案。

以 DAG (a,b),(a,c) 和 (b,a),(b,c) 为例。您可以通过两种不同的方式“合并”它们:

    (a,b),(a,c),(b,c) (a,c),(b,a),(b,c)

解决方案的数量随着两个图联合中的循环数组合增长,因此对于您的大图,您可能有很多方法可以“合并”它们。

您是否有一个标准可以帮助您确定哪个 DAG 比另一个“更好”?

【讨论】:

【参考方案3】:

假设两个图的顶点相同,如果不是, 考虑一下

V = V1 U V1

假设您有一个邻接列表。现在对于 V1 和 V2 中的每个顶点 v,您可以按边缘通向的顶点对邻接列表进行排序(如果是 (vertex, weight) 对,则按顶点排序)。这不应该那么昂贵,因为图表很小,它应该是summation degree(v)*log(degree(v)),应该不会那么糟糕。

在此之后,您可以迭代 V1 和 V2 中的所有顶点 v,并对 V1 和 V2 中的 v 的邻接列表进行合并排序。这类似于使用归并排序查找 2 个已排序数组的并集,只是在两个都出现的元素中,您可以选择较大的边。

【讨论】:

以上是关于合并两个 DAG 的高效算法的主要内容,如果未能解决你的问题,请参考以下文章

美团一面:如何高效地将两个有序的数组合并成一个有序数组

美团一面:两个有序的数组,如何高效合并成一个有序数组?

(转)算法大数乘法问题及其高效算法

高效算法求解数独

八皇后问题的两个高效的算法(回溯与递归)

算法如何设计--高效的大数据匹配算法