그래프에서주기를 찾는 데 BFS가 아닌 DFS가 필요한 이유


85

주로 DFS는 BFS가 아닌 그래프에서주기를 찾는 데 사용됩니다. 이유가 있습니까? 둘 다 트리 / 그래프를 횡단하는 동안 노드가 이미 방문되었는지 확인할 수 있습니다.


5
유 방향 그래프에서는 DFS 만주기를 감지하는 데 사용할 수 있습니다. 그러나 무 방향 그래프에서는 둘 다 사용할 수 있습니다.
Hengameh

답변:


73

깊이 우선 검색은 더 빨리 역 추적 할 수 있으므로 폭 우선 검색보다 메모리 효율적입니다. 호출 스택을 사용하면 구현하기가 더 쉽지만 스택을 오버플로하지 않는 가장 긴 경로에 의존합니다.

또한 그래프가 방향 이 지정되면 노드를 방문했는지 여부뿐만 아니라 거기에 어떻게 도달했는지 기억해야합니다. 그렇지 않으면주기를 찾았다 고 생각할 수 있지만 실제로는 두 개의 별도 경로 A-> B 만 있지만 이것이 경로 B-> A가 있다는 의미는 아닙니다. 예를 들면

에서 시작하여 BFS를 수행하면 0주기가 있지만 실제로는주기가없는 것으로 감지합니다.

깊이 우선 검색을 사용하면 하강 할 때 방문한 노드를 표시하고 역 추적 할 때 표시를 해제 할 수 있습니다. 이 알고리즘의 성능 향상에 대한 설명을 참조하십시오.

유향 그래프에서주기를 감지 하는 가장 좋은 알고리즘Tarjan의 알고리즘을 참조 할 수 있습니다.


3
(더 빨리 역 추적 할 수 있기 때문에 메모리 효율적이고 명시 적으로 유지 관리 할 필요없이 스택이 공개 목록을 저장하도록 할 수 있기 때문에 구현하기가 더 쉽습니다.)
Amber

3
IMO, 꼬리 재귀에 의존 할 수있는 경우에만 더 쉽습니다.
Hank Gay

2
"역 추적 할 때 표시 해제"-위험에 처하세요! 이것은 쉽게 O (n ^ 2) 동작으로 이어질 수 있습니다. 특히 이러한 DFS는 교차 모서리를 "나무"모서리로 오해 할 것입니다 ( "나무"모서리는 실제로 더 이상 나무를 형성하지 않기 때문에 잘못된 이름이 될 것입니다)
Dimitris Andreou

1
@Dimitris Andreo : 성능 향상을 위해 2 개가 아닌 3 개의 방문 상태를 사용할 수 있습니다. 유 방향 그래프를 사용하면 '이 노드를 이전에 본 적이 있습니다'와 '이 노드는 루프의 일부입니다'사이에 차이가 있습니다. 무 방향 그래프에서는 동등합니다.
Mark Byers

정확히 알고리즘을 선형으로 만들기 위해 세 번째 상태가 필요하므로 해당 부분을 수정하는 것이 좋습니다.
Dimitris Andreou

28
  1. DFS는 구현하기가 더 쉽습니다.
  2. DFS가주기를 찾으면 스택에는주기를 형성하는 노드가 포함됩니다. BFS의 경우도 마찬가지이므로 발견 된 주기도 인쇄하려면 추가 작업을 수행해야합니다. 이것은 DFS를 훨씬 더 편리하게 만듭니다.

10

그래프가 방향이 지정되지 않은 경우 BFS가 합리적 일 수 있습니다 (방향성 그래프로주기를보고하는 BFS를 사용하여 효율적인 알고리즘을 보여줄 수 있습니다!). 여기서 각 "교차 에지"는주기를 정의합니다. 교차 에지가 {v1, v2}이고 해당 노드를 포함하는 루트 (BFS 트리에서)가 r이면주기는 r ~ v1 - v2 ~ r( ~경로, -단일 에지)이며 DFS 에서처럼 거의 쉽게보고 될 수 있습니다.

BFS를 사용하는 유일한 이유는 (무 방향) 그래프가 긴 경로와 작은 경로 커버 (즉, 깊고 좁은)를 가질 것이라는 것을 알고있는 경우입니다. 이 경우 BFS는 DFS 스택보다 대기열에 대해 비례 적으로 적은 메모리를 필요로합니다 (물론 여전히 선형).

