유 방향 그래프에서 모든 사이클 찾기


198

주어진 노드에서 /로 지정된 그래프로 모든 사이클을 어떻게 찾을 수 있습니까?

예를 들어 다음과 같은 것을 원합니다.

A->B->A
A->B->C->A

B-> C-> B


1
내가 생각하는 숙제? me.utexas.edu/~bard/IP/Handouts/cycles.pdf 유효한 질문이 아닙니다 :)
ShuggyCoUk

5
이것은 적어도 NP Hard입니다. 아마도 PSPACE, 나는 그것에 대해 생각해야 할 것이지만, 복잡성 이론으로는 너무 이른 아침 B-)
Brian Postow

2
입력 그래프에 정점과 e 모서리가있는 경우 2 ^ (e-v +1) -1 개의 서로 다른주기가 있습니다 (모두 단순한주기 아니지만 ). 그것은 꽤 많이입니다-당신은 그것들을 모두 명시 적으로 쓰고 싶지 않을 수도 있습니다. 또한 출력 크기가 지수이므로 알고리즘의 복잡성은 다항식 일 수 없습니다. 나는 아직도이 질문에 대한 답이 없다고 생각합니다.
CygnusX1

1
나를위한 나의 최선의 선택은 이것이다 : personal.kent.edu/~rmuhamma/Algorithms/MyAlgorithms/GraphAlgor/…
Melsi

답변:


105

검색에서이 페이지를 찾았고 사이클이 강하게 연결된 구성 요소와 같지 않기 때문에 검색을 계속하고 마침내 유향 그래프의 모든 (기본) 사이클을 나열하는 효율적인 알고리즘을 찾았습니다. 이 문서는 Donald B. Johnson이 작성했으며 다음 링크에서 찾을 수 있습니다.

http://www.cs.tufts.edu/comp/150GA/homeworks/hw1/Johnson%2075.PDF

Java 구현은 다음에서 찾을 수 있습니다.

http://normalisiert.de/code/java/elementaryCycles.zip

Johnson의 알고리즘에 대한 Mathematica 데모는 여기 에서 찾을 수 있으며 , 구현은 오른쪽에서 다운로드 할 수 있습니다 ( "저자 코드 다운로드" ).

참고 : 실제로이 문제에 대한 많은 알고리즘이 있습니다. 이들 중 일부는이 기사에 나열되어 있습니다.

http://dx.doi.org/10.1137/0205007

기사에 따르면 Johnson의 알고리즘이 가장 빠릅니다.


