Как проверить, ацикличен ли ориентированный граф?

Как проверить, ацикличен ли ориентированный граф? А как называется алгоритм? Буду признателен за ссылку.


person nes1983    schedule 24.02.2009    source источник
comment
Еще один случай в пользу того, чтобы как-то исправить неправильные ответы на SO.   -  person Sparr    schedule 25.02.2009
comment
Так что, ммм, меня больше всего интересует время, необходимое, чтобы его найти. Итак, мне просто нужен абстрактный алгоритм.   -  person nes1983    schedule 25.02.2009
comment
вы должны пройти все ребра и проверить все вершины, чтобы нижняя граница была O (| V | + | E |). DFS и BFS имеют одинаковую сложность, но DFS легче кодировать, если у вас есть рекурсия, поскольку она управляет стеком за вас ...   -  person ShuggyCoUk    schedule 25.02.2009
comment
DFS не такой же сложности. Рассмотрим граф с узлами {1 .. N} и ребрами вида {(a, b) | a ‹b}. Этот график ациклический, и все же DFS будет O (n!)   -  person FryGuy    schedule 25.02.2009
comment
DFS никогда не бывает O (n!). Он посещает каждый узел один раз и каждое ребро не более двух раз. Итак, O (| V | + | E |) или O (n).   -  person Jay Conrod    schedule 25.02.2009
comment
Полагаю, мы говорим о двух разных реализациях DFS. Учитывая узлы A, B, C, D и кодировку двух комментариев выше, каким был бы алгоритм, который генерирует следующее? A, AB, ABC, ABCD, ABD, ACD, AD. Он сначала увеличивает глубину, а затем выполняет возврат. В противном случае вы не сможете обнаружить циклы.   -  person FryGuy    schedule 25.02.2009
comment
В моем комментарии к DFS я тупо думал в терминах неориентированных графиков.   -  person ShuggyCoUk    schedule 25.02.2009


Ответы (12)


Я бы попытался отсортировать граф топологически, и если вы не можете, то у него есть циклы .

person FryGuy    schedule 25.02.2009
comment
Как это не было голосов ?? Он линейен по узлам + ребрам, намного превосходит решения O (n ^ 2)! - person Loren Pechtel; 25.02.2009
comment
Во многих случаях DFS (см. Ответ Дж. Конрода) может быть проще, особенно если DFS все равно необходимо выполнить. Но, конечно, это зависит от контекста. - person sleske; 30.09.2009
comment
Топологический порядок будет в бесконечном цикле, но он не скажет нам, где происходит цикл ... - person Baradwaj Aryasomayajula; 03.11.2015
comment
Возможно, сейчас он довольно старый, но то, как вы помечаете вершину, посещенную во время DFS, может сказать вам, содержит ли граф цикл или нет. Если вершина посещается сверху вниз, отметьте посещение как открытую и отметьте ее закрытой при движении снизу вверх. Если вы посещаете открытую вершину, это означает, что граф содержит цикл, в противном случае - нет. - person Jaskirat Singh; 20.05.2021

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

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

Как указывает Рутгер Принс, если ваш граф не связан, вам нужно повторить поиск для каждого связного компонента.

Для справки: сильно связанный компонентный алгоритм Тарьяна тесно связан. Это также поможет вам найти циклы, а не только сообщить, существуют ли они.

person Jay Conrod    schedule 25.02.2009
comment
Кстати: ребро, которое указывает на узел, уже находящийся в вашем стеке, в литературе часто называют задним ребром по понятным причинам. И да, это может быть проще, чем топологическая сортировка графа, особенно если вам все равно нужно выполнить DFS. - person sleske; 30.09.2009
comment
Чтобы граф был ацикличным, вы говорите, что каждый компонент связности должен содержать узел только с исходящими ребрами. Можете ли вы порекомендовать алгоритм для поиска компонентов связности (в отличие от компонентов сильной связи) ориентированного графа для использования в вашем основном алгоритме? - person kostmo; 21.09.2010
comment
@kostmo, если граф имеет более одного связного компонента, вы не посетите все узлы в своей первой DFS. Следите за узлами, которые вы посетили, и повторяйте алгоритм с непосещенными узлами, пока не дойдете до них всех. По сути, именно так и работает алгоритм связанных компонентов. - person Jay Conrod; 22.09.2010
comment
Хотя цель этого ответа верна, ответ сбивает с толку при использовании реализации DFS на основе стека: стек, используемый для реализации DFS, не будет содержать правильных элементов для тестирования. Необходимо добавить дополнительный стек в алгоритм, используемый для отслеживания набора узлов-предков. - person Theodore Murdock; 03.04.2012
comment
У меня есть несколько вопросов по поводу вашего ответа. Я разместил их здесь: stackoverflow.com/questions/37582599/ - person Ari; 02.06.2016

Лемма 22.11 в Книге Introduction to Algorithms (Второе издание) утверждает, что:

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

person Filipe Miguel Fonseca    schedule 30.05.2009
comment
По сути, это просто сокращенная версия ответа Джея Конрода :-). - person sleske; 30.09.2009
comment
Одна из задач из той же книги, кажется, предполагает наличие | V | временной алгоритм. Здесь дан ответ: stackoverflow.com/questions/526331/ - person Justin; 14.03.2011

Решение1Алгоритм Кана для проверки цикла. Основная идея: поддерживать очередь, в которую будет добавлен узел с нулевой внутренней степенью. Затем снимайте узел один за другим, пока очередь не станет пустой. Проверьте, существуют ли внутренние ребра какого-либо узла.

Решение2: Алгоритм Тарьяна для проверки компонента "Сильно связный".