다른 모든 경우에는 DFS가 분명히 승자입니다. 방향성 그래프와 무 방향성 그래프 모두에서 작동하며주기를보고하는 것은 간단합니다. 조상에서 후손까지의 경로에 뒤쪽 가장자리를 연결하면주기를 얻을 수 있습니다. 대체로이 문제에 대해 BFS보다 훨씬 낫고 실용적입니다.


4

BFS는주기를 찾는 방향 그래프에서 작동하지 않습니다. A-> B 및 A-> C-> B를 그래프에서 A에서 B 로의 경로로 고려하십시오. BFS는 B가 방문한 경로 중 하나를 따라 가면 말합니다. 다음 경로를 계속 이동하면 표시된 노드 B가 다시 발견되었으므로 사이클이 있습니다. 분명히 여기에는주기가 없습니다.


DFS가 귀하의 예제에 사이클이 존재하지 않는다는 것을 어떻게 명확하게 식별하는지 설명해 주시겠습니까? 제공된 예제에는 사이클이 존재하지 않는다는 데 동의하지만 A-> B에서 A-> C-> B로 이동하면 B는 이미 방문했고 그 부모는 C가 아니라 A이고 나는 DFS가 이미 방문한 요소의 부모를 현재 우리가 현재 확인중인 노드와 비교하여주기를 감지한다는 것을 읽었습니다. 뭐?
쏟아

여기에서 보신 것은이 특정 구현이 작동하지 않는다는 것입니다. BFS에서는 불가능하다는 것이 아닙니다. 사실, 그것은 이다 그것은 더 많은 작업과 공간을 차지하지만, 수.
Prune

@Prune : 여기에있는 모든 스레드 (내 생각에)는 bfs가주기를 감지하는 데 작동하지 않는다는 것을 증명하려고합니다. 반박하는 방법을 알고 있다면 그 증거를 제공해야합니다. 간단하게 노력이보다 못해 충분할 것을 말
디트 야 라만

알고리즘이 링크 된 게시물에 주어 졌기 때문에 여기에서 개요를 반복하는 것이 적절하다고 생각하지 않습니다.
Prune

링크 된 게시물을 찾을 수 없으므로 동일한 요청을 받았습니다. bfs 기능에 대한 귀하의 요점에 동의하고 구현에 대해 방금 생각했습니다. :) 팁을위한 감사합니다
아 디트 야 라만

3

왜 그런 오래된 질문이 내 피드에 나타 났는지 모르겠지만 이전 답변이 모두 나쁘기 때문에 ...

DFS는 작동 하기 때문에 방향성 그래프에서주기를 찾는 데 사용됩니다. .

DFS에서 모든 정점은 "방문"되며, 정점 방문은 다음을 의미합니다.

  1. 정점이 시작됩니다.
  2. 해당 정점에서 도달 할 수있는 하위 그래프를 방문합니다. 여기에는 해당 정점에서 도달 할 수있는 추적되지 않은 모든 가장자리를 추적하고 도달 할 수있는 모든 방문하지 않은 정점을 방문하는 것이 포함됩니다.

  3. 정점이 완성되었습니다.

중요한 기능은 정점이 완료되기 전에 정점에서 도달 할 수있는 모든 가장자리가 추적된다는 것입니다. 이것은 DFS의 기능이지만 BFS는 아닙니다. 사실 이것이 DFS의 정의입니다.

이 기능으로 인해 주기 의 첫 번째 정점이 시작 되는시기를 알고 있습니다 .

  1. 주기의 가장자리가 추적되지 않았습니다. 우리는 이것을 알고 있습니다. 왜냐하면 여러분은 사이클의 다른 정점에서만 얻을 수 있기 때문입니다. 그리고 우리는 시작할 첫 번째 정점에 대해 이야기 하고 있습니다.
  2. 해당 정점에서 도달 할 수있는 모든 추적되지 않은 가장자리 는 완료되기 전에 추적 되며 여기에는 아직 추적 된 가장자리가 없기 때문에주기의 모든 가장자리 가 포함됩니다 . 따라서주기가있는 경우 시작된 후 완료되기 전에 첫 번째 정점으로 돌아가는 가장자리를 찾을 수 있습니다. 과
  3. 추적되는 모든 가장자리는 시작되었지만 완료되지 않은 모든 정점에서 도달 할 수 있으므로 이러한 정점에 대한 가장자리를 찾는 것은 항상 주기를 나타냅니다.