1
나는 종이에서 구현하는 것이 번거 롭다는 것을 알고 궁극적 으로이 민첩성은 여전히 ​​Tarjan의 구현이 필요합니다. 그리고 Java 코드도 끔찍합니다. :(
Gleno

7
@Gleno 글쎄, Tarjan을 사용하여 나머지를 구현하는 대신 그래프에서 모든주기를 찾을 수 있다면 잘못되었습니다. 여기 에서 강력하게 연결된 구성 요소와 모든 사이클의 차이를 볼 수 있습니다 (사이클 cd와 gh는 Tarjan의 alg에 의해 반환되지 않습니다) (@ batbrat 혼란의 답도 여기에 숨겨져 있습니다. 가능한 모든 사이클은 Tarjan의 alg, 그래서 그것의 복잡성은 지수보다 작을 수 있습니다). Java 코드가 더 좋을 수는 있지만 논문에서 구현하는 노력을 아끼지 않았습니다.
eminsenay

4
이 답변은 선택한 답변보다 훨씬 낫습니다. 나는 강하게 연결된 구성 요소에서 모든 간단한 사이클을 얻는 방법을 알아 내려고 꽤 힘들었습니다. 이것은 사소한 것이 아닙니다. Johnson의 논문에는 훌륭한 알고리즘이 포함되어 있지만 다루기가 약간 어렵습니다. Java 구현을 살펴보고 Matlab에서 내 자신을 굴 렸습니다. 코드는 gist.github.com/1260153에 있습니다.
codehippo

5
@ moteutsch : 어쩌면 뭔가 빠졌을 수도 있지만 Johnson 논문 (및 기타 출처)에 따르면 정점 (시작 / 종료를 제외하고)이 두 번 이상 나타나지 않으면 순환이 기본입니다. 그 정의에 따르면 A->B->C->A초등학교도 그렇지 않습니까?
psmears

9
이것을 위해 파이썬을 사용하는 모든 사람을위한 참고 사항 : Johnson 알고리즘은 simple_cyclenetworkx에서 와 같이 구현됩니다 .
Joel

35

역 추적을 통한 깊이있는 첫 번째 검색이 여기에서 작동합니다. 이전에 노드를 방문했는지 여부를 추적하려면 부울 값 배열을 유지하십시오. 새 노드가 부족하여 (이미 노드를 치지 않은 상태로) 이동 한 경우 역 추적하고 다른 분기를 시도하십시오.

그래프를 나타내는 인접 목록이 있으면 DFS를 쉽게 구현할 수 있습니다. 예를 들어 adj [A] = {B, C}는 B와 C가 A의 자식임을 나타냅니다.

예를 들어, 아래의 의사 코드입니다. "start"는 시작한 노드입니다.

dfs(adj,node,visited):  
  if (visited[node]):  
    if (node == start):  
      "found a path"  
    return;  
  visited[node]=YES;  
  for child in adj[node]:  
    dfs(adj,child,visited)
  visited[node]=NO;

시작 노드와 함께 위 함수를 호출하십시오.

visited = {}
dfs(adj,start,visited)

2
감사. 나는 비록 비록 최적이지는 않지만 이해하기 쉽고 합리적인 시간 복잡성을 가지고 있기 때문에 여기에 언급 된 다른 것들에 대한이 접근법을 선호한다.
redcalx

1
이것이 모든주기를 어떻게 찾습니까?
brain storm

3
if (node == start): - node and start첫번째 부름에있는 것
뇌 폭풍

2
@ user1988876 이것은 주어진 꼭짓점 ()과 관련된 모든주기를 찾는 것으로 보입니다 start. 해당 정점에서 시작하여 해당 정점으로 다시 돌아올 때까지 DFS를 수행 한 다음주기를 찾았 음을 알게됩니다. 그러나 실제로 사이클을 출력하지는 않지만 사이클 수를 계산하지만 너무 어렵지 않아야합니다.
Bernhard Barker

1
@ user1988876 글쎄, 그것은 발견 된 사이클의 수와 같은 횟수의 "경로를 찾았다"만 출력한다 (이것은 쉽게 카운트로 대체 될 수있다). 예,의 주기만 감지 start합니다. 로 인해 방문한 각 플래그가 지워 지므로 방문한 플래그를 지울 필요는 없습니다 visited[node]=NO;. 그러나주기가있는 경우 A->B->C->A그 중 3 개와 같이 3 회 감지한다는 점을 명심하십시오 start. 이를 방지하기위한 한 가지 아이디어 start는 특정 시점에 노드 였던 모든 노드 가 설정된 다른 방문 배열을 설정 한 다음이를 다시 방문하지 않는 것입니다.
Bernhard Barker

23

우선-실제로 모든 사이클을 찾으려고하지는 않습니다 .1이 있으면 무한의 수가 있기 때문입니다. 예를 들어 ABA, ABABA 등. 또는 2 사이클을 8와 같은 사이클 등으로 결합하는 것이 가능할 수 있습니다. 의미있는 접근 방식은 소위 단순한 사이클을 찾는 것입니다. 시작 / 종료 지점에서. 그런 다음 원하는 경우 간단한 사이클 조합을 생성 할 수 있습니다.

유 방향 그래프에서 모든 단순 사이클을 찾기위한 기본 알고리즘 중 하나는 다음과 같습니다. 그래프에서 모든 단순 경로 (자체를 가로 지르지 않는 경로)의 깊이 우선 순회를 수행합니다. 현재 노드의 스택에 후속 노드가있을 때마다 간단한주기가 감지됩니다. 이는 식별 된 후속 작업으로 시작하여 스택의 상단으로 끝나는 스택의 요소로 구성됩니다. 모든 단순 경로의 깊이 우선 순회는 깊이 우선 검색과 유사하지만 현재 스택에있는 노드 이외의 방문한 노드를 중지 점으로 표시 / 기록하지 않습니다.

위의 무차별 대입 알고리즘은 매우 비효율적이며 사이클의 여러 복사본을 생성합니다. 그러나 성능을 향상시키고 사이클 복제를 피하기 위해 다양한 개선 사항을 적용하는 것은 여러 가지 실용적인 알고리즘의 출발점입니다. 나는이 알고리즘들이 교과서와 웹에서 쉽게 구할 수 없다는 것을 얼마 전에 알게 된 것에 놀랐다. 그래서 오픈 소스 Java 라이브러리 ( http://code.google.com/p/niographs/) 에서 무 방향 그래프로 4 개의 알고리즘과 1 사이클의 알고리즘을 구현했습니다 .

BTW, 나는 무향 그래프를 언급했기 때문에 : 그 알고리즘은 다릅니다. 스패닝 트리를 구축 한 다음 트리의 일부가 아닌 모든 에지는 트리의 일부 에지와 함께 간단한주기를 형성합니다. 사이클은 이런 방식으로 소위 사이클베이스를 형성합니다. 그런 다음 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
Vishrant

19

이 문제를 해결하기 위해 찾은 가장 간단한 선택은이라는 python lib를 사용하는 것 networkx입니다.

이 질문에 대한 최선의 답변에서 언급 한 Johnson의 알고리즘을 구현하지만 실행이 매우 간단합니다.

간단히 말해 다음이 필요합니다.

import networkx as nx
import matplotlib.pyplot as plt

# Create Directed Graph
G=nx.DiGraph()

# Add a list of nodes:
G.add_nodes_from(["a","b","c","d","e"])

# Add a list of edges:
G.add_edges_from([("a","b"),("b","c"), ("c","a"), ("b","d"), ("d","e"), ("e","a")])

#Return a list of cycles described as a list o nodes
list(nx.simple_cycles(G))

답변 : [[ 'a', 'b', 'd', 'e'], [ 'a', 'b', 'c']]

여기에 이미지 설명을 입력하십시오


1
또한 사전을 networkx 그래프로 cnovert 할 수 있습니다 :nx.DiGraph({'a': ['b'], 'b': ['c','d'], 'c': ['a'], 'd': ['e'], 'e':['a']})
Luke Miles

시작 정점을 어떻게 지정합니까?
nosense

5

명확히하기 위해 :

  1. 강력하게 연결된 구성 요소는 그래프에서 가능한 모든주기가 아니라 하나 이상의주기를 갖는 모든 하위 그래프를 찾습니다. 예를 들어, 강력하게 연결된 모든 구성 요소를 가져 와서 각 구성 요소를 하나의 노드 (예 : 구성 요소 당 노드)로 축소 / 그룹화 / 병합하면 사이클이없는 트리가 생깁니다 (실제로 DAG). 각 구성 요소 (기본적으로 하나 이상의주기를 가진 하위 그래프)는 내부에 더 많은 가능한주기를 포함 할 수 있으므로 SCC는 가능한 모든주기를 찾지 못하고 하나 이상의주기를 가진 가능한 모든 그룹을 찾게됩니다. 그래프에 사이클이 없습니다.

  2. 그래프에서 모든 간단한 사이클 을 찾으려면 Johnson의 알고리즘이 후보입니다.


3

나는 이것을 인터뷰 질문으로 한 번 받았는데, 이것이 당신에게 일어난 것으로 의심되며 당신은 여기에 도움을 요청하고 있습니다. 문제를 세 가지 질문으로 나누면 더 쉬워집니다.

  1. 다음 유효한 경로를 어떻게 결정합니까
  2. 포인트가 사용되었는지 어떻게 알 수 있습니까
  3. 같은 지점을 다시 교차하는 것을 피하는 방법

문제 1) 반복자 패턴을 사용하여 경로 결과를 반복하는 방법을 제공하십시오. 다음 경로를 얻기 위해 논리를 배치하기에 좋은 장소는 아마도 반복자의 "moveNext"일 것입니다. 유효한 경로를 찾으려면 데이터 구조에 따라 다릅니다. 나에게 그것은 유효한 경로 가능성으로 가득 찬 SQL 테이블이므로 소스가 주어진 유효한 목적지를 얻기 위해 쿼리를 작성해야했습니다.

문제 2) 각 노드를 찾을 때 컬렉션에 넣을 때마다 밀어 넣습니다. 즉, 작성중인 컬렉션을 심문하여 특정 지점에 대해 "두 배로 빠르게"돌아가는지 확인할 수 있습니다.

