역 추적과 깊이 우선 검색의 차이점은 무엇입니까?


105

역 추적과 깊이 우선 검색의 차이점은 무엇입니까?

답변:


98

역 추적 은보다 일반적인 목적의 알고리즘입니다.

깊이 우선 검색 은 트리 구조 검색과 관련된 특정 형태의 역 추적입니다. Wikipedia에서 :

하나는 루트에서 시작하여 (그래프 케이스에서 일부 노드를 루트로 선택) 역 추적하기 전에 각 분기를 따라 가능한 한 멀리 탐색합니다.

역 추적을 트리 작업 수단의 일부로 사용하지만 트리 구조로 제한됩니다.

그러나 역 추적은 논리적 트리인지 여부에 관계없이 도메인의 일부를 제거 할 수있는 모든 유형의 구조에서 사용할 수 있습니다. Wiki 예제는 체스 판과 특정 문제를 사용합니다. 특정 동작을보고 제거한 다음 가능한 다음 동작으로 되돌아가 제거 할 수 있습니다.


13
여기 고대 게시물에 대한 응답. 좋은 대답이지만 ... 체스 판 문제도 나무로 표현 될 수 없습니까? :-) 주어진 조각에 대한 체스 판의 어떤 위치에서, 미래로 확장 가능한 이동 트리가 없습니까? 내 일부는 역 추적을 사용할 수 있고 나무로도 모델링 할 수있는 경우처럼 느껴지지만 그 직감이 옳은지 확실하지 않습니다.
The111

4
@ The111 : 실제로 모든 검색 문제는 트리로 표시 될 수 있습니다. 가능한 부분 솔루션마다 노드가 있고 각 노드에서이 상태에서 만들 수있는 하나 이상의 가능한 대안 선택에 대한 에지가 있습니다. 역 추적은 일반적으로 재귀 중에 생성되는 (일반적으로 암시 적) 검색 트리의 DFS가 진실에 가장 가깝다는 것을 의미한다는 lcn의 대답이라고 생각합니다.
j_random_hacker

5
@j_random_hacker 따라서 DFS는 트리 (또는보다 일반적으로 그래프)를 탐색하는 방법이고 역 추적은 문제를 해결하는 방법입니다 (가지 치기와 함께 DFS를 사용함). :-)
The111

혼란스럽게도 Wikipedia 는 역 추적을 깊이 우선 검색 알고리즘으로 설명 하여이 두 개념을 결합한 것 같습니다.
Anderson Green

29

다른 관련 질문에 대한 답변이 더 많은 통찰력을 제공 한다고 생각 합니다 .

나에게 역 추적과 DFS의 차이점은 역 추적은 암시 적 트리를 처리하고 DFS는 명시 적 트리를 처리한다는 것입니다. 이것은 사소한 것처럼 보이지만 많은 것을 의미합니다. 역 추적을 통해 문제의 검색 공간을 방문하면 묵시적 트리가 탐색되고 그 중간에서 정리됩니다. 그러나 DFS의 경우 처리하는 트리 / 그래프는 명시 적으로 구성되어 있으며 허용되지 않는 경우는 검색이 완료되기 전에 이미 버려졌습니다.

따라서 역 추적은 암시 적 트리의 경우 DFS이고 DFS는 가지 치기없이 역 추적합니다.


역 추적을 암시 적 트리를 처리하는 것으로 생각하는 것이 혼란 스럽습니다. 이 글을 처음 읽었을 때 동의했지만 더 깊이 파헤쳐 보면 "암시 적 트리"가 정말 재귀 트리라는 것을 깨달았습니다. 문자열을 순열하는 것과 같이 역 추적을 사용하는 예를 들어 보면 논리적 트리가 없습니다 (암시 적 트리 없음). 문제와 관련하여 무엇이든간에 증분 문자열 작성 프로세스를 모델링하는 재귀 트리가 있습니다. 가지 치기는 완전 무차별 대입이 수행되는 재귀 트리에 수행되는 가지 치기입니다 ... (계속 예정)
Gang Fang

(계속) 예를 들어 "답변"문자열의 모든 순열을 인쇄하고 세 번째 문자가 문자 "a"여야한다고 가정합니다. 재귀 트리의 처음 2 개 수준은 O (n!)을 따르지만 3 번째 수준에서는 "a"를 추가하는 분기를 제외한 모든 분기가 정리됩니다 (역 추적).
Gang Fang

6

역 추적은 일반적으로 DFS와 검색 정리로 구현됩니다. 길을 따라 부분 솔루션을 구성하는 검색 공간 트리 깊이를 탐색합니다. 무차별 대입 DFS는 실제로 의미가없는 모든 검색 결과를 구성 할 수 있습니다. 이것은 또한 모든 솔루션 (n! 또는 2 ^ n)을 구성하는 데 매우 비효율적 일 수 있습니다. 따라서 실제로 DFS를 수행하는 것처럼 실제 작업의 맥락에서 말이되지 않는 부분 솔루션도 제거하고 유효한 최적 솔루션으로 이어질 수있는 부분 솔루션에 집중해야합니다. 이것이 실제 역 추적 기법입니다. 가능한 한 빨리 부분 솔루션을 버리고 한 발 뒤로 물러나서 로컬 최적을 다시 찾으려고합니다.

