유 방향 그래프 내에서 모든주기를 감지하는 가장 효율적인 알고리즘은 무엇입니까?
실행 해야하는 작업 일정, 작업은 노드이고 종속성은 가장자리라는 방향 그래프가 있습니다. 이 그래프 내에서주기의 오류 사례를 감지하여 주기적 종속성을 유발해야합니다.
유 방향 그래프 내에서 모든주기를 감지하는 가장 효율적인 알고리즘은 무엇입니까?
실행 해야하는 작업 일정, 작업은 노드이고 종속성은 가장자리라는 방향 그래프가 있습니다. 이 그래프 내에서주기의 오류 사례를 감지하여 주기적 종속성을 유발해야합니다.
답변:
Tarjan의 강력하게 연결된 컴포넌트 알고리즘 은 O(|E| + |V|)
시간이 복잡합니다.
다른 알고리즘 은 Wikipedia의 강력하게 연결된 구성 요소 를 참조하십시오 .
O(|E| + |V|)
집니다. 회색 노드가 다른 회색 노드를 찾으면 흰색 (절대로 방문하지 않음), 회색 (현재 노드는 방문했지만 모든 도달 가능한 노드는 아직 방문하지 않음) 및 검은 색 (모든 도달 가능한 노드는 현재 노드와 함께 방문 함) 색상 코딩을 사용합니다. 사이클. [Cormen의 알고리즘 책에서 우리가 가진 것을 매우 예쁘게]. 'Tarjan 's algorithm'이 그러한 DFS보다 이점이 있는지 궁금합니다!
이것이 작업 일정이라는 것을 감안할 때 언젠가 는 제안 된 실행 순서로 정렬 할 것이라고 생각합니다 .
이 경우 토폴로지 정렬 구현은 어떤 경우에도 사이클을 감지 할 수 있습니다. 유닉스는 tsort
확실히 그렇습니다. 따라서 별도의 단계가 아닌 분류와 동시에 사이클을 감지하는 것이 더 효율적이라고 생각합니다.
따라서 문제는 "루프를 가장 효율적으로 감지하는 방법"이 아니라 "어떻게 가장 효율적으로 분류 하는가"가 될 수 있습니다. 대답은 아마도 "라이브러리 사용"일 것이지만 다음 Wikipedia 기사는 실패합니다.
한 알고리즘에 대한 의사 코드와 Tarjan의 다른 알고리즘에 대한 간단한 설명이 있습니다. 둘 다 O(|V| + |E|)
시간이 복잡합니다.
가장 간단한 방법 은 그래프의 DFT (Depth First Traversal)를 수행하는 것입니다 .
그래프에 n
꼭짓점이 있으면 O(n)
시간 복잡성 알고리즘입니다. 각 정점에서 시작하여 DFT를 수행해야하므로 총 복잡성이가됩니다 O(n^2)
.
현재 깊이 첫 번째 순회에서 모든 정점을 포함 하는 스택 을 유지해야 하며 첫 번째 요소는 루트 노드입니다. DFT 중에 이미 스택에있는 요소를 발견하면 사이클이 발생합니다.
O(n)
스택에 이미 방문한 노드가 포함되어 있는지 확인하는 것이 시간 복잡성이라고 말하는 이유는 무엇 입니까? 스택 O(n)
을 스캔하면 각 새 노드에서 스택을 스캔해야하므로 런타임에 시간이 추가됩니다 . O(n)
방문한 노드를 표시하면 달성 할 수 있습니다
Cormen 등 의 Lemma 22.11 , 알고리즘 소개 (CLRS) 에 따르면 :
유 방향 그래프 G는 G에 대한 깊이 우선 탐색이 후방 에지를 생성하지 않는 경우에만 비순환 적이다.
이것은 몇 가지 답변에서 언급되었습니다. 여기에서는 CLRS의 22 장을 기반으로하는 코드 예제도 제공합니다. 예제 그래프는 아래와 같습니다.
깊이 우선 검색을위한 CLRS 의사 코드 :
CLRS 그림 22.4의 예에서 그래프는 두 개의 DFS 트리로 구성됩니다. 하나는 노드 u , v , x 및 y 로 구성되고 다른 하나는 노드 w 및 z로 구성 됩니다. 각 트리에는 하나의 뒷면이 있습니다. 하나는 x 에서 v로 , 다른 하나는 z 에서 z로 (자체 루프).
핵심 실현은에 때, 백 에지가 발생한다는 것입니다 DFS-VISIT
이웃 반복하면서 기능, v
의 u
, 노드가 함께 발생 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.
DFS로 시작 : DFS 중에 백 에지가 발견 된 경우에만주기가 존재합니다 . 이것은 백색 경로 이론의 결과로 증명됩니다.
내 생각에, 유향 그래프에서 사이클을 감지하는 가장 이해하기 쉬운 알고리즘은 그래프 색 알고리즘입니다.
기본적으로 그래프 채색 알고리즘은 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 에서 그래프 색상 구현을 제공합니다.
노드에 "방문"속성을 추가 할 수없는 경우 세트 (또는 맵)를 사용하고 이미 방문한 노드가 세트에 있지 않으면 세트에 방문한 모든 노드를 추가하십시오. 고유 키 또는 개체의 주소를 "키"로 사용하십시오.
또한 순환 종속성의 "루트"노드에 대한 정보를 제공하여 사용자가 문제를 해결해야 할 때 유용합니다.
또 다른 해결책은 실행할 다음 종속성을 찾는 것입니다. 이를 위해서는 현재 위치와 다음에해야 할 일을 기억할 수있는 스택이 있어야합니다. 종속성을 실행하기 전에이 스택에 이미 있는지 확인하십시오. 그렇다면 사이클을 찾았습니다.
이것이 O (N * M)의 복잡성을 갖는 것처럼 보일 수 있지만 스택의 깊이는 매우 제한적이므로 (N이 작음) "실행"플러스로 확인할 수있는 각 종속성에 따라 M이 작아짐을 기억해야합니다. 당신은 당신이 잎을 발견했을 때 검색을 중지 할 수 있습니다 (따라서 모든 노드를 확인할 필요가 없습니다 -> M도 작습니다).
MetaMake에서는 그래프를 목록의 목록으로 만든 다음 검색 볼륨을 자연스럽게 줄이는 노드를 실행할 때마다 모든 노드를 삭제했습니다. 나는 실제로 독립적 인 검사를 수행 할 필요가 없었습니다. 모든 것이 정상적인 실행 중에 자동으로 발생했습니다.
"테스트 전용"모드가 필요한 경우 실제 작업 실행을 비활성화하는 "dry-run"플래그를 추가하십시오.
다항식 시간의 방향 그래프에서 모든주기를 찾을 수있는 알고리즘은 없습니다. 유 방향 그래프에 n 개의 노드가 있고 모든 노드 쌍이 서로 연결되어 있다고 가정하면 완전한 그래프를 갖게됩니다. 따라서이 n 개 노드의 비어 있지 않은 하위 집합은주기를 나타내며 이러한 하위 집합의 수는 2 ^ n-1 개입니다. 따라서 다항식 시간 알고리즘이 없습니다. 따라서 그래프에서 지시 된주기의 수를 알려주는 효율적인 (어리석지 않은) 알고리즘이 있다고 가정하면 먼저 강력한 연결된 구성 요소를 찾은 다음이 연결된 구성 요소에 알고리즘을 적용 할 수 있습니다. 주기는 구성 요소 내에 만 존재하며 그 사이에는 존재하지 않기 때문입니다.
내가하는 방법은 방문한 정점의 수를 세면서 토폴로지 정렬을 수행하는 것입니다. 이 수가 DAG의 총 정점 수보다 작은 경우주기가 있습니다.
/mathpro/16393/finding-a-cycle-of-fixed-length 이 솔루션이 특히 4 길이에 가장 적합합니다 :)
또한 phys 마법사는 U (V ^ 2)를 수행해야한다고 말합니다. O (V) / O (V + E) 만 있으면된다고 생각합니다. 그래프가 연결되면 DFS는 모든 노드를 방문합니다. 그래프에 하위 그래프가 연결되어 있으면이 하위 그래프의 정점에서 DFS를 실행할 때마다 연결된 정점을 찾을 수 있으며 다음 DFS 실행에 대해서는이를 고려할 필요가 없습니다. 따라서 각 정점마다 실행할 가능성이 올바르지 않습니다.
DFS가 이미 방문한 정점을 가리키는 모서리를 찾으면주기가 있습니다.
당신이 말했듯이, 당신은 작업 세트를 가지고 있으며, 특정 순서로 실행되어야합니다. Topological sort
작업 예약 순서가 필요하거나 (또는 종속성 문제인 경우 direct acyclic graph
) dfs
목록을 실행 하고 유지 보수하고 목록의 시작 부분에 이미 방문한 노드가있는 경우 노드 추가를 시작하십시오. 그런 다음 주어진 그래프에서 사이클을 발견했습니다.
그래프가이 속성을 만족시키는 경우
|e| > |v| - 1
그래프에는 최소한 사이클이 포함됩니다.