문제 3) 어느 시점에서든, 배가 두 배가되면 컬렉션에서 물건을 꺼내 "백업"할 수 있습니다. 그런 다음 해당 지점부터 다시 "앞으로 이동"하십시오.

해킹 : Sql Server 2008을 사용하는 경우 데이터를 트리에서 구조화하면이를 신속하게 해결하는 데 사용할 수있는 새로운 "계층 구조"가 있습니다.


3

후면 모서리가있는 DFS 기반 변형은 실제로주기를 찾을 수 있지만 대부분의 경우 최소 가 아닙니다. 주기 . 일반적으로 DFS는주기가 있지만 실제로주기를 찾기에 충분하지 않다는 플래그를 제공합니다. 예를 들어, 두 개의 모서리를 공유하는 5 개의 다른 사이클을 상상해보십시오. 역 추적 변형을 포함하여 DFS 만 사용하여주기를 식별하는 간단한 방법은 없습니다.

Johnson의 알고리즘은 실제로 모든 고유 한 간단한주기를 제공하며 시간과 공간이 복잡합니다.

그러나 최소 사이클을 찾으려면 (정점을 통과하는 하나 이상의 사이클이 있고 최소 사이클을 찾는 데 관심이 있음을 의미 함) 그래프가 크지 않은 경우 아래의 간단한 방법을 사용해보십시오. Johnson에 비해 매우 간단하지만 다소 느립니다.

