주로 DFS는 BFS가 아닌 그래프에서주기를 찾는 데 사용됩니다. 이유가 있습니까? 둘 다 트리 / 그래프를 횡단하는 동안 노드가 이미 방문되었는지 확인할 수 있습니다.
주로 DFS는 BFS가 아닌 그래프에서주기를 찾는 데 사용됩니다. 이유가 있습니까? 둘 다 트리 / 그래프를 횡단하는 동안 노드가 이미 방문되었는지 확인할 수 있습니다.
답변:
깊이 우선 검색은 더 빨리 역 추적 할 수 있으므로 폭 우선 검색보다 메모리 효율적입니다. 호출 스택을 사용하면 구현하기가 더 쉽지만 스택을 오버플로하지 않는 가장 긴 경로에 의존합니다.
또한 그래프가 방향 이 지정되면 노드를 방문했는지 여부뿐만 아니라 거기에 어떻게 도달했는지 기억해야합니다. 그렇지 않으면주기를 찾았다 고 생각할 수 있지만 실제로는 두 개의 별도 경로 A-> B 만 있지만 이것이 경로 B-> A가 있다는 의미는 아닙니다. 예를 들면
에서 시작하여 BFS를 수행하면 0
주기가 있지만 실제로는주기가없는 것으로 감지합니다.
깊이 우선 검색을 사용하면 하강 할 때 방문한 노드를 표시하고 역 추적 할 때 표시를 해제 할 수 있습니다. 이 알고리즘의 성능 향상에 대한 설명을 참조하십시오.
유향 그래프에서주기를 감지 하는 가장 좋은 알고리즘 은 Tarjan의 알고리즘을 참조 할 수 있습니다.
그래프가 방향이 지정되지 않은 경우 BFS가 합리적 일 수 있습니다 (방향성 그래프로주기를보고하는 BFS를 사용하여 효율적인 알고리즘을 보여줄 수 있습니다!). 여기서 각 "교차 에지"는주기를 정의합니다. 교차 에지가 {v1, v2}
이고 해당 노드를 포함하는 루트 (BFS 트리에서)가 r
이면주기는 r ~ v1 - v2 ~ r
( ~
경로, -
단일 에지)이며 DFS 에서처럼 거의 쉽게보고 될 수 있습니다.
BFS를 사용하는 유일한 이유는 (무 방향) 그래프가 긴 경로와 작은 경로 커버 (즉, 깊고 좁은)를 가질 것이라는 것을 알고있는 경우입니다. 이 경우 BFS는 DFS 스택보다 대기열에 대해 비례 적으로 적은 메모리를 필요로합니다 (물론 여전히 선형).
다른 모든 경우에는 DFS가 분명히 승자입니다. 방향성 그래프와 무 방향성 그래프 모두에서 작동하며주기를보고하는 것은 간단합니다. 조상에서 후손까지의 경로에 뒤쪽 가장자리를 연결하면주기를 얻을 수 있습니다. 대체로이 문제에 대해 BFS보다 훨씬 낫고 실용적입니다.
BFS는주기를 찾는 방향 그래프에서 작동하지 않습니다. A-> B 및 A-> C-> B를 그래프에서 A에서 B 로의 경로로 고려하십시오. BFS는 B가 방문한 경로 중 하나를 따라 가면 말합니다. 다음 경로를 계속 이동하면 표시된 노드 B가 다시 발견되었으므로 사이클이 있습니다. 분명히 여기에는주기가 없습니다.
왜 그런 오래된 질문이 내 피드에 나타 났는지 모르겠지만 이전 답변이 모두 나쁘기 때문에 ...
DFS는 작동 하기 때문에 방향성 그래프에서주기를 찾는 데 사용됩니다. .
DFS에서 모든 정점은 "방문"되며, 정점 방문은 다음을 의미합니다.
해당 정점에서 도달 할 수있는 하위 그래프를 방문합니다. 여기에는 해당 정점에서 도달 할 수있는 추적되지 않은 모든 가장자리를 추적하고 도달 할 수있는 모든 방문하지 않은 정점을 방문하는 것이 포함됩니다.
정점이 완성되었습니다.
중요한 기능은 정점이 완료되기 전에 정점에서 도달 할 수있는 모든 가장자리가 추적된다는 것입니다. 이것은 DFS의 기능이지만 BFS는 아닙니다. 사실 이것이 DFS의 정의입니다.
이 기능으로 인해 주기 의 첫 번째 정점이 시작 되는시기를 알고 있습니다 .
따라서주기가있는 경우 시작되었지만 완료되지 않은 정점 (2)에 대한 가장자리를 찾을 수 있으며 그러한 가장자리를 찾으면주기 (3)가 있음을 보장합니다.
이것이 DFS가 방향성 그래프에서주기를 찾는 데 사용되는 이유입니다.
BFS는 그러한 보장을 제공하지 않으므로 작동하지 않습니다. (BFS 또는 유사한 하위 절차를 포함하는 완벽하게 좋은주기 찾기 알고리즘에도 불구하고)
반면에 무 방향 그래프는 한 쌍의 꼭지점 사이에 두 개의 경로가있을 때마다, 즉 나무가 아닐 때마다주기가 있습니다. 이것은 BFS 또는 DFS 중에 쉽게 감지 할 수 있습니다. 새 꼭지점으로 추적 된 가장자리는 트리를 형성하고 다른 가장자리는주기를 나타냅니다.
나무의 임의의 지점에 사이클을 배치하면 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는 모든 노드를 두 번 방문합니다. Iterative-BFS는 모든 노드를 한 번 방문합니다.
주기를 감지하려면 인접성을 추가하기 전과 후에 노드를 조사해야합니다. 노드에서 "시작"할 때와 노드에서 "마침"할 때 모두입니다.
이를 위해서는 Iterative-BFS에서 더 많은 작업이 필요하므로 대부분의 사람들은 Recursive-DFS를 선택합니다.
예를 들어 std :: stack을 사용한 Iterative-DFS의 간단한 구현에는 Iterative-BFS와 동일한 문제가 있습니다. 이 경우 더미 요소를 스택에 배치하여 노드 작업을 "완료"할 때 추적해야합니다.
Iterative-DFS에서 노드로 "완료"할 때를 결정하기 위해 추가 작업이 필요한 방법에 대한 자세한 내용은이 답변을 참조하십시오 (TopoSort 컨텍스트에서 답변 됨).
이것이 사람들이 노드 처리를 "완료"할 때 결정해야하는 문제에 대해 Recursive-DFS를 선호하는 이유를 설명합니다.