Как я могу найти (перебрать) ВСЕ циклы в ориентированном графе из / в данный узел?
Например, я хочу что-то вроде этого:
A->B->A
A->B->C->A
но не: B-> C-> B
algorithm
graph-theory
graph-algorithm
user7305
источник
источник
Ответы:
Я нашел эту страницу в своем поиске, и поскольку циклы не совпадают с сильно связанными компонентами, я продолжил поиск и, наконец, нашел эффективный алгоритм, который перечисляет все (элементарные) циклы ориентированного графа. Это от Дональда Б. Джонсона, и документ можно найти по следующей ссылке:
http://www.cs.tufts.edu/comp/150GA/homeworks/hw1/Johnson%2075.PDF
Реализация Java может быть найдена в:
http://normalisiert.de/code/java/elementaryCycles.zip
Mathematica демонстрация алгоритма Джонсона можно найти здесь , реализация может быть загружена с правой стороны ( «Скачать автор кода» ).
Примечание: на самом деле, есть много алгоритмов для этой проблемы. Некоторые из них перечислены в этой статье:
http://dx.doi.org/10.1137/0205007
Согласно статье, алгоритм Джонсона является самым быстрым.
источник
A->B->C->A
тоже не элементарно?simple_cycle
в networkx.Поиск в глубину с возвратом должен работать здесь. Сохраняйте массив логических значений, чтобы отслеживать, посещали ли вы узел ранее. Если у вас заканчиваются новые узлы для перехода (без попадания на узел, которым вы уже были), просто вернитесь назад и попробуйте другую ветвь.
DFS легко реализовать, если у вас есть список смежности для представления графа. Например, adj [A] = {B, C} указывает, что B и C являются потомками A.
Например, псевдокод ниже. «start» - это узел, с которого вы начинаете.
Вызовите вышеуказанную функцию с начальным узлом:
источник
if (node == start):
- чтоnode and start
в первом звонкеstart
). Он начинается в этой вершине и выполняет DFS, пока не вернется обратно в эту вершину, затем он узнает, что нашел цикл. Но на самом деле он не выводит циклы, просто их количество (но вместо этого модифицировать его, чтобы сделать это, не должно быть слишком сложно).start
. Вам не нужно очищать посещенные флаги, так как каждый посещенный флаг будет очищен из-заvisited[node]=NO;
. Но имейте в виду, что если у вас есть циклA->B->C->A
, вы обнаружите это 3 раза, какstart
и любые 3 из них. Одна идея предотвратить это состоит в том, чтобы иметь другой посещенный массив, в котором установлен каждый узел, который былstart
узлом в какой-то момент, и тогда вы не будете их повторно посещать.Прежде всего - вы не хотите пытаться найти буквально все циклы, потому что, если есть 1, то их бесконечное количество. Например, ABA, ABABA и т. Д. Или может быть возможно объединить 2 цикла в 8-подобный цикл и т. Д. И т. Д. Значимый подход заключается в поиске всех так называемых простых циклов - тех, которые не пересекаются, кроме в начальной / конечной точке. Тогда, если вы хотите, вы можете генерировать комбинации простых циклов.
Один из базовых алгоритмов для нахождения всех простых циклов в ориентированном графе состоит в следующем: Выполните обход в глубину всех простых путей (тех, которые не пересекаются) в графе. Каждый раз, когда текущий узел имеет преемника в стеке, обнаруживается простой цикл. Он состоит из элементов в стеке, начиная с идентифицированного преемника и заканчивая вершиной стека. Первичный обход по глубине всех простых путей аналогичен поиску по глубине, но вы не помечаете / записываете посещенные узлы, кроме тех, которые в данный момент находятся в стеке, как точки остановки.
Приведенный выше алгоритм грубой силы ужасно неэффективен, и в дополнение к этому генерирует несколько копий циклов. Это, однако, отправная точка множества практических алгоритмов, которые применяют различные усовершенствования, чтобы улучшить производительность и избежать дублирования цикла. Я был удивлен, узнав некоторое время назад, что эти алгоритмы не всегда доступны в учебниках и в Интернете. Поэтому я провел некоторое исследование и реализовал 4 таких алгоритма и 1 алгоритм для циклов в неориентированных графах в библиотеке Java с открытым исходным кодом здесь: http://code.google.com/p/niographs/ .
Кстати, поскольку я упомянул неориентированные графы: алгоритм для них другой. Создайте остовное дерево, а затем каждое ребро, которое не является частью дерева, образует простой цикл вместе с некоторыми ребрами в дереве. Найденные таким образом циклы образуют так называемую базу циклов. Затем можно найти все простые циклы, комбинируя 2 или более различных базовых цикла. Для получения более подробной информации смотрите, например, это: http://dspace.mit.edu/bitstream/handle/1721.1/68106/FTL_R_1982_07.pdf .
источник
jgrapht
что используется вhttp://code.google.com/p/niographs/
вас, вы можете взять пример с github.com/jgrapht/jgrapht/wiki/DirectedGraphDemoСамый простой выбор, который я нашел для решения этой проблемы, - это использование Python-библиотеки под названием
networkx
.Он реализует алгоритм Джонсона, упомянутый в лучшем ответе на этот вопрос, но делает его довольно простым для выполнения.
Короче нужно следующее:
Ответ: [['a', 'b', 'd', 'e'], ['a', 'b', 'c']]
источник
nx.DiGraph({'a': ['b'], 'b': ['c','d'], 'c': ['a'], 'd': ['e'], 'e':['a']})
Чтобы уточнить:
Сильно связанные компоненты найдут все подграфы, в которых есть хотя бы один цикл, а не все возможные циклы в графе. например, если вы возьмете все сильно связанные компоненты и свернете / сгруппируете / объедините каждый из них в один узел (т. е. узел на компонент), вы получите дерево без циклов (на самом деле DAG). Каждый компонент (который является в основном подграфом с по крайней мере одним циклом в нем) может содержать внутри себя еще много возможных циклов, поэтому SCC НЕ найдет все возможные циклы, он найдет все возможные группы, которые имеют по крайней мере один цикл, и если вы группируете их, то на графике не будет циклов.
чтобы найти все простые циклы в графе, как уже упоминалось, алгоритм Джонсона является кандидатом.
источник
Однажды мне задали это как вопрос для интервью. Я подозреваю, что это случилось с вами, и вы приходите сюда за помощью. Разбейте проблему на три вопроса, и это станет легче.
Проблема 1) Используйте шаблон итератора, чтобы обеспечить способ итерации результатов маршрута. Хорошее место для размещения логики для получения следующего маршрута - это, вероятно, «moveNext» вашего итератора. Чтобы найти правильный маршрут, это зависит от вашей структуры данных. Для меня это была таблица sql, полная допустимых маршрутов, поэтому мне пришлось создать запрос, чтобы получить действительные пункты назначения с указанием источника.
Задача 2) Толкайте каждый узел, когда вы находите их в коллекции, по мере их получения, это означает, что вы можете увидеть, очень легко ли вы «удваиваете» точку, опрашивая коллекцию, которую вы строите на лету.
Задача 3) Если в какой-то момент вы видите, что удваиваете назад, вы можете вытолкнуть вещи из коллекции и «сделать резервную копию». Затем с этого момента попробуйте снова «двигаться вперед».
Хак: если вы используете Sql Server 2008, есть некоторые новые «иерархические» вещи, которые вы можете использовать, чтобы быстро решить эту проблему, если вы структурируете свои данные в виде дерева.
источник
Варианты на основе DFS с задними краями действительно найдут циклы, но во многих случаях они НЕ будут минимальными циклами. В общем, DFS дает вам флаг, что цикл существует, но он недостаточно хорош для того, чтобы фактически находить циклы. Например, представьте 5 разных циклов, имеющих два ребра. Нет простого способа идентифицировать циклы, используя только DFS (включая варианты возврата).
Алгоритм Джонсона действительно дает все уникальные простые циклы и имеет хорошую временную и пространственную сложность.
Но если вы хотите просто найти МИНИМАЛЬНЫЕ циклы (это означает, что может быть более одного цикла, проходящего через любую вершину, и мы заинтересованы в поиске минимальных циклов) И ваш граф не очень большой, вы можете попробовать использовать простой метод ниже. Это ОЧЕНЬ просто, но довольно медленно по сравнению с Джонсоном.
Так, один из абсолютно простой способ найти МИНИМАЛЬНЫЕ циклов заключается в использовании алгоритма Флойда , чтобы найти минимальные пути между всеми вершинами , используя матрицу смежности. Этот алгоритм далеко не так оптимален, как алгоритм Джонсона, но он настолько прост, и его внутренний цикл настолько узок, что для небольших графов (<= 50-100 узлов) его абсолютно имеет смысл использовать. Временная сложность O (n ^ 3), пространственная сложность O (n ^ 2), если вы используете родительское отслеживание, и O (1), если вы не используете. Прежде всего, давайте найдем ответ на вопрос, существует ли цикл. Алгоритм очень прост. Ниже приведен фрагмент кода в Scala.
Первоначально этот алгоритм работает на графе взвешенных ребер, чтобы найти все кратчайшие пути между всеми парами узлов (отсюда и весовой аргумент). Для правильной работы необходимо указать 1, если между узлами есть направленное ребро, или NO_EDGE в противном случае. После выполнения алгоритма вы можете проверить основную диагональ, если есть значения меньше NO_EDGE, чем этот узел участвует в цикле длины, равной значению. Каждый другой узел того же цикла будет иметь одинаковое значение (на главной диагонали).
Для реконструкции самого цикла нам нужно использовать слегка модифицированную версию алгоритма с родительским отслеживанием.
Родительская матрица изначально должна содержать исходный индекс вершин в граничной ячейке, если между вершинами есть ребро, и -1 в противном случае. После возврата функции для каждого ребра у вас будет ссылка на родительский узел в дереве кратчайшего пути. И тогда легко восстановить реальные циклы.
В общем, у нас есть следующая программа, чтобы найти все минимальные циклы
и маленький основной метод, чтобы проверить результат
и вывод
источник
В случае неориентированного графа недавно опубликованная статья ( Оптимальный список циклов и st-путей в неориентированных графах ) предлагает асимптотически оптимальное решение. Вы можете прочитать его здесь http://arxiv.org/abs/1205.2766 или здесь http://dl.acm.org/citation.cfm?id=2627951 Я знаю, что это не отвечает на ваш вопрос, но так как название в вашем вопросе не указано направление, оно все равно может быть полезным для поиска в Google
источник
Начните с узла X и проверьте все дочерние узлы (родительский и дочерний узлы эквивалентны, если не направлены). Отметьте эти дочерние узлы как дочерние элементы X. От любого такого дочернего узла A отметьте, что его дочерние узлы являются дочерними по отношению к A, X ', где X' помечен как находящийся на расстоянии 2 шагов. Если позже вы нажмете X и отметите его как дочерний элемент X '', это означает, что X находится в цикле из 3 узлов. Возврат к его родителю очень прост (как есть, алгоритм не поддерживает это, поэтому вы можете найти, какой родитель имеет X ').
Примечание. Если график ненаправленный или имеет двунаправленные ребра, этот алгоритм усложняется, если вы не хотите пересекать одно ребро дважды за цикл.
источник
Если вам нужно найти все элементарные схемы на графике, вы можете использовать алгоритм EC Джеймса Тирнана, который был найден в статье с 1970 года.
Очень оригинально алгоритм EC , как мне удалось реализовать в PHP (надеюсь , что нет никаких ошибок приведены ниже). Он также может найти петли, если они есть. Схемы в этой реализации (которые пытаются клонировать оригинал) являются ненулевыми элементами. Ноль здесь означает небытие (ноль, как мы его знаем).
Помимо этого, ниже следует другая реализация, которая дает алгоритму большую независимость, это означает, что узлы могут начинаться откуда угодно, даже с отрицательных чисел, например, -4, -3, -2, и т. Д.
В обоих случаях требуется, чтобы узлы были последовательными.
Возможно, вам придется изучить оригинальную статью, Алгоритм элементарной цепи Джеймса С. Тирнана
тогда это другая реализация, более независимая от графа, без перехода и без значений массива, вместо этого она использует ключи массива, путь, график и схемы хранятся в виде ключей массива (используйте значения массива, если хотите, просто измените необходимые линии). Граф примера начинается с -4, чтобы показать его независимость.
Я проанализировал и задокументировал EC, но, к сожалению, документация на греческом языке.
источник
Есть два шага (алгоритма), участвующих в поиске всех циклов в DAG.
Первым шагом является использование алгоритма Тарьяна для нахождения набора сильно связанных компонентов.
Второй шаг - найти циклы (пути) внутри подключенных компонентов. Мое предложение состоит в том, чтобы использовать модифицированную версию алгоритма Hierholzer.
Идея заключается в следующем:
Вот ссылка на реализацию Java с тестовым примером:
http://stones333.blogspot.com/2013/12/find-cycles-in-directed-graph-dag.html
источник
http://www.me.utexas.edu/~bard/IP/Handouts/cycles.pdf
источник
Я наткнулся на следующий алгоритм, который кажется более эффективным, чем алгоритм Джонсона (по крайней мере, для больших графиков). Однако я не уверен в его производительности по сравнению с алгоритмом Тарьяна.
Кроме того, я проверил только треугольники. Если интересно, см. «Алгоритмы составления списка и подграфа» Норишиге Тиба и Такао Нишизеки ( http://dx.doi.org/10.1137/0214017 )
источник
Решение Javascript с использованием несвязанных наборов связанных списков. Можно модернизировать, чтобы разделить установленные леса для ускорения работы.
источник
DFS с начального узла s, отслеживайте путь DFS во время обхода и записывайте путь, если вы найдете ребро от узла v на пути к s. (v, s) является фоном в дереве DFS и, таким образом, указывает цикл, содержащий s.
источник
Относительно вашего вопроса о цикле перестановок , прочитайте больше здесь: https://www.codechef.com/problems/PCYCLE
Вы можете попробовать этот код (введите размер и номер цифры):
источник
Версия DFS c ++ для псевдокода в ответе второго этажа:
источник