그래서, 하나 절대적으로 MINIMAL 사이클을 찾을 수있는 가장 쉬운 방법은 인접 행렬을 사용하여 모든 정점 사이의 최소한의 경로를 찾기 위해 플로이드의 알고리즘을 사용하는 것입니다. 이 알고리즘은 Johnson만큼 최적의 위치는 아니지만 너무 간단하고 내부 루프가 너무 작아서 작은 그래프 (<= 50-100 노드)의 경우에는 사용하는 것이 좋습니다. 부모 추적을 사용하는 경우 시간 복잡도는 O (n ^ 3), 공간 복잡도 O (n ^ 2), 그렇지 않은 경우 O (1)입니다. 우선주기가 있으면 질문에 대한 답을 찾으십시오. 알고리즘은 매우 간단합니다. 아래는 스칼라의 스 니펫입니다.

  val NO_EDGE = Integer.MAX_VALUE / 2

  def shortestPath(weights: Array[Array[Int]]) = {
    for (k <- weights.indices;
         i <- weights.indices;
         j <- weights.indices) {
      val throughK = weights(i)(k) + weights(k)(j)
      if (throughK < weights(i)(j)) {
        weights(i)(j) = throughK
      }
    }
  }

원래이 알고리즘은 가중치 에지 그래프에서 작동하여 모든 노드 쌍 사이의 모든 최단 경로 (따라서 가중치 인수)를 찾습니다. 제대로 작동하려면 노드 사이에 방향이 지정된 가장자리가 있으면 1을 제공하고 그렇지 않으면 NO_EDGE를 제공해야합니다. 알고리즘이 실행 된 후이 노드가 값과 동일한 길이의주기에 참여하는 것보다 NO_EDGE보다 작은 값이 있으면 기본 대각선을 확인할 수 있습니다. 동일한주기의 다른 모든 노드는 동일한 값을 갖습니다 (주 대각선).

사이클 자체를 재구성하려면 부모 추적과 함께 약간 수정 된 알고리즘 버전을 사용해야합니다.

  def shortestPath(weights: Array[Array[Int]], parents: Array[Array[Int]]) = {
    for (k <- weights.indices;
         i <- weights.indices;
         j <- weights.indices) {
      val throughK = weights(i)(k) + weights(k)(j)
      if (throughK < weights(i)(j)) {
        parents(i)(j) = k
        weights(i)(j) = throughK
      }
    }
  }

부모 행렬은 처음에 꼭짓점 사이에 가장자리가 있으면 가장자리 셀에 소스 꼭짓점 인덱스를 포함해야하며, 그렇지 않으면 -1입니다. 함수가 반환 된 후 각 모서리에 대해 최단 경로 트리에서 상위 노드를 참조하게됩니다. 그런 다음 실제주기를 쉽게 복구 할 수 있습니다.

우리는 모든 최소한의 사이클을 찾기 위해 다음과 같은 프로그램을 가지고 있습니다.

  val NO_EDGE = Integer.MAX_VALUE / 2;

  def shortestPathWithParentTracking(
         weights: Array[Array[Int]],
         parents: Array[Array[Int]]) = {
    for (k <- weights.indices;
         i <- weights.indices;
         j <- weights.indices) {
      val throughK = weights(i)(k) + weights(k)(j)
      if (throughK < weights(i)(j)) {
        parents(i)(j) = parents(i)(k)
        weights(i)(j) = throughK
      }
    }
  }

  def recoverCycles(
         cycleNodes: Seq[Int], 
         parents: Array[Array[Int]]): Set[Seq[Int]] = {
    val res = new mutable.HashSet[Seq[Int]]()
    for (node <- cycleNodes) {
      var cycle = new mutable.ArrayBuffer[Int]()
      cycle += node
      var other = parents(node)(node)
      do {
        cycle += other
        other = parents(other)(node)
      } while(other != node)
      res += cycle.sorted
    }
    res.toSet
  }

결과를 테스트하는 작은 주요 방법

  def main(args: Array[String]): Unit = {
    val n = 3
    val weights = Array(Array(NO_EDGE, 1, NO_EDGE), Array(NO_EDGE, NO_EDGE, 1), Array(1, NO_EDGE, NO_EDGE))
    val parents = Array(Array(-1, 1, -1), Array(-1, -1, 2), Array(0, -1, -1))
    shortestPathWithParentTracking(weights, parents)
    val cycleNodes = parents.indices.filter(i => parents(i)(i) < NO_EDGE)
    val cycles: Set[Seq[Int]] = recoverCycles(cycleNodes, parents)
    println("The following minimal cycle found:")
    cycles.foreach(c => println(c.mkString))
    println(s"Total: ${cycles.size} cycle found")
  }

