목차
- 그래프 순회 Graph Traversals
- 순회를 위한 그래프 준비
- 깊이 우선 탐색(Depth First Search : DFS)
- 백트래킹 Backtracking
- 넓이 우선 탐색(Breadth-First Search : BFS)
그래프 순회 Graph Traversals
그래프 순회: 그래프 탐색 Search 이라고도 불리우며 그래프의 각 정점을 방문 하는 과정을 말한다.
- DFS: 깊이 우선 탐색 Depth First Search
- BFS: 너비 우선 탐색 Breadth-First Search의 2가지 알고리즘이 있음

순회를 위한 그래프 준비
파이썬의 딕셔너리 자료형으로 구현 
 
- 키: 출발 노드
- 값: 도착 노드
graph = { 
 A: [B, C, D], 
 B: [E], 
 C: [E], 
 D: [], 
 E: [F, G], 
 F: [], 
 G: [C]
 } 
- 모든 정점들을 방문한 후 탐색을 종료함
- 순차적인 프로그램보다는 DFS 알고리즘을 재귀(recursive) 알고리즘으로 구현하는 것이 좋음
- 재귀 알고리즘으로 구현할 경우에는 스택(stack)을 사용함
깊이 우선 탐색(Depth First Search : DFS)
맹목적 탐색 기법 중 하나로, 자식 노드를 확장할 수 있을 때까지 깊이 우선으로 탐색을 진행하는 방법 
 일반적으로 BFS에 비해 더 널리 쓰임 
 코딩 테스트 시에도 대부분의 그래프 탐색은 DFS로 구현하게 될 것임
- 주로 스택으로 구현하거나 재귀로 구현 - 일반적으로 DFS는 스택으로 구현
- 재귀를 이용하면 좀더 간단하게 구현 가능(코테 시 더 많이 쓰임)
 
- 이후에 살펴볼 백트래킹을 통해 뛰어난 효용을 보임
재귀 구현
수도코드
DFS(G, v) 
 label v as discovered 
 for all directed edges from v to w that are in G.adjacentEdges(v) do 
  if vertex w is not labeled as discovered then 
   recursively call DFS(G, w)
(해석)
- 정점 v 의 모든 인접 유향 Directed 간선들을 반복하라고 표기됨
Python 구현
def recursive_dfs(v, discovered=[]):
 # 방문한 노드 기록
 discovered.append(v) 
 for w in graph[v]:
  #  현재 방문한 노드와 연결되어 있는 노드 (w)
  if w not in discovered:
   # w가 방문되지 않았을 경우에 아래 실행
   discovered = recursive_dfs(w, discovered) 
  return discovered

[탐색 결과]
>>> f'recursive dfs: {recursive_dfs(1)}'
'recursive dfs: [A, B, E, F, G, C, D]'
스택 구현
수도코드
DFS-iterative(G, v) 
    let S be a stack 
    S.push(v) 
    while S is not empty do 
        v = S.pop() 
        if v is not labeled as discovered then 
            label v as discovered 
            for all edges from v to w in G.adjacentEdges(v) do
                S.push(w)
Python
def iterative_dfs(start_v):
    discovered = [] 
    stack = [start_v] 
    while stack:
        v = stack.pop()
        if v not in discovered:
            discovered.append(v) 
            for w in graph[v]:
                stack.append(w) 
        return discovered