BFS를 사용하여 검색 공간 트리를 탐색하고 그 과정에서 역 추적 전략을 실행하는 것을 멈추지 않지만 실제로는 검색 상태를 대기열에 레이어별로 저장해야하고 트리 너비가 높이까지 기하 급수적으로 증가하기 때문에 실제로는 의미가 없습니다. 그래서 우리는 매우 빠르게 많은 공간을 낭비 할 것입니다. 이것이 나무가 일반적으로 DFS를 사용하여 횡단하는 이유입니다. 이 경우 검색 상태는 스택 (호출 스택 또는 명시 적 구조)에 저장되며 트리 높이를 초과 할 수 없습니다.


5

일반적으로 깊이 우선 검색은 값을 찾기 위해 실제 그래프 / 트리 구조를 반복하는 방법 인 반면 역 추적은 솔루션을 찾기 위해 문제 공간을 반복하는 방법입니다. 역 추적은 나무와 반드시 관련이있는 것은 아닌보다 일반적인 알고리즘입니다.


5

DFS는 특별한 형태의 역 추적입니다. 역 추적은 DFS의 일반적인 형태입니다.

DFS를 일반적인 문제로 확장하면 역 추적이라고 부를 수 있습니다. 역 추적을 사용하여 트리 / 그래프 관련 문제를 해결하면 DFS라고 부를 수 있습니다.

그들은 알고리즘 측면에서 동일한 아이디어를 가지고 있습니다.


DFS와 역 추적 사이의 관계는 사실 그 반대입니다. 자세한 내용은 내 응답을 확인하십시오.
KGhatak


5

IMHO, 대부분의 답변은 대체로 부정확하거나 확인할 참조가 없습니다. 그래서 참조와 함께 매우 명확한 설명을 공유하겠습니다 .

첫째, DFS는 일반적인 그래프 순회 (및 검색) 알고리즘입니다. 따라서 모든 그래프 (또는 포리스트)에 적용 할 수 있습니다. 트리는 특별한 종류의 그래프이므로 DFS는 트리에서도 작동합니다. 본질적으로, 그것이 나무 나 그와 비슷한 것에 만 효과가 있다고 말하지 말자.

[1]에 따르면 Backtracking은 주로 공간 (메모리) 절약에 사용되는 특별한 종류의 DFS입니다. 이러한 종류의 그래프 알고리즘에서는 인접 목록 표현을 사용 하고 노드의 모든 인접 이웃 ( 트리의 경우 직계 자식 ) 을 방문하기 위해 반복 패턴을 사용하는 데 익숙하기 때문에 제가 언급하려는 구분은 혼란스러워 보일 수 있습니다. , 우리는 종종 get_all_immediate_neighbors 의 잘못된 구현 이 기본 알고리즘의 메모리 사용에 차이를 일으킬 수 있음을 무시합니다 .

또한 그래프 노드에 분기 계수 b와 지름 h ( 나무의 경우 트리 높이 )가있는 경우 노드를 방문하는 각 단계에서 모든 인접 이웃을 저장하면 메모리 요구 사항은 big-O (bh) 입니다. 그러나 한 번에 하나의 (즉시) 이웃 만 가져와 확장하면 메모리 복잡성이 big-O (h)로 줄어 듭니다 . 전자의 구현 유형을 DFS 라고 하고 후자 의 구현을 역 추적 이라고 합니다.

이제 고급 언어로 작업하는 경우 실제로 DFS를 가장하여 역 추적을 사용하고있을 가능성이 높습니다. 더욱이, 매우 큰 문제 세트에 대해 방문한 노드를 추적하는 것은 실제로 메모리 집약적 일 수 있습니다. get_all_immediate_neighbors (또는 무한 루프에 들어 가지 않고 노드 재 방문을 처리 할 수있는 알고리즘) 의 신중한 설계를 요구합니다 .

[1] Stuart Russell 및 Peter Norvig, 인공 지능 : 현대적인 접근 방식, 3rd Ed


2

깊이 우선은 트리를 탐색하거나 검색하는 알고리즘입니다. 보다 여기 . 역 추적은 솔루션 후보가 형성되고 나중에 이전 상태로 역 추적하여 폐기 될 때마다 사용되는 훨씬 더 광범위한 용어입니다. 를 참조하십시오 여기 . 깊이 우선 검색은 역 추적을 사용하여 분기를 먼저 검색하고 (솔루션 후보) 성공하지 못한 경우 다른 분기를 검색합니다.


2

DFS는 그래프를 탐색하거나 탐색하는 방법을 설명합니다. 그것은 주어진 선택을 가능한 한 깊게한다는 개념에 초점을 맞추고 있습니다.

역 추적은 일반적으로 DFS를 통해 구현되지만 가능한 한 빨리 타협하지 않는 검색 부분 공간을 정리하는 개념에 더 중점을 둡니다.


1