출력은

The following minimal cycle found:
012
Total: 1 cycle found

2

무향 그래프 의 경우 , 최근에 출판 된 논문 ( 무향 그래프에서 최적의 사이클 및 st- 경로 목록 )은 무증상 최적 솔루션을 제공합니다. http://arxiv.org/abs/1205.2766 또는 http://dl.acm.org/citation.cfm?id=2627951 에서 읽을 수 있지만 질문에 대한 답변이 아니라는 것을 알고 있습니다. 질문에 방향이 언급되지 않았지만 Google 검색에 여전히 유용 할 수 있습니다.


1

노드 X에서 시작하여 모든 하위 노드를 확인하십시오 (방향이 지정되지 않은 경우 상위 및 하위 노드가 동일 함). 해당 자식 노드를 X의 자식으로 표시합니다. 그러한 자식 노드 A에서 A, X '의 자식임을 표시합니다. 여기서 X'는 2 단계 떨어져있는 것으로 표시됩니다.) 나중에 X를 누르고 X ''의 자식으로 표시하면 X가 3 노드 사이클에 있음을 의미합니다. 부모의 역 추적은 쉽습니다 (있는 그대로 알고리즘은 이것을 지원하지 않으므로 X '가있는 부모를 찾을 수 있습니다).

참고 : 그래프가 방향이 없거나 양방향 모서리가있는 경우 사이클 동안 동일한 모서리를 두 번 통과하지 않으려는 경우이 알고리즘이 더 복잡해집니다.


1

그래프에서 모든 기본 회로를 찾는 것이 1970 년 이후 논문에서 발견 된 JAMES C. TIERNAN의 EC 알고리즘을 사용할 수 있습니다.

매우 원래 나는 (희망 실수는 다음과 같습니다 거기 없음) PHP에서 그것을 구현하는 관리로 EC 알고리즘. 루프가 있으면 루프도 찾을 수 있습니다. 이 구현의 회로 (원본을 복제하려고 시도하는 회로)는 0이 아닌 요소입니다. 여기에 0은 존재하지 않는 것을 나타냅니다 (우리가 아는 한 null).

아래의 것 외에도 알고리즘에 더 독립성을 제공하는 다른 구현이 뒤 따릅니다. 즉, 노드는 음수 (예 : -4, -3, -2 등) 등 어디에서나 시작할 수 있습니다.

두 경우 모두 노드가 순차적이어야합니다.

James C. Tiernan Elementary Circuit Algorithm 원본을 공부해야 할 수도 있습니다 .

<?php
echo  "<pre><br><br>";

$G = array(
        1=>array(1,2,3),
        2=>array(1,2,3),
        3=>array(1,2,3)
);


define('N',key(array_slice($G, -1, 1, true)));
$P = array(1=>0,2=>0,3=>0,4=>0,5=>0);
$H = array(1=>$P, 2=>$P, 3=>$P, 4=>$P, 5=>$P );
$k = 1;
$P[$k] = key($G);
$Circ = array();


#[Path Extension]
EC2_Path_Extension:
foreach($G[$P[$k]] as $j => $child ){
    if( $child>$P[1] and in_array($child, $P)===false and in_array($child, $H[$P[$k]])===false ){
    $k++;
    $P[$k] = $child;
    goto EC2_Path_Extension;
}   }

#[EC3 Circuit Confirmation]
if( in_array($P[1], $G[$P[$k]])===true ){//if PATH[1] is not child of PATH[current] then don't have a cycle
    $Circ[] = $P;
}

#[EC4 Vertex Closure]
if($k===1){
    goto EC5_Advance_Initial_Vertex;
}
//afou den ksana theoreitai einai asfales na svisoume
for( $m=1; $m<=N; $m++){//H[P[k], m] <- O, m = 1, 2, . . . , N
    if( $H[$P[$k-1]][$m]===0 ){
        $H[$P[$k-1]][$m]=$P[$k];
        break(1);
    }
}
for( $m=1; $m<=N; $m++ ){//H[P[k], m] <- O, m = 1, 2, . . . , N
    $H[$P[$k]][$m]=0;
}
$P[$k]=0;
$k--;
goto EC2_Path_Extension;

#[EC5 Advance Initial Vertex]
EC5_Advance_Initial_Vertex:
if($P[1] === N){
    goto EC6_Terminate;
}
$P[1]++;
$k=1;
$H=array(
        1=>array(1=>0,2=>0,3=>0,4=>0,5=>0),
        2=>array(1=>0,2=>0,3=>0,4=>0,5=>0),
        3=>array(1=>0,2=>0,3=>0,4=>0,5=>0),
        4=>array(1=>0,2=>0,3=>0,4=>0,5=>0),
        5=>array(1=>0,2=>0,3=>0,4=>0,5=>0)
);
goto EC2_Path_Extension;

#[EC5 Advance Initial Vertex]
EC6_Terminate:
print_r($Circ);
?>

그런 다음 이동과 배열 값이없는 그래프와 더 독립적 인 다른 구현입니다. 대신 배열 키를 사용합니다. 경로, 그래프 및 회로가 배열 키로 저장됩니다 (원하는 경우 배열 값을 사용하십시오) 윤곽). 예제 그래프는 -4에서 시작하여 독립성을 보여줍니다.