따라서주기가있는 경우 시작되었지만 완료되지 않은 정점 (2)에 대한 가장자리를 찾을 수 있으며 그러한 가장자리를 찾으면주기 (3)가 있음을 보장합니다.

이것이 DFS가 방향성 그래프에서주기를 찾는 데 사용되는 이유입니다.

BFS는 그러한 보장을 제공하지 않으므로 작동하지 않습니다. (BFS 또는 유사한 하위 절차를 포함하는 완벽하게 좋은주기 찾기 알고리즘에도 불구하고)

반면에 무 방향 그래프는 한 쌍의 꼭지점 사이에 두 개의 경로가있을 때마다, 즉 나무가 아닐 때마다주기가 있습니다. 이것은 BFS 또는 DFS 중에 쉽게 감지 할 수 있습니다. 새 꼭지점으로 추적 된 가장자리는 트리를 형성하고 다른 가장자리는주기를 나타냅니다.


실제로 이것은 실제 이유에 대해 자세히 설명하는 가장 (유일한) 관련 답변입니다.
plasmacel

2

나무의 임의의 지점에 사이클을 배치하면 DFS는 나무의 절반 정도를 덮었을 때 사이클에 부딪히는 경향이 있으며, 절반은 이미 사이클이가는 곳을 횡단하고 절반은 그렇지 않습니다 ( 평균적으로 나머지 트리의 절반에서 찾을 수 있으므로 평균적으로 트리의 약 0.5 * 0.5 + 0.5 * 0.75 = 0.625로 평가됩니다.

나무의 임의의 지점에주기를 배치하면 BFS는 해당 깊이에서 나무의 레이어를 평가할 때만주기에 충돌하는 경향이 있습니다. 따라서 일반적으로 균형 이진 트리의 잎을 평가해야하며 일반적으로 더 많은 트리를 평가하게됩니다. 특히, 3/4의 경우 두 개의 링크 중 적어도 하나가 나무의 잎에 나타나며,이 경우 평균적으로 나무의 3/4 (하나의 링크가있는 경우) 또는 7 / 나무의 8 개 (두 개가있는 경우)이므로 이미 검색 기대치에 도달했습니다. 1 / 2 * 3 / 4 + 1 / 4 * 7 / 8 = (7 + 12) / 32 = 21/32 = 트리의 0.656 ... 잎 노드에서 추가 된 주기로 트리를 검색하는 비용을 추가하지 않고도.

또한 DFS는 BFS보다 구현하기 쉽습니다. 그래서 당신이 당신의주기에 대해 알지 못한다면 그것은 사용하는 것입니다 (예를 들어,주기는 당신이 검색하는 루트 근처에있을 가능성이 있고 BFS가 당신에게 이점을 제공합니다).


거기에 많은 마법의 숫자. 나는 "DFS가 더 빠르다"라는 주장에 동의하지 않는다. 전적으로 입력에 의존하며이 경우 다른 입력보다 더 일반적인 입력은 없습니다.
IVlad

@Vlad-숫자는 마법이 아닙니다. 그것들은 수단이고, 그렇게 명시되어 있으며, 내가 언급 한 가정을 감안할 때 계산하기가 거의 없습니다. 평균으로 근사하는 것이 잘못된 근사치라면 유효한 비판이 될 것입니다. (그리고 구조에 대한 가정을 할 수 있다면 대답이 바뀔 수 있다고 명시 적으로 언급했습니다.)
Rex Kerr

숫자는 아무 의미가 없기 때문에 마법입니다. 당신은 DFS가 더 나은 사례를 가지고 그 결과를 일반적인 사례로 외삽했습니다. 당신의 진술은 근거가 없습니다 : "DFS는 나무의 절반 정도를 덮을 때주기를 치는 경향이 있습니다.": 그것을 증명하십시오. 나무의 순환에 대해 말할 수 없다는 것은 말할 것도 없습니다. 나무에는 정의에 따라주기가 없습니다. 나는 당신의 요점이 무엇인지 모르겠습니다. DFS는 막 다른 골목에 도달 할 때까지 한 방향으로 진행되므로 평균적으로 탐색 할 GRAPH (트리가 아님)의 양을 알 방법이 없습니다. 아무것도 증명되지 않는 무작위 케이스를 선택했습니다.
IVlad

@Vlad-모든 비순환 완전 연결 무 방향 그래프는 (루팅되지 않은 무 방향) 트리입니다. 나는 "하나의 가짜 링크에 대한 트리 저장이 될 그래프"를 의미했습니다. 아마도 이것이 알고리즘의 주요 응용 프로그램이 아닐 수도 있습니다. 트리가 아닌 링크가 매우 많은 얽힌 그래프에서 사이클을 찾고 싶을 수도 있습니다. 그러나 그것이 나무와 비슷하고 모든 그래프에서 평균을 낸다면, 모든 노드는 똑같이 상기 스퓨리어스 링크의 소스 일 가능성이 높으며, 이는 링크에 도달했을 때 예상되는 트리 커버리지를 50 %로 만듭니다. 그래서 나는 그 예가 대표적이지 않았을 수도 있음을 인정합니다. 그러나 수학은 사소해야합니다.
Rex Kerr

1

그래프가 순환적임을 증명하려면 하나의 순환 (직접 또는 간접적으로 자신을 향하는 에지)이 있음을 증명해야합니다.

DFS에서는 한 번에 하나의 정점을 가져 와서주기가 있는지 확인합니다. 주기가 발견되는 즉시 다른 정점 검사를 생략 할 수 있습니다.

BFS에서 우리는 많은 정점 가장자리를 동시에 추적해야하며 마지막에주기가 있는지 확인하는 경우보다 더 자주 추적해야합니다. 그래프의 크기가 커짐에 따라 BFS는 DFS에 비해 더 많은 공간, 계산 및 시간이 필요합니다.


0

재귀 또는 반복 구현에 대해 이야기하고 있는지 여부에 따라 다릅니다.

재귀 DFS는 모든 노드를 두 번 방문합니다. Iterative-BFS는 모든 노드를 한 번 방문합니다.

주기를 감지하려면 인접성을 추가하기 전과 후에 노드를 조사해야합니다. 노드에서 "시작"할 때와 노드에서 "마침"할 때 모두입니다.

이를 위해서는 Iterative-BFS에서 더 많은 작업이 필요하므로 대부분의 사람들은 Recursive-DFS를 선택합니다.

예를 들어 std :: stack을 사용한 Iterative-DFS의 간단한 구현에는 Iterative-BFS와 동일한 문제가 있습니다. 이 경우 더미 요소를 스택에 배치하여 노드 작업을 "완료"할 때 추적해야합니다.

Iterative-DFS에서 노드로 "완료"할 때를 결정하기 위해 추가 작업이 필요한 방법에 대한 자세한 내용은이 답변을 참조하십시오 (TopoSort 컨텍스트에서 답변 됨).

재귀없이 DFS를 사용하는 토폴로지 정렬

이것이 사람들이 노드 처리를 "완료"할 때 결정해야하는 문제에 대해 Recursive-DFS를 선호하는 이유를 설명합니다.


재귀를 사용하는지 또는 반복을 통해 재귀를 제거하는지는 중요하지 않기 때문에 이것은 완전히 잘못된 것입니다. 모든 노드를 한 번만 방문하는 재귀 변형을 구현할 수있는 것처럼 모든 노드를 두 번 방문하는 반복 DFS를 구현할 수 있습니다.
plasmacel

0

BFS방향 그래프에서 주어진 노드를 포함하는 가장 짧은 사이클을 찾고 싶을 때 사용해야 합니다.

예 :여기에 이미지 설명 입력

주어진 노드가 2 인 경우- [2,3,4], [2,3,4,5,6,7,8,9]&의 일부인 세주기가 있습니다 [2,5,6,7,8,9]. 가장 짧은 것은[2,3,4]

BFS를 사용하여이를 구현하려면 적절한 데이터 구조를 사용하여 방문한 노드의 기록을 명시 적으로 유지해야합니다.

그러나 다른 모든 목적 (예 : 주기적 경로를 찾거나주기가 있는지 여부를 확인하기 위해) DFS은 다른 사람들이 언급 한 이유 때문에 분명한 선택입니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.