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


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.