Лучший алгоритм обнаружения циклов в ориентированном графе - PullRequest
358 голосов
/ 04 ноября 2008

Какой самый эффективный алгоритм для обнаружения всех циклов в ориентированном графе?

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

Ответы [ 14 ]

182 голосов
/ 04 ноября 2008

Алгоритм сильной связи Тарьяна имеет O(|E| + |V|) сложность времени.

Другие алгоритмы см. Сильно связанные компоненты в Википедии.

67 голосов
/ 04 ноября 2008

Учитывая, что это график заданий, я подозреваю, что в какой-то момент вы собираетесь отсортировать их в предлагаемом порядке выполнения.

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

Таким образом, вопрос может звучать так: «Как мне наиболее эффективно выполнять сортировку», а не «как наиболее эффективно обнаруживать петли». На что ответ, вероятно, «использовать библиотеку», но не в том, что следующая статья в Википедии:

http://en.wikipedia.org/wiki/Topological_sorting

имеет псевдокод для одного алгоритма и краткое описание другого от Тарьяна. Оба имеют O(|V| + |E|) сложность времени.

29 голосов
/ 30 декабря 2010

Начать с DFS: цикл существует тогда и только тогда, когда во время DFS обнаружен обратный фронт . Это доказано в результате теории белого пути.

25 голосов
/ 21 апреля 2009

Самый простой способ сделать это - сделать первый проход по глубине (DFT) графика .

Если граф имеет n вершин, это алгоритм O(n) временной сложности. Поскольку вам, возможно, придется выполнять ДПФ, начиная с каждой вершины, общая сложность становится равной O(n^2).

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

21 голосов
/ 26 мая 2016

На мой взгляд, наиболее понятным алгоритмом обнаружения цикла в ориентированном графе является алгоритм раскраски графа.

По сути, алгоритм раскраски графа обходит граф DFS (Поиск в глубину, что означает, что он полностью исследует путь, прежде чем исследовать другой путь). Когда он находит задний край, он помечает график как содержащий цикл.

Для более подробного объяснения алгоритма раскраски графов, пожалуйста, прочитайте эту статью: http://www.geeksforgeeks.org/detect-cycle-direct-graph-using-colors/

Также я предоставляю реализацию раскраски графа в JavaScript https://github.com/dexcodeinc/graph_algorithm.js/blob/master/graph_algorithm.js

7 голосов
/ 04 ноября 2008

Если вы не можете добавить «посещенное» свойство к узлам, используйте набор (или карту) и просто добавьте все посещенные узлы в набор, если они уже не находятся в наборе. Используйте уникальный ключ или адрес объектов в качестве «ключа».

Это также дает вам информацию о «корневом» узле циклической зависимости, которая пригодится, когда пользователь должен будет решить проблему.

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

Хотя это может показаться сложным O (N * M), вы должны помнить, что стек имеет очень ограниченную глубину (поэтому N маленький), и что M становится меньше с каждой зависимостью, которую вы можете отметить как " выполнено "плюс вы можете остановить поиск, когда нашли лист (таким образом, вы никогда не должны проверять каждый узел -> M тоже будет маленьким).

В MetaMake я создал график в виде списка списков, а затем удалял каждый узел, когда выполнял их, что, естественно, сокращало объем поиска. На самом деле мне никогда не приходилось выполнять независимую проверку, все это происходило автоматически во время обычного выполнения.

Если вам нужен режим «только тест», просто добавьте флаг «пробного запуска», который запрещает выполнение реальных заданий.

5 голосов
/ 13 апреля 2013

Не существует алгоритма, который мог бы найти все циклы в ориентированном графе за полиномиальное время. Предположим, у ориентированного графа есть n узлов, и каждая пара узлов имеет связи друг с другом, что означает, что у вас есть полный граф. Таким образом, любое непустое подмножество этих n узлов указывает цикл, и существует 2 ^ n-1 количество таких подмножеств. Таким образом, никакого алгоритма полиномиального времени не существует. Итак, предположим, что у вас есть эффективный (не глупый) алгоритм, который может сообщить вам количество направленных циклов в графе, вы можете сначала найти сильные связанные компоненты, а затем применить свой алгоритм к этим связанным компонентам. Поскольку циклы существуют только внутри компонентов, а не между ними.

3 голосов
/ 12 мая 2013

Если DFS находит ребро, которое указывает на уже посещенную вершину, у вас есть цикл там.

2 голосов
/ 01 января 2019

Согласно лемме 22.11 Кормен и др., Введение в алгоритмы (CLRS):

Направленный граф G является ацикличным тогда и только тогда, когда поиск в G по глубине не дает обратных ребер.

Это было упомянуто в нескольких ответах; здесь я также приведу пример кода, основанного на главе 22 CLRS. Пример графика показан ниже.

enter image description here

Псевдокод CLRS для поиска в глубину гласит:

enter image description here

В примере в CLRS на рисунке 22.4 график состоит из двух деревьев DFS: одно состоит из узлов u , v , x и y и другие узлы w и z . Каждое дерево содержит один задний край: один от x до v и другой от z до z (самоконтроль).

Ключевая реализация состоит в том, что задний фронт встречается, когда в функции DFS-VISIT при переборе соседей v из u встречается узел с цветом GRAY.

Следующий код Python является адаптацией псевдокода CLRS с добавленным предложением if, которое обнаруживает циклы:

import collections


class Graph(object):
    def __init__(self, edges):
        self.edges = edges
        self.adj = Graph._build_adjacency_list(edges)

    @staticmethod
    def _build_adjacency_list(edges):
        adj = collections.defaultdict(list)
        for edge in edges:
            adj[edge[0]].append(edge[1])
        return adj


def dfs(G):
    discovered = set()
    finished = set()

    for u in G.adj:
        if u not in discovered and u not in finished:
            discovered, finished = dfs_visit(G, u, discovered, finished)


def dfs_visit(G, u, discovered, finished):
    discovered.add(u)

    for v in G.adj[u]:
        # Detect cycles
        if v in discovered:
            print(f"Cycle detected: found a back edge from {u} to {v}.")

        # Recurse into DFS tree
        if v not in discovered and v not in finished:
            dfs_visit(G, v, discovered, finished)

    discovered.remove(u)
    finished.add(u)

    return discovered, finished


if __name__ == "__main__":
    G = Graph([
        ('u', 'v'),
        ('u', 'x'),
        ('v', 'y'),
        ('w', 'y'),
        ('w', 'z'),
        ('x', 'v'),
        ('y', 'x'),
        ('z', 'z')])

    dfs(G)

Обратите внимание, что в этом примере псевдокод time в CLRS не фиксируется, поскольку мы заинтересованы только в обнаружении циклов. Существует также некоторый шаблонный код для построения представления списка смежности графа из списка ребер.

Когда этот скрипт выполняется, он печатает следующий вывод:

Cycle detected: found a back edge from x to v.
Cycle detected: found a back edge from z to z.

Это именно задние края в примере в CLRS Рисунок 22.4.

2 голосов
/ 24 января 2013

Я реализовал эту проблему в sml (императивное программирование). Вот схема. Найдите все узлы, которые имеют степень или степень 0. Такие узлы не могут быть частью цикла (поэтому удалите их). Затем удалите все входящие или исходящие ребра из таких узлов. Рекурсивно применить этот процесс к результирующему графу. Если в конце у вас не осталось ни одного узла или ребра, у графа нет циклов, иначе он имеет.

...