Решение3: DFS. Используйте целочисленный массив, чтобы пометить текущий статус узла: т.е. 0 - означает, что этот узел ранее не посещался. -1 - означает, что этот узел был посещен, и его дочерние узлы посещаются. 1 - означает, что этот узел был посещен и готово. Итак, если статус узла равен -1 при выполнении DFS, это означает, что должен существовать цикл.

person Chris Su    schedule 19.09.2014

Решение, данное ShuggyCoUk, является неполным, поскольку оно может не проверять все узлы.


def isDAG(nodes V):
    while there is an unvisited node v in V:
        bool cycleFound = dfs(v)
        if cyclefound:
            return false
    return true

Это имеет временную сложность O (n + m) или O (n ^ 2)

person Community    schedule 25.02.2009
comment
мой действительно неверен - я удалил его, поэтому теперь ваш кажется немного не в контексте - person ShuggyCoUk; 25.02.2009
comment
@Artru O (n ^ 2) при использовании матрицы смежности, O (n + m) при использовании списка смежности для представления графа. - person 0x450; 15.06.2016
comment
Гм ... m = O(n^2), поскольку полный граф имеет ровно m=n^2 ребра. Итак, это O(n+m) = O(n + n^2) = O(n^2). - person Alex Reinking; 27.06.2016

Только что задал этот вопрос в интервью Google.

Топологическая сортировка

Вы можете попытаться отсортировать топологически, то есть O (V + E), где V - количество вершин, а E - количество ребер. Ориентированный граф ацикличен тогда и только тогда, когда это возможно.

Рекурсивное удаление листа

Рекурсивно удаляют листовые узлы до тех пор, пока не останется ни одного, и если осталось более одного узла, у вас будет цикл. Если не ошибаюсь, это O (V ^ 2 + VE).

DFS-стиль ~ O (n + m)

Однако эффективный алгоритм DFS в худшем случае O (V + E):

function isAcyclic (root) {
    const previous = new Set();

    function DFS (node) {
        previous.add(node);

        let isAcyclic = true;
        for (let child of children) {
            if (previous.has(node) || DFS(child)) {
                isAcyclic = false;
                break;
            }
        }

        previous.delete(node);

        return isAcyclic;
    }

    return DFS(root);
}
person Tom Golden    schedule 15.08.2018

Я знаю, что это старая тема, но для будущих искателей вот созданная мной реализация C # (не утверждаю, что она наиболее эффективна!). Это предназначено для использования простого целого числа для идентификации каждого узла. Вы можете украсить это, как вам нравится, при условии, что ваши хэши объекта узла и равняются должным образом.

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

Вы вводите узел, из которого хотите искать, и путь к этому узлу.

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

    private bool FindCycle(int node, HashSet<int> path)
    {
    
        if (path.Contains(node))
            return true;
    
        var extendedPath = new HashSet<int>(path) {node};
    
        foreach (var child in GetChildren(node))
        {
            if (FindCycle(child, extendedPath))
                return true;
        }
    
        return false;
    }
    
person Matthew    schedule 24.10.2013

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

person santhosh    schedule 28.10.2014

вот быстрый код, чтобы узнать, есть ли у графика циклы:

func isCyclic(G : Dictionary<Int,Array<Int>>,root : Int , var visited : Array<Bool>,var breadCrumb : Array<Bool>)-> Bool
{

    if(breadCrumb[root] == true)
    {
        return true;
    }

    if(visited[root] == true)
    {
        return false;
    }

    visited[root] = true;

    breadCrumb[root] = true;

    if(G[root] != nil)
    {
        for child : Int in G[root]!
        {
            if(isCyclic(G,root : child,visited : visited,breadCrumb : breadCrumb))
            {
                return true;
            }
        }
    }

    breadCrumb[root] = false;
    return false;
}


let G = [0:[1,2,3],1:[4,5,6],2:[3,7,6],3:[5,7,8],5:[2]];

var visited = [false,false,false,false,false,false,false,false,false];
var breadCrumb = [false,false,false,false,false,false,false,false,false];




var isthereCycles = isCyclic(G,root : 0, visited : visited, breadCrumb : breadCrumb)

Идея такова: обычный алгоритм dfs с массивом для отслеживания посещенных узлов и дополнительный массив, который служит маркером для узлов, которые привели к текущему узлу, так что когда бы мы ни выполняли dfs для узла мы устанавливаем соответствующий ему элемент в массиве маркеров как истинный, так что, когда когда-либо встречается уже посещенный узел, мы проверяем, истинен ли соответствующий ему элемент в массиве маркеров, если он истинен, то это один из узлов, который позволяет себе (следовательно, cycle), и фокус в том, что всякий раз, когда dfs узла возвращает, мы устанавливаем его соответствующий маркер обратно в false, чтобы, если мы снова посетили его с другого маршрута, нас не обманули.

person m.eldehairy    schedule 10.01.2015


Вы можете использовать инверсию цикла поиска из моего ответа здесь https://stackoverflow.com/a/60196714/1763149

def is_acyclic(graph):
    return not has_cycle(graph)
person Marcin Raczyński    schedule 12.02.2020

Вот моя реализация в псевдокоде:

bool Acyclacity_Test
   InitColor() //Sets to WHITE every vertex
   while there is a node v in V:
      if (v.color == WHITE) then
         tmp = Aux_Acy(v);
         if ( not tmp ) return false
   return true
END

bool Aux_Acy(u)
   u.color = GREY
   for each node v in Adj(u)
       if(v.color == GREY) return false
       else if(v.color == WHITE) tmp = Aux_Acy(v)
       if(!tmp) return false;
   u.color = BLACK
   return true
END
person AlessandroF    schedule 21.06.2021