Эффективный обход ориентированного ациклического графа c - PullRequest
1 голос
/ 13 февраля 2020

Ввод:

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 from above code

Вывод:

total_time = sum of all individual wait_time //without overlap

Правила:

  • Входной код всегда будет приводить к направленному ациклу c graph
  • Каждый узел имеет некоторое wait_time значение
  • Полный обход графа должен вычислять общую сумму wait_time всего графа
  • Все независимые узлы должны быть пройдены параллельно (или, по крайней мере, расчет времени должен быть таким)
  • Если происходит перекрытие wait_time двух разных узлов, то будет учитываться максимальное значение, и обход узла с меньшим временем будет перейти к следующему независимому узлу
  • Все одиночные и парные узлы будут иметь ровно 1 и 2 входящих и исходящих ребра, кроме корней и листьев (может быть несколько корней и листьев)

Задача 1: Как преобразовать данный входной код в граф, чтобы его можно было обойти ред? (псевдокод или любое руководство может помочь)

Задача 2: Как добиться успешного обхода на основе упомянутых правил?

Предыдущее усилие:

  • Мне удалось отсортировать узлы линейным способом, используя Топологическая сортировка , но я не уверен, как мне достичь этих спецификаций c цели.

  • Я нарисовал этот график на основе своей интуиции из кода.

  • Цифры на краях просто для того, чтобы уточнить, какое число вызывает Зависимость.

  • Я использую python 3

  • Мой предыдущий код, кажется, не имеет никакого смысла в отношении этих проблем, поэтому я не включил это.

1 Ответ

0 голосов
/ 16 февраля 2020
  1. Создание графа

Пусть a, b, c три узла, которые имеют общее свойство / депанданс 0 (для узла 0). Если a обнаружен до b, а b обнаружен до c, край должен быть a->b и b->c.

Чтобы обратиться к вышеуказанному правилу, рассмотрите кэш для каждого обнаруженного ди git. Если узел включает 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"

Теперь, чтобы построить график как «слои», вы можете начать с листьев (layer 0), здесь [1,4],[0,2],[0,3] only. Затем возьмите узлы, выходной край которых связан с любым из листьев, здесь только [1,4]. (потому что [1,3] при ссылке на [3], который является листом, также ссылается на [1,4], который не является. Так исключено). Затем вы построили слой 1.

. При построении слоя n вы можете добавлять только узлы с глубиной к слою 0 к n-1

Более компактный Можно сказать, что это для узла, для вычисления его эксцентриситета (самый длинный путь (здесь к листьям)).

Чтобы напечатать график, как вы это сделали, обратите внимание, что вы можете просто построить узлы с помощью эксцентриситета, что показать «уровень» зависимости (как далеко они находятся от листьев / конца, см. plotByLayers в коде ниже).

Вычисление общего времени

Нам не нужно go к сумме слоев зданий, как указано выше.

Узел может запускаться только тогда, когда все конец его предков.

Когда мы построили график, предки уже определены.

Таким образом, рекурсия

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

Инициализация для root узлов (которые не зависят) и где мы можем просто хранить время ожидания.

nroot.endAt = nroot.wait

Относительно нотации node.id i)x,y. я - это i-й узел, прочитанный на входе. (его true id как-то. 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)
...