Сериализация графиков

Я ищу простой алгоритм для "сериализации" ориентированного графа. В частности, у меня есть набор файлов с взаимозависимостями в порядке их выполнения, и я хочу найти правильный порядок во время компиляции. Я знаю, что это должно быть довольно распространенным явлением - компиляторы делают это все время, но мой google-fu сегодня слаб. Какой алгоритм "идти" для этого?

Ответ 1

Топологическая сортировка (из Википедии):

В теории графов топологическая сортировка или топологическое упорядочение направленного ациклический граф (DAG) является линейным упорядочение его узлов, в которых каждый node происходит перед всеми узлами, к которым он имеет исходящие ребра. Каждая группа один или несколько топологических сортов.

Псевдокод:

L ← Empty list where we put the sorted elements
Q ← Set of all nodes with no incoming edges
while Q is non-empty do
    remove a node n from Q
    insert n into L
    for each node m with an edge e from n to m do
        remove edge e from the graph
        if m has no other incoming edges then
            insert m into Q
if graph has edges then
    output error message (graph has a cycle)
else 
    output message (proposed topologically sorted order: L)

Ответ 2

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

Пока это DAG, этот простой шаг на основе стека должен быть тривиальным.

Ответ 3

Если график содержит циклы, как могут существовать допустимые заказы на выполнение для ваших файлов? Мне кажется, что если граф содержит циклы, то у вас нет решения, и это корректно сообщается указанным выше алгоритмом.

Ответ 4

Я придумал довольно наивный рекурсивный алгоритм (псевдокод):

Map<Object, List<Object>> source; // map of each object to its dependency list
List<Object> dest; // destination list

function resolve(a):
    if (dest.contains(a)) return;
    foreach (b in source[a]):
        resolve(b);
    dest.add(a);

foreach (a in source):
    resolve(a);

Самая большая проблема заключается в том, что он не имеет возможности обнаруживать циклические зависимости - он может перейти в бесконечную рекурсию (т.е. переполнение стека; -p). Единственный способ, который я могу видеть, - перевернуть рекурсивный алгоритм в интерактивный с ручным стеком и вручную проверить стек для повторных элементов.

У кого-то есть что-то лучше?