<?php

$G = array(
        -4=>array(-4=>true,-3=>true,-2=>true),
        -3=>array(-4=>true,-3=>true,-2=>true),
        -2=>array(-4=>true,-3=>true,-2=>true)
);


$C = array();


EC($G,$C);
echo "<pre>";
print_r($C);
function EC($G, &$C){

    $CNST_not_closed =  false;                          // this flag indicates no closure
    $CNST_closed        = true;                         // this flag indicates closure
    // define the state where there is no closures for some node
    $tmp_first_node  =  key($G);                        // first node = first key
    $tmp_last_node  =   $tmp_first_node-1+count($G);    // last node  = last  key
    $CNST_closure_reset = array();
    for($k=$tmp_first_node; $k<=$tmp_last_node; $k++){
        $CNST_closure_reset[$k] = $CNST_not_closed;
    }
    // define the state where there is no closure for all nodes
    for($k=$tmp_first_node; $k<=$tmp_last_node; $k++){
        $H[$k] = $CNST_closure_reset;   // Key in the closure arrays represent nodes
    }
    unset($tmp_first_node);
    unset($tmp_last_node);


    # Start algorithm
    foreach($G as $init_node => $children){#[Jump to initial node set]
        #[Initial Node Set]
        $P = array();                   // declare at starup, remove the old $init_node from path on loop
        $P[$init_node]=true;            // the first key in P is always the new initial node
        $k=$init_node;                  // update the current node
                                        // On loop H[old_init_node] is not cleared cause is never checked again
        do{#Path 1,3,7,4 jump here to extend father 7
            do{#Path from 1,3,8,5 became 2,4,8,5,6 jump here to extend child 6
                $new_expansion = false;
                foreach( $G[$k] as $child => $foo ){#Consider each child of 7 or 6
                    if( $child>$init_node and isset($P[$child])===false and $H[$k][$child]===$CNST_not_closed ){
                        $P[$child]=true;    // add this child to the path
                        $k = $child;        // update the current node
                        $new_expansion=true;// set the flag for expanding the child of k
                        break(1);           // we are done, one child at a time
            }   }   }while(($new_expansion===true));// Do while a new child has been added to the path

            # If the first node is child of the last we have a circuit
            if( isset($G[$k][$init_node])===true ){
                $C[] = $P;  // Leaving this out of closure will catch loops to
            }

            # Closure
            if($k>$init_node){                  //if k>init_node then alwaya count(P)>1, so proceed to closure
                $new_expansion=true;            // $new_expansion is never true, set true to expand father of k
                unset($P[$k]);                  // remove k from path
                end($P); $k_father = key($P);   // get father of k
                $H[$k_father][$k]=$CNST_closed; // mark k as closed
                $H[$k] = $CNST_closure_reset;   // reset k closure
                $k = $k_father;                 // update k
        }   } while($new_expansion===true);//if we don't wnter the if block m has the old k$k_father_old = $k;
        // Advance Initial Vertex Context
    }//foreach initial


}//function

?>

나는 EC를 분석하고 문서화했지만 불행히도 문서는 그리스어로되어 있습니다.


1

DAG에서 모든주기를 찾는 데는 두 가지 단계 (알고리즘)가 있습니다.

첫 번째 단계는 Tarjan의 알고리즘을 사용하여 강력하게 연결된 구성 요소 집합을 찾는 것입니다.

  1. 임의의 정점에서 시작하십시오.
  2. 해당 정점의 DFS 각 노드 x에 대해 dfs_index [x] 및 dfs_lowval [x]의 두 숫자를 유지하십시오. dfs_index [x]는 해당 노드를 방문한 시점을 저장하고 dfs_lowval [x] = min (dfs_low [k]) 여기서 k는 dfs-spanning 트리에서 x의 직접적인 부모가 아닌 x의 모든 자식입니다.
  3. dfs_lowval [x]가 동일한 모든 노드는 동일하게 연결된 구성 요소에 있습니다.

두 번째 단계는 연결된 구성 요소 내에서 사이클 (경로)을 찾는 것입니다. 내 제안은 수정 된 버전의 Hierholzer 알고리즘을 사용하는 것입니다.

아이디어는 다음과 같습니다.

  1. 시작 정점 v를 선택하고 v로 돌아올 때까지 해당 정점에서 가장자리의 흔적을 따라갑니다. v가 아닌 다른 정점에서 고착 될 수는 없습니다. 꼭짓점 w를 떠나는 미사용 모서리가 있어야합니다. 이러한 방식으로 형성된 둘러보기는 비공개 둘러보기이지만 초기 그래프의 모든 정점과 가장자리를 다룰 수는 없습니다.
  2. 현재 둘러보기에 속하지만 둘러보기의 일부가 아닌 인접한 가장자리가있는 정점 v가있는 경우 v로 돌아올 때까지 사용되지 않은 가장자리를 따라 v에서 다른 트레일을 시작하고이 방법으로 형성된 둘러보기를 이전 투어.

다음은 테스트 사례가 포함 된 Java 구현에 대한 링크입니다.

http://stones333.blogspot.com/2013/12/find-cycles-in-directed-graph-dag.html


16
DAG (Directed Acyclic Graph)에 사이클이 어떻게 존재할 수 있습니까?
sky_coder123

모든 사이클을 찾을 수는 없습니다.
Vishwa Ratna


0