A의 깊이 우선 탐색 , 당신은 트리의 루트에서 시작하여 다음 각 분기를 따라 멀리 당신을 탐구 철수 이후의 각 부모 노드와 그것의 아이를 횡단

역 추적 은 목표의 끝에서 시작하여 점진적으로 뒤로 이동하여 점진적으로 솔루션을 구축하는 일반화 된 용어입니다.


4
역 추적은 끝에서 시작하여 뒤로 이동하는 것을 의미하지 않습니다. 막 다른 골목이 발견되면 역 추적하기 위해 방문한 노드의 로그를 유지합니다.
Günther Jena

1
"끝에서 시작해서 ...", 허 !!
7kemZmani

1

IMO, 역 추적의 특정 노드에서 먼저 각 자식으로 분기를 깊이 시도하지만 자식 노드로 분기하기 전에 이전 자식의 상태를 "삭제"해야합니다 (이 단계는 back과 동일합니다. 부모 노드로 이동). 즉, 각 형제 상태는 서로 영향을주지 않아야합니다.

반대로 일반 DFS 알고리즘에서는 일반적으로이 제약 조건이 없으므로 다음 형제 노드를 구성하기 위해 이전 형제 상태를 지울 필요가 없습니다.


1

아이디어-어느 지점에서 시작하여 원하는 끝점인지 확인하고, 그렇다면 해결책이 다음 가능한 모든 위치로 이동하고 더 이상 갈 수없는 경우 이전 위치로 돌아가서 현재를 표시하는 다른 대안을 찾습니다. 길은 우리를 해결책으로 이끌지 않을 것입니다.

이제 역 추적과 DFS는 2 개의 다른 추상 데이터 유형에 적용된 동일한 아이디어에 주어진 2 개의 다른 이름입니다.

아이디어가 행렬 데이터 구조에 적용되면 역 추적이라고합니다.

동일한 아이디어가 트리 나 그래프에 적용되면 DFS라고합니다.

여기서 진부한 것은 행렬이 그래프로 변환 될 수 있고 그래프가 행렬로 변환 될 수 있다는 것입니다. 그래서 우리는 실제로 아이디어를 적용합니다. 그래프에서는 DFS라고하고 행렬에서는 역 추적이라고합니다.

두 알고리즘의 아이디어는 동일합니다.


0

역 추적은 특정 종료 조건이있는 심도 우선 검색입니다.

결정을 내리는 각 단계에 대해 결정을 내리는 미로를 통과하는 것을 고려하십시오. 그 결정은 호출 스택에 대한 호출입니다 (깊이 첫 번째 검색을 수행합니다) ... 끝에 도달하면 경로를 반환 할 수 있습니다. 그러나 막 다른 골목에 도달하면 특정 결정에서 돌아와 본질적으로 호출 스택의 함수에서 돌아오고 싶습니다.

그래서 역 추적을 생각할 때

  1. 상태
  2. 결정
  3. 기본 사례 (종료 조건)

여기서 역 추적에 대한 내 비디오에서 설명합니다. .

역 추적 코드 분석은 다음과 같습니다. 이 역 추적 코드에서 특정 합계 또는 목표를 생성하는 모든 조합을 원합니다. 따라서 내 호출 스택을 호출하는 3 가지 결정이 있습니다. 각 결정에서 목표 번호에 도달하기위한 경로의 일부로 번호를 선택하거나 해당 번호를 건너 뛰거나 선택하고 다시 선택할 수 있습니다. 그런 다음 종료 조건에 도달하면 역 추적 단계는을 반환하는 것 입니다. 반환은 호출 스택의 해당 호출에서 벗어나기 때문에 역 추적 단계입니다.

class Solution:    

"""

Approach: Backtracking 

State
    -candidates 
    -index 
    -target 

Decisions
    -pick one --> call func changing state: index + 1, target - candidates[index], path + [candidates[index]]
    -pick one again --> call func changing state: index, target - candidates[index], path + [candidates[index]]
    -skip one --> call func changing state: index + 1, target, path

Base Cases (Termination Conditions)
    -if target == 0 and path not in ret
        append path to ret
    -if target < 0: 
        return # backtrack 

"""

def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
    """
    @desc find all unique combos summing to target
    @args
        @arg1 candidates, list of ints
        @arg2 target, an int
    @ret ret, list of lists 
    """
    if not candidates or min(candidates) > target: return []

    ret = []
    self.dfs(candidates, 0, target, [], ret)
    return ret 

def dfs(self, nums, index, target, path, ret):
    if target == 0 and path not in ret: 
        ret.append(path)
        return #backtracking 
    elif target < 0 or index >= len(nums): 
        return #backtracking 


    # for i in range(index, len(nums)): 
    #     self.dfs(nums, i, target-nums[i], path+[nums[i]], ret)

    pick_one = self.dfs(nums, index + 1, target - nums[index], path + [nums[index]], ret)
    pick_one_again = self.dfs(nums, index, target - nums[index], path + [nums[index]], ret)
    skip_one = self.dfs(nums, index + 1, target, path, ret)
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.