繁体   English   中英

有向无环图的高效遍历

[英]Efficient traversal of a directed acyclic graph

输入:

nd[0];
nd[1, 2];
nd[3, 4];
nd[0, 1];
nd[2, 3];
nd[0, 4];
nd[1, 3];
nd[0, 2];
nd[1, 4];
nd[3];
nd[1, 4];

结果树:

上面代码的结果树

输出:

total_time = sum of all individual wait_time //without overlap

规则:

  • 输入代码总是会产生一个有向无环图
  • 每个节点都有一些wait_time
  • 一次完整的图遍历应该计算整个图的总wait_time
  • 所有独立节点必须并行遍历(或者至少时间计算应该是这样)
  • 如果两个不同节点的wait_time发生重叠,则将考虑最大值,并且遍历时间较短的节点将移动到下一个独立节点
  • 除了根和叶(可以有多个根和叶)之外,所有单个和成对的节点都将恰好有 1 条和 2 条传入和传出边

问题 1:如何将给定的输入代码转换为图形以便遍历? (伪代码或任何类型的指导可能会有所帮助)

问题二:如何根据上述规则实现成功遍历?

以前的努力:

  • 我能够使用Topological sort以线性方式对节点进行排序,但我不确定如何实现这些特定目标。

  • 我根据代码的直觉绘制了这张图。

  • 边缘上的数字只是为了澄清导致依赖的数字。

  • 我正在使用 python 3

  • 我以前的代码似乎与这些问题没有任何意义,所以我没有包含它。

  1. 创建图表

a, b, c三个节点,它们共同具有0属性/依赖关系(到节点 0)。 如果ab之前被发现, bc之前被发现,则边应该是a->bb->c

为了解决上述规则,请考虑为每个发现的数字缓存。 如果节点包含0 ,则将其缓存为cache[0] 稍后,如果其他节点b包含0 ,则必须从cache[0]b一条边,并更新该缓存,以便包含0后续节点从b获得一条边。

foreach input as node
  forall dependancy of node
    if discovered[dependancy]
      make_edge(discovered[dependancy], node)
    fi
    discovered[dependancy] = node // apply dependancy "transitivity"

现在要将图形构建为“层”,您可以从叶子(层0 )开始,这里仅[1,4],[0,2],[0,3] 然后取其输出边链接到任何叶子的节点,这里只有[1,4] (因为[1,3]在链接到[3]是叶子的同时,也链接到[1,4]不是。所以排除了)。 然后,您已经构建了第1层。

在构建第 n 层时,您只能将具有 deps 的节点添加到第 0 层到 n-1 层

一种更紧凑的说法是对于一个节点,计算它的离心率(最长的路径(这里是叶子))。

要像您一样打印图形,请注意您可以通过偏心率绘制节点,这将显示依赖的“级别”(它们离叶子/末端有多远,请参阅下面代码中的plotByLayers )。

  1. 计算总时间

我们不需要像上面那样去构建层的麻烦。

一个节点只有在它的所有祖先都结束时才能开始。

当我们制作图表时,祖先已经确定了。

递归是这样的

n.endAt = max(n.endAt for n in n.ancestors) + n.wait

初始化是针对根节点(没有依赖关系)以及我们可以保持等待时间的地方。

nroot.endAt = nroot.wait

关于 node.id 符号i)x,y i 是在输入中读取的第 i 个节点。 (不知何故x,y它的真实id。x x,y只是帮助我们形象化的垃圾)。

 const input = ` nd[0]; nd[1, 2]; nd[3, 4]; nd[0, 1]; nd[2, 3]; nd[0, 4]; nd[1, 3]; nd[0, 2]; nd[1, 4]; nd[3]; nd[1, 4];` function makeGraph(input) { const nodes = input.trim().split('\\n').map((x,i) => { const deps = x.match(/\\[(.+)\\]/)[1] return { id: i+')'+deps, v: deps.split(',').map(x => x.trim()) } }) const edges = {} const discovered = {} nodes.forEach((node, i) => { node.v.forEach(digit => { if (discovered[digit]) { // there is an edge edges[discovered[digit].id] = edges[discovered[digit].id] || [] edges[discovered[digit].id].push(node) } // apply transitivity a->b->c discovered[digit] = node }) }) return {nodes, edges} } function computeEccentricity(n, edges) { if (typeof(n.eccentricity) !== 'undefined') { return n.eccentricity } if (!edges[n.id]) {// a leaf has no outgoing edges n.eccentricity = 0 return 0 } const distances = Object.values(edges[n.id]).map(n => computeEccentricity(n, edges)) const min = Math.max(...distances) n.eccentricity = 1 + min return n.eccentricity } function plotByLayers(nodes) { const lvls = [] let m = 0; nodes.forEach(n => { const i = n.eccentricity lvls[i] = lvls[i] || [] lvls[i].push(n) m = i > m ? i: m }) for(let i = m; i >=0 ; --i) { console.log(lvls[i].map(x => x.id)) } return lvls } const { nodes, edges } = makeGraph(input) nodes.forEach(n => computeEccentricity(n, edges)) plotByLayers(nodes) // for any node, compute its ancestors. nodes.forEach((n, i) => { if (edges[n.id]) { edges[n.id].forEach(v => { v.ancestors = v.ancestors || [] v.ancestors.push(n) }) } n.wait = 2**i // say arbitrarily that i node waits 2^i some time }) function computeEndAt(node) { if (typeof(node.endAt) !== 'undefined') { return node.endAt } if (!node.ancestors) { node.endAt = 0 + node.wait return node.endAt } const maxEnd = Math.max(...node.ancestors.map(computeEndAt)) node.endAt = maxEnd + node.wait return node.endAt } nodes.forEach(computeEndAt) const longest = nodes.sort((a,b)=>b.endAt - a.endAt)[0] console.log('awaited: ', longest.endAt, longest.id)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM