유 방향 그래프에서 사이클을 감지하기위한 최상의 알고리즘


396

유 방향 그래프 내에서 모든주기를 감지하는 가장 효율적인 알고리즘은 무엇입니까?

실행 해야하는 작업 일정, 작업은 노드이고 종속성은 가장자리라는 방향 그래프가 있습니다. 이 그래프 내에서주기의 오류 사례를 감지하여 주기적 종속성을 유발해야합니다.


13
모든주기를 감지하고 싶다고 말하지만 유스 케이스는주기가 있는지 감지하는 것으로 충분하다고 제안합니다.
Steve Jessop

29
점검, 수정, 점검, 수정 등이 아닌 한 번에 고정 될 수 있도록 모든 사이클을 감지하는 것이 좋습니다.
Peauters

2
Donald B. Johnson의 "직접 그래프의 모든 기본 회로 찾기"논문을 읽어야합니다. 기본 회로 만 찾을 수 있지만 이것은 충분합니다. : 그리고 여기 내 사용할 준비가이 알고리즘의 자바 구현 github.com/1123/johnson
user152468은

알고리즘을 추가로 수정하여 DFS를 실행하십시오. 방문한 각 노드를 표시하십시오. 이미 방문한 노드를 방문하면 고름이 있습니다. 경로에서 후퇴 할 때는 방문한 노드의 표시를 해제하십시오.
Hesham Yassin

2
@HeshamYassin, 이미 방문한 노드를 방문한다고해서 반드시 루프가있는 것은 아닙니다. 내 의견 cs.stackexchange.com/questions/9676/…을 읽으 십시오 .
Maksim Dmitriev 님

답변:


193

Tarjan의 강력하게 연결된 컴포넌트 알고리즘O(|E| + |V|)시간이 복잡합니다.

다른 알고리즘 은 Wikipedia의 강력하게 연결된 구성 요소 를 참조하십시오 .


69
강하게 연결된 구성 요소를 찾는 방법은 그래프에 존재하는주기에 대해 어떻게 알 수 있습니까?
Peter

4
누군가는 확인할 수 있지만 Tarjan 알고리즘은 A-> A와 같이 자신을 직접 가리키는 노드주기를 지원하지 않습니다.
Cédric Guillemette

24
@Cedrik 맞습니다. Tarjan 알고리즘의 결함은 아니지만이 질문에 사용되는 방식입니다. Tarjan은 직접 사이클을 찾지 않고 강력하게 연결된 구성 요소를 찾습니다. 물론 크기가 1보다 큰 SCC는주기를 의미합니다. 비순환 구성 요소에는 단일 SCC가 있습니다. 문제는 자체 루프가 자체적으로 SCC로 이동한다는 것입니다. 따라서 자체 루프에 대한 별도의 검사가 필요합니다.
mgiuca

13
(그래프의 모든 강력하게 연결된 구성 요소)! = (그래프의 모든 사이클)
optimusfrenk

4
@ aku : 3 색 DFS도 같은 런타임을가 O(|E| + |V|)집니다. 회색 노드가 다른 회색 노드를 찾으면 흰색 (절대로 방문하지 않음), 회색 (현재 노드는 방문했지만 모든 도달 가능한 노드는 아직 방문하지 않음) 및 검은 색 (모든 도달 가능한 노드는 현재 노드와 함께 방문 함) 색상 코딩을 사용합니다. 사이클. [Cormen의 알고리즘 책에서 우리가 가진 것을 매우 예쁘게]. 'Tarjan 's algorithm'이 그러한 DFS보다 이점이 있는지 궁금합니다!
KGhatak

73

이것이 작업 일정이라는 것을 감안할 때 언젠가 는 제안 된 실행 순서로 정렬 할 것이라고 생각합니다 .

이 경우 토폴로지 정렬 구현은 어떤 경우에도 사이클을 감지 할 수 있습니다. 유닉스는 tsort확실히 그렇습니다. 따라서 별도의 단계가 아닌 분류와 동시에 사이클을 감지하는 것이 더 효율적이라고 생각합니다.

따라서 문제는 "루프를 가장 효율적으로 감지하는 방법"이 아니라 "어떻게 가장 효율적으로 분류 하는가"가 될 수 있습니다. 대답은 아마도 "라이브러리 사용"일 것이지만 다음 Wikipedia 기사는 실패합니다.

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

한 알고리즘에 대한 의사 코드와 Tarjan의 다른 알고리즘에 대한 간단한 설명이 있습니다. 둘 다 O(|V| + |E|)시간이 복잡합니다.


깊이 정렬 검색 알고리즘에 의존하기 때문에 토폴로지 정렬은주기를 감지 할 수 있지만 실제로주기를 감지하려면 추가 부기가 필요합니다. Kurt Peek의 정답을 참조하십시오.
Luke Hutchison

33

가장 간단한 방법 은 그래프의 DFT (Depth First Traversal)를 수행하는 것입니다 .

그래프에 n꼭짓점이 있으면 O(n)시간 복잡성 알고리즘입니다. 각 정점에서 시작하여 DFT를 수행해야하므로 총 복잡성이가됩니다 O(n^2).

현재 깊이 첫 번째 순회에서 모든 정점을 포함 하는 스택 을 유지해야 하며 첫 번째 요소는 루트 노드입니다. DFT 중에 이미 스택에있는 요소를 발견하면 사이클이 발생합니다.


21
이것은 "일반적인"그래프 사실,하지만에 대한 거짓 인 것 감독 그래프. 예를 들어, 4 개의 노드가있는 "다이아몬드 종속성 다이어그램"을 고려해보십시오. A가 B를 가리키는 C와 C가 각각 D를 가리키는 에지를 갖습니다. A에서이 다이어그램의 DFT 통과는 "루프"가 잘못되었다고 잘못 판단합니다 실제로 사이클-루프가 있지만 화살표를 따라 통과 할 수 없으므로 사이클이 아닙니다.
Peter

9
@ peter A의 DFT 가주 기가 있다고 잘못 결론을 내릴 방법을 설명해 주시겠습니까?
Deepak

10
@Deepak-사실, 나는 "phys wizard"의 대답을 잘못 읽었습니다. "스택에서"쓴 곳은 "이미 발견되었습니다"라고 생각했습니다. 실제로 DFT를 실행하는 동안 "스택에서"듀프를 확인하기에 충분합니다 (직접 루프 감지 용). 각자 한 개씩 공감하십시오.
피터

2
O(n)스택에 이미 방문한 노드가 포함되어 있는지 확인하는 것이 시간 복잡성이라고 말하는 이유는 무엇 입니까? 스택 O(n)을 스캔하면 각 새 노드에서 스택을 스캔해야하므로 런타임에 시간이 추가됩니다 . O(n)방문한 노드를 표시하면 달성 할 수 있습니다
James Wierzba

Peter가 말했듯이 이것은 방향 그래프에 대해서는 불완전합니다. Kurt Peek의 정답을 참조하십시오.
Luke Hutchison

32

Cormen 등 의 Lemma 22.11 , 알고리즘 소개 (CLRS) 에 따르면 :

유 방향 그래프 G는 G에 대한 깊이 우선 탐색이 후방 에지를 생성하지 않는 경우에만 비순환 적이다.

이것은 몇 가지 답변에서 언급되었습니다. 여기에서는 CLRS의 22 장을 기반으로하는 코드 예제도 제공합니다. 예제 그래프는 아래와 같습니다.

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

깊이 우선 검색을위한 CLRS 의사 코드 :

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

CLRS 그림 22.4의 예에서 그래프는 두 개의 DFS 트리로 구성됩니다. 하나는 노드 u , v , xy 로 구성되고 다른 하나는 노드 wz로 구성 됩니다. 각 트리에는 하나의 뒷면이 있습니다. 하나는 x 에서 v로 , 다른 하나는 z 에서 z로 (자체 루프).

핵심 실현은에 때, 백 에지가 발생한다는 것입니다 DFS-VISIT이웃 반복하면서 기능, vu, 노드가 함께 발생 GRAY색상.

다음 파이썬 코드는 if사이클을 감지 하는 절이 추가 된 CLRS 의사 코드의 적응입니다 .

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 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.


29

DFS로 시작 : DFS 중에 백 에지가 발견 된 경우에만주기가 존재합니다 . 이것은 백색 경로 이론의 결과로 증명됩니다.


3
네, 같은 생각,하지만 난 내 방식 cs.stackexchange.com/questions/7216/find-the-simple-cycles-in-a-directed-graph 게시, 충분하지 않습니다
jonaprieto

진실. Ajay Garg는 "질문"을 찾는 방법에 대해서만 말하고 있습니다.이 질문에 대한 일부 답변입니다. 귀하의 링크는 질문에 따라 모든주기를 찾는 것에 대해 이야기하지만 Ajay Garg와 동일한 접근 방식을 사용하는 것처럼 보이지만 가능한 모든 dfs-tree를 수행합니다.
Manohar Reddy Poreddy

유 방향 그래프에는 불완전합니다. Kurt Peek의 정답을 참조하십시오.
Luke Hutchison

26

내 생각에, 유향 그래프에서 사이클을 감지하는 가장 이해하기 쉬운 알고리즘은 그래프 색 알고리즘입니다.

기본적으로 그래프 채색 알고리즘은 DFS 방식으로 그래프를 이동합니다 (Depth First Search, 즉 다른 경로를 탐색하기 전에 경로를 완전히 탐색 함을 의미 함). 뒤쪽 가장자리를 찾으면 루프가 포함 된 것으로 표시합니다.

그래프 채색 알고리즘에 대한 자세한 설명은이 기사를 읽으십시오. http://www.geeksforgeeks.org/detect-cycle-direct-graph-using-colors/

또한 JavaScript https://github.com/dexcodeinc/graph_algorithm.js/blob/master/graph_algorithm.js 에서 그래프 색상 구현을 제공합니다.


8

노드에 "방문"속성을 추가 할 수없는 경우 세트 (또는 맵)를 사용하고 이미 방문한 노드가 세트에 있지 않으면 세트에 방문한 모든 노드를 추가하십시오. 고유 키 또는 개체의 주소를 "키"로 사용하십시오.

또한 순환 종속성의 "루트"노드에 대한 정보를 제공하여 사용자가 문제를 해결해야 할 때 유용합니다.

또 다른 해결책은 실행할 다음 종속성을 찾는 것입니다. 이를 위해서는 현재 위치와 다음에해야 할 일을 기억할 수있는 스택이 있어야합니다. 종속성을 실행하기 전에이 스택에 이미 있는지 확인하십시오. 그렇다면 사이클을 찾았습니다.

이것이 O (N * M)의 복잡성을 갖는 것처럼 보일 수 있지만 스택의 깊이는 매우 제한적이므로 (N이 작음) "실행"플러스로 확인할 수있는 각 종속성에 따라 M이 작아짐을 기억해야합니다. 당신은 당신이 잎을 발견했을 때 검색을 중지 할 수 있습니다 (따라서 모든 노드를 확인할 필요가 없습니다 -> M도 작습니다).

MetaMake에서는 그래프를 목록의 목록으로 만든 다음 검색 볼륨을 자연스럽게 줄이는 노드를 실행할 때마다 모든 노드를 삭제했습니다. 나는 실제로 독립적 인 검사를 수행 할 필요가 없었습니다. 모든 것이 정상적인 실행 중에 자동으로 발생했습니다.

"테스트 전용"모드가 필요한 경우 실제 작업 실행을 비활성화하는 "dry-run"플래그를 추가하십시오.


7

다항식 시간의 방향 그래프에서 모든주기를 찾을 수있는 알고리즘은 없습니다. 유 방향 그래프에 n 개의 노드가 있고 모든 노드 쌍이 서로 연결되어 있다고 가정하면 완전한 그래프를 갖게됩니다. 따라서이 n 개 노드의 비어 있지 않은 하위 집합은주기를 나타내며 이러한 하위 집합의 수는 2 ^ n-1 개입니다. 따라서 다항식 시간 알고리즘이 없습니다. 따라서 그래프에서 지시 된주기의 수를 알려주는 효율적인 (어리석지 않은) 알고리즘이 있다고 가정하면 먼저 강력한 연결된 구성 요소를 찾은 다음이 연결된 구성 요소에 알고리즘을 적용 할 수 있습니다. 주기는 구성 요소 내에 만 존재하며 그 사이에는 존재하지 않기 때문입니다.


1
노드 수를 입력 크기로 간주하면 true입니다. 에지 수 또는주기 수 또는 이러한 측정 값의 조합으로 런타임 복잡성을 설명 할 수도 있습니다. Donald B. Johnson의 "지향 그래프의 모든 기본 회로 찾기"알고리즘은 O ((n + e) ​​(c + 1))에 의해 주어진 다항식 실행 시간을 갖습니다. 여기서 n은 노드 수, e 에지 수 c 그래프의 기본 회로 수. 그리고이 알고리즘의 Java 구현은 다음과 같습니다 : github.com/1123/johnson .
user152468

4

나는이 문제를 sml (명령어 프로그래밍)에서 구현했다. 개요는 다음과 같습니다. indegree 또는 outdegree가 0 인 모든 노드를 찾으십시오. 이러한 노드는주기의 일부가 될 수 없으므로 제거하십시오. 그런 다음 해당 노드에서 모든 수신 또는 발신 에지를 제거하십시오. 이 프로세스를 결과 그래프에 반복적으로 적용하십시오. 마지막에 노드 나 모서리가없는 경우 그래프에주기가없는 것입니다.


2

내가하는 방법은 방문한 정점의 수를 세면서 토폴로지 정렬을 수행하는 것입니다. 이 수가 DAG의 총 정점 수보다 작은 경우주기가 있습니다.


4
말이되지 않습니다. 그래프에주기가있는 경우 토폴로지 정렬이 없으므로 토폴로지 정렬을위한 올바른 알고리즘이 중단됩니다.
sleske

4
위키피디아의 자료 : 많은 토폴로지 정렬 알고리즘은 사이클을 감지하는데, 이는 사이클이 토폴로지 순서의 존재를 방해하기 때문입니다.
Oleg Mikheev

1
@OlegMikheev 예. 그러나 Steve는 "그 수가 DAG의 정점 수보다 적 으면주기가 있습니다"라고 말하고 있습니다.
nbro

@nbro 내기, 그것은 토폴로지 정렬 알고리즘의 변형을 의미합니다. 이는 토폴로지 정렬이 존재하지 않을 때 중단되고 모든 정점을 방문하지 않습니다.
maaartinus

사이클이있는 그래프에서 토폴로지 정렬을 수행하면 불량 가장자리가 가장 적은 순서 (주문 번호> 이웃의 주문 번호)가됩니다. 그러나 정렬을 한 후에는 불량 에지를 쉽게 감지 할 수 있습니다. 결과적으로 사이클이있는 그래프가 감지됩니다.
UGP

2

/mathpro/16393/finding-a-cycle-of-fixed-length 이 솔루션이 특히 4 길이에 가장 적합합니다 :)

또한 phys 마법사는 U (V ^ 2)를 수행해야한다고 말합니다. O (V) / O (V + E) 만 있으면된다고 생각합니다. 그래프가 연결되면 DFS는 모든 노드를 방문합니다. 그래프에 하위 그래프가 연결되어 있으면이 하위 그래프의 정점에서 DFS를 실행할 때마다 연결된 정점을 찾을 수 있으며 다음 DFS 실행에 대해서는이를 고려할 필요가 없습니다. 따라서 각 정점마다 실행할 가능성이 올바르지 않습니다.


1

DFS가 이미 방문한 정점을 가리키는 모서리를 찾으면주기가 있습니다.


1
1,2,3에서 실패 : 1,2; 1,3; 2,3;
시끄러운 고양이

4
@JakeGreene 여기를보십시오 : i.imgur.com/tEkM5xy.png 이해하기에 충분히 간단합니다. 0부터 시작한다고 가정하겠습니다. 그런 다음 노드 1로 이동하여 더 이상 경로가 없으면 다시 되돌아갑니다. 이제 이미 방문한 정점 1에 가장자리가있는 노드 2를 방문합니다. 당신의 의견으로는 당신은 사이클을해야합니다-그리고 당신은 정말로 하나가 없습니다
시끄러운 고양이

3
@kittyPL 해당 그래프에는 사이클이 없습니다. Wikipedia에서 : "지향 그래프의 지정주기는 동일한 정점에서 시작하고 끝나는 일련의 정점으로,주기의 연속 된 두 정점마다 이전 정점에서 다음 정점으로 향하는 모서리가 있습니다." 지시 된주기 동안 V로 돌아가는 V에서 경로를 따라갈 수 있어야합니다. mafonya의 솔루션은 주어진 문제에 대해 작동합니다
Jake Greene

2
@JakeGreene 물론 그렇지 않습니다. 알고리즘을 사용하고 1부터 시작하면 어쨌든 사이클을 감지 할 수 있습니다 ...이 알고리즘은 매우 나쁩니다. 일반적으로 방문한 정점을 발견 할 때마다 뒤로 걷는 것으로 충분합니다.
시끄러운 고양이

6
@kittyPL DFS는 지정된 시작 노드에서주기를 감지하기 위해 작동합니다. 그러나 DFS를 수행 할 때는 방문한 노드를 색칠하여 교차 에지와 백 에지를 구별해야합니다. 꼭짓점을 처음 방문하면 회색으로 변하고 모든 가장자리가 방문되면 검은 색으로 바뀝니다. DFS를 수행 할 때 회색 정점에 도달하면 해당 정점이 조상입니다 (예 :주기가 있음). 정점이 검은 색이면 그냥 가장자리입니다.
Kyrra

0

당신이 말했듯이, 당신은 작업 세트를 가지고 있으며, 특정 순서로 실행되어야합니다. Topological sort작업 예약 순서가 필요하거나 (또는 ​​종속성 문제인 경우 direct acyclic graph) dfs목록을 실행 하고 유지 보수하고 목록의 시작 부분에 이미 방문한 노드가있는 경우 노드 추가를 시작하십시오. 그런 다음 주어진 그래프에서 사이클을 발견했습니다.


-11

그래프가이 속성을 만족시키는 경우

|e| > |v| - 1

그래프에는 최소한 사이클이 포함됩니다.


10
무 방향 그래프의 경우에는 사실 일 수 있지만 확실히 방향 그래프의 경우에는 그렇지 않습니다.
Hans-Peter Störr

6
카운터 예는 A-> B, B-> C, A-> C입니다.
user152468

모든 정점에 모서리가있는 것은 아닙니다.
Debanjan Dhar
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.