나는 Johnson 알고리즘보다 더 효율적인 것으로 보이는 다음 알고리즘을 우연히 발견했습니다 (적어도 큰 그래프의 경우). 그러나 Tarjan의 알고리즘과 비교하여 성능이 확실하지 않습니다.
또한 삼각형까지만 확인했습니다. 관심이 있으시면 Norishige Chiba와 Takao Nishizeki ( http://dx.doi.org/10.1137/0214017 )의 "수목 및 하위 목록 리스팅 알고리즘"을 참조하십시오.


0

분리 된 연결 목록을 사용하는 Javascript 솔루션. 빠른 실행 시간을 위해 분리 된 포리스트로 업그레이드 할 수 있습니다.

var input = '5\nYYNNN\nYYYNN\nNYYNN\nNNNYN\nNNNNY'
console.log(input);
//above solution should be 3 because the components are
//{0,1,2}, because {0,1} and {1,2} therefore {0,1,2}
//{3}
//{4}

//MIT license, authored by Ling Qing Meng

//'4\nYYNN\nYYYN\nNYYN\nNNNY'

//Read Input, preformatting
var reformat = input.split(/\n/);
var N = reformat[0];
var adjMatrix = [];
for (var i = 1; i < reformat.length; i++) {
    adjMatrix.push(reformat[i]);
}

//for (each person x from 1 to N) CREATE-SET(x)
var sets = [];
for (var i = 0; i < N; i++) {
    var s = new LinkedList();
    s.add(i);
    sets.push(s);
}

//populate friend potentials using combinatorics, then filters
var people =  [];
var friends = [];
for (var i = 0; i < N; i++) {
    people.push(i);
}
var potentialFriends = k_combinations(people,2);
for (var i = 0; i < potentialFriends.length; i++){
    if (isFriend(adjMatrix,potentialFriends[i]) === 'Y'){
        friends.push(potentialFriends[i]);
    }
}


//for (each pair of friends (x y) ) if (FIND-SET(x) != FIND-SET(y)) MERGE-SETS(x, y)
for (var i = 0; i < friends.length; i++) {
    var x = friends[i][0];
    var y = friends[i][1];
    if (FindSet(x) != FindSet(y)) {
        sets.push(MergeSet(x,y));
    }
}


for (var i = 0; i < sets.length; i++) {
    //sets[i].traverse();
}
console.log('How many distinct connected components?',sets.length);



//Linked List data structures neccesary for above to work
function Node(){
    this.data = null;
    this.next = null;
}

function LinkedList(){
    this.head = null;
    this.tail = null;
    this.size = 0;

    // Add node to the end
    this.add = function(data){
        var node = new Node();
        node.data = data;
        if (this.head == null){
            this.head = node;
            this.tail = node;
        } else {
            this.tail.next = node;
            this.tail = node;
        }
        this.size++;
    };


    this.contains = function(data) {
        if (this.head.data === data) 
            return this;
        var next = this.head.next;
        while (next !== null) {
            if (next.data === data) {
                return this;
            }
            next = next.next;
        }
        return null;
    };

    this.traverse = function() {
        var current = this.head;
        var toPrint = '';
        while (current !== null) {
            //callback.call(this, current); put callback as an argument to top function
            toPrint += current.data.toString() + ' ';
            current = current.next; 
        }
        console.log('list data: ',toPrint);
    }

    this.merge = function(list) {
        var current = this.head;
        var next = current.next;
        while (next !== null) {
            current = next;
            next = next.next;
        }
        current.next = list.head;
        this.size += list.size;
        return this;
    };

    this.reverse = function() {
      if (this.head == null) 
        return;
      if (this.head.next == null) 
        return;

      var currentNode = this.head;
      var nextNode = this.head.next;
      var prevNode = this.head;
      this.head.next = null;
      while (nextNode != null) {
        currentNode = nextNode;
        nextNode = currentNode.next;
        currentNode.next = prevNode;
        prevNode = currentNode;
      }
      this.head = currentNode;
      return this;
    }


}


/**
 * GENERAL HELPER FUNCTIONS
 */

function FindSet(x) {
    for (var i = 0; i < sets.length; i++){
        if (sets[i].contains(x) != null) {
            return sets[i].contains(x);
        }
    }
    return null;
}

function MergeSet(x,y) {
    var listA,listB;
    for (var i = 0; i < sets.length; i++){
        if (sets[i].contains(x) != null) {
            listA = sets[i].contains(x);
            sets.splice(i,1);
        }
    }
    for (var i = 0; i < sets.length; i++) {
        if (sets[i].contains(y) != null) {
            listB = sets[i].contains(y);
            sets.splice(i,1);
        }
    }
    var res = MergeLists(listA,listB);
    return res;

}


function MergeLists(listA, listB) {
    var listC = new LinkedList();
    listA.merge(listB);
    listC = listA;
    return listC;
}

//access matrix by i,j -> returns 'Y' or 'N'
function isFriend(matrix, pair){
    return matrix[pair[0]].charAt(pair[1]);
}

function k_combinations(set, k) {
    var i, j, combs, head, tailcombs;
    if (k > set.length || k <= 0) {
        return [];
    }
    if (k == set.length) {
        return [set];
    }
    if (k == 1) {
        combs = [];
        for (i = 0; i < set.length; i++) {
            combs.push([set[i]]);
        }
        return combs;
    }
    // Assert {1 < k < set.length}
    combs = [];
    for (i = 0; i < set.length - k + 1; i++) {
        head = set.slice(i, i+1);
        tailcombs = k_combinations(set.slice(i + 1), k - 1);
        for (j = 0; j < tailcombs.length; j++) {
            combs.push(head.concat(tailcombs[j]));
        }
    }
    return combs;
}

0

시작 노드에서 DFS, 순회 중 DFS 경로를 추적하고 경로에서 노드 v에서 s까지의 모서리를 찾으면 경로를 기록하십시오. (v, s)는 DFS 트리의 백 에지이므로 s를 포함하는주기를 나타냅니다.


그러나 이것은 OP가 찾고있는 것이 아닙니다. 모든주기를 찾으십시오.
Sean L

0

순열주기 에 대한 질문은 https://www.codechef.com/problems/PCYCLE 에서 자세히 읽어보십시오.

이 코드를 사용해보십시오 (크기와 숫자를 입력하십시오) :

# include<cstdio>
using namespace std;

int main()
{
    int n;
    scanf("%d",&n);

    int num[1000];
    int visited[1000]={0};
    int vindex[2000];
    for(int i=1;i<=n;i++)
        scanf("%d",&num[i]);

    int t_visited=0;
    int cycles=0;
    int start=0, index;

    while(t_visited < n)
    {
        for(int i=1;i<=n;i++)
        {
            if(visited[i]==0)
            {
                vindex[start]=i;
                visited[i]=1;
                t_visited++;
                index=start;
                break;
            }
        }
        while(true)
        {
            index++;
            vindex[index]=num[vindex[index-1]];

            if(vindex[index]==vindex[start])
                break;
            visited[vindex[index]]=1;
            t_visited++;
        }
        vindex[++index]=0;
        start=index+1;
        cycles++;
    }

    printf("%d\n",cycles,vindex[0]);

    for(int i=0;i<(n+2*cycles);i++)
    {
        if(vindex[i]==0)
            printf("\n");
        else
            printf("%d ",vindex[i]);
    }
}

0

2 층 답변의 의사 코드 용 DFS C ++ 버전 :

void findCircleUnit(int start, int v, bool* visited, vector<int>& path) {
    if(visited[v]) {
        if(v == start) {
            for(auto c : path)
                cout << c << " ";
            cout << endl;
            return;
        }
        else 
            return;
    }
    visited[v] = true;
    path.push_back(v);
    for(auto i : G[v])
        findCircleUnit(start, i, visited, path);
    visited[v] = false;
    path.pop_back();
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.