[탐색 결과]
>>> f'iterative dfs: {iterative_dfs(1)}'
'iterative dfs: [A,D,C,E,G,F,B]'
평가
- 앞서 코드가 길고 빈틈없어 보이는 재귀 구현에 비해 우아함은 떨어지지만
- 좀 더 직관적 -> 이해하기는 훨씬 더 쉬움
- 실행 속도 또한 더 빠른 편
대부분의 경우 재귀 구현과 반복구현 동시구현 가능
- 서로 바꿔서 알고리즘을 구현할 자유롭게 바꿔가며 익숙해질 때까지 꾸준히 연습 필요
[똑같은 DFS인데 순서가 다른 이유]
- 재귀 DFS: 사전식 순서 Lexicographical Order로 방문
- 반복 DFS는 역순으로 방문
- 스택으로 구현: 가장 마지막에 삽입된 노드부터 꺼내서 반복해서
백트래킹 Backtracking
해결책에 대한 후보를 구축해 나아가다 가능성이 없는 후보를 포기(백트랙 Backtrack )해 정답을 찾아가는 범용적인 알고리즘
탐색을 하다가 더 갈 수 없으면 왔던 길을 되돌아가 다른 길을 찾는다는데서 유래
가보고 되돌아오고를 반복: 운이 좋으면 시행착오를 덜 거치고 목적지에 도착할 수 있지만 최악의 경우에는 모든 경우를 다거친 다음에 도착할 수 있다.
주로 재귀로 구현
- 제약 충족 문제 Constraint Satisfaction Problems 에 특히 유용
DFS(깊이 우선 탐색)를 이야기하다 보면 항상 백트래킹이라는 단어가 함께 나옴
- 백트래킹은 DFS보다 좀 더 광의의 의미를 지님.
- 백트래킹은 DFS와 같은 방식 으로 탐색하는 모든 방법을 뜻하며, DFS는 백트래킹의 골격을 이루는 알고리즘.
탐색해야 하는 가지가 많은 트리
- 브루트 포스로 전체 트리를 탐색한다면
- 매우 긴 시간이 소요
- 트리의 가지치기 Pruning 이용
- 하지만 그림 12-10과 같이 DFS로 탐색을 시도하면
- 가능성이 없는 후보는 즉시 포기하고 백트래킹
- 트리의 불필요한 거의 대부분을 버릴 수 있음
제약 충족 문제
= Constraint Satisfaction Problems 
 이하 CSP 
 백트래킹이 필수적. 
 제약 충족 문제: 수많은 제약 조건 Constraints 을 충족하는 상태 States 를 찾아내는 수학 문제
넓이 우선 탐색(Breadth-First Search : BFS)
그래프의 최단 경로를 구하는 문제 등에 사용됨
- 주로 큐로 구현
너비 우선 탐색 
 DFS보다 쓰임새는 적음 
 최단 경로를 찾는 다익스트라 알고리즘 등에 매우 유용하게 쓰임
큐 구현
수도코드
스택을 이용하는 DFS와 달리, BFS를 반복 구조로 구현할 때는 큐를 이용
BFS(G, start_v) 
 let Q be a queue 
 label start_v as discovered 
 Q.enqueue(start_v) 
 while Q is not empty do 
  v := Q.dequeue() 
  if v is the goal then 
   return v 
  for all edges from v to w in G.adjacentEdges(v) do
   if w is not labeled as discovered then 
    label w as discovered 
    w.parent := v 
    Q.enqueue(w)
Python 구현
def iterative_bfs(start_v):
 discovered = [start_v] 
 queue = [start_v]
 while queue:
  v = queue.pop(0) 
  for w in graph[v]:
   if w not in discovered:
    discovered.append(w) 
    queue.append(w) 
  return discovered

[탐색 결과]
>>> f'iterative bfs: {iterative_bfs(1)}'
'iterative bfs: [A,B,C,D,E,F,G]'
결과
- 리스트 자료형을 사용했지만 pop(0) 과 같은 큐의 연산만을 사용.
-  따라서 큐를 사용 하는 것과 사실상 동일 
- 좀 더 최적화를 위해서는 데크 같은 자료형을 사용해 보는 것을 고민 가능
BFS의 장점
- 노드의 수가 적고 깊이가 얕은 경우 빠르게 동작할 수 있다.
- 단순 검색 속도가 깊이 우선 탐색(DFS)보다 빠름 
 3 .너비를 우선 탐색하기에 답이 되는 경로가 여러개인 경우에도 최단경로임을 보장한다.
- 최단경로가 존재한다면 어느 한 경로가 무한히 깊어진다해도 최단경로를 반드시 찾을 수 있다.
BFS의 단점
- 재귀호출의 DFS와는 달리 큐에 다음에 탐색할 정점들을 저장해야 하므로 저장공간이 많이 필요하다.
- 노드의 수가 늘어나면 탐색해야하는 노드 또한 많아지기에 비현실적이다.
재귀 구현 불가
이번에는 BFS를 재귀로 풀이해볼 수 있을까? 
 사실 많은 이가 혼동하는 부분인데 BFS는 재귀로 동작하지 않음 
 큐를 이용하는 반복 구현만 가능