Я ищу простой алгоритм для "сериализации" ориентированного графа. В частности, у меня есть набор файлов с взаимозависимостями в порядке их выполнения, и я хочу найти правильный порядок во время компиляции. Я знаю, что это должно быть довольно распространенным явлением - компиляторы делают это все время, но мой 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). Единственный способ, который я могу видеть, - перевернуть рекурсивный алгоритм в интерактивный с ручным стеком и вручную проверить стек для повторных элементов.
У кого-то есть что-то лучше?