简体   繁体   中英

Efficient traversal of a directed acyclic graph

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];

Resulting Tree:

上面代码的结果树

Output:

total_time = sum of all individual wait_time //without overlap

Rules:

  • Input code will always result a directed acyclic graph
  • Each node has some wait_time value
  • A complete graph traversal should calculate the total wait_time of whole graph
  • All independent nodes must be traversed in parallel (or at least time calculation should be in this way)
  • If overlapping of wait_time of two different nodes occur then maximum value will be considered and traversal of a node with lesser time will move to the next independent node
  • All single and paired nodes will have exactly 1 and 2 in-coming and out-going edges except roots and leaves (there can be multiple roots and leaves)

Problem 1: How to convert given input-code into the graph so it can be traversed? (pseudo-code or any kind of guidance might help)

Problem 2: How to achieve the successful traversal based on mentioned rules?

Previous Effort:

  • I was able to sort the nodes in a linear way using Topological sort , but I am not sure how can I achieve these specific goals.

  • I drew this graph based on my intuition from code.

  • Digits on the edges are just to clarify what number is causing the dependency.

  • I am using python 3

  • My previous code does not seem to make any sense in relevance to these problems so I did not include that.

  1. Creating the graph

Let a, b, c three nodes which have a 0 property/depandancy in common (to node 0). If a is discovered before b , and b discovered before c , edge should be a->b and b->c .

To address above rule, consider a cache for every discovered digit. If a node includes 0 , then you cache it as cache[0] . Later on if an other node b includes 0 , you must make an edge from cache[0] to b , and update that cache so that subsequent node including 0 get an edge from b .

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

Now to build the graph as "layers" you can start from the leaves (layer 0 ), here [1,4],[0,2],[0,3] only. Then take the nodes whose output edge link to any of the leaves, here only [1,4] . (because [1,3] while linking to [3] which is a leaf, also links to [1,4] which is not. So excluded). You then have built the layer 1 .

While building layer n, you can only add nodes having deps to layer 0 to n-1

A more compact way of saying this is for a node, to compute its eccentricity (the longest path (here to the leaves)).

To print the graph as you did, notice you can just plot the nodes by eccentricity which would show the "level" of dependancy (how far they are from the leaves/end, see plotByLayers in code below).

  1. Computing the total time

We don't need to go to the hassle of building layers as above.

A node can start only when all of its ancestors end.

The ancestors have already been determined when we made the graph.

The recursion is thus

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

Initialization being for the root nodes (which have no dependancy) and where we can just keep the wait time.

nroot.endAt = nroot.wait

Regarding the node.id notation i)x,y . i is the ith node as read in input. (its true id somehow. x,y is just garbage to help us visualize).

 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)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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