| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | |||
| 5 | 6 | 7 | 8 | 9 | 10 | 11 |
| 12 | 13 | 14 | 15 | 16 | 17 | 18 |
| 19 | 20 | 21 | 22 | 23 | 24 | 25 |
| 26 | 27 | 28 | 29 | 30 | 31 |
Tags
- spring boot
- Context map
- JPA
- 공통 언어
- 안드로이드 스튜디오
- Spring Cloud
- subdomain
- CSRF
- Rabbit MQ
- Hexagonal Architecture
- Ubiquitous Language
- 클린 아키텍처
- event
- Domain Driven
- hexagonal
- clean architecture
- EC2
- 이벤트 기반
- rabbitmq
- Thymeleaf
- 헥사고날
- Spring
- nopasswd
- BOJ
- DTO Projection
- 백준
- android studio
- 헥사고날 아키텍처
- 컨텍스트 맵
- Domain-Driven
Archives
- Today
- Total
취미겸생업
[Algorithm] DFS, 백트래킹 본문
SMALL
DFS
DFS(Depth-First Seach, 깊이 우선 탐색)란

루트 노드에서 시작해, 다음 분기(Branch)로 넘어가기 전, 해당 분기를 완벽하게 탐색하는 방법이다. 노드 순회 시, 깊이를 우선으로 모든 노드를 방문하고자 할 경우 사용한다. 단순 검색 속도 자체는 BFS에 비해 느리다.
특성
- 전위 순회(Pre-Order) 방법 또한 DFS의 일종
- 그래프 탐색의 경우 어떤 노드를 방문했었는지 여부를 반드시 검사해야 함 (검사하지 않을 경우 무한루프 발생)
- 깊이 우선 순위로 모든 노드에 대한 완전 탐색
DFS 과정
요소의 역할
- Stack : 현재 탐색 상태(탐색한 경로와 되돌아갈 경로)를 기억하기 위한 자료구조
- Visited : 한 번 방문한 노드를 다시 방문하지 않기 위한, 모든 노드에 대한 방문 여부 체크리스트
준비 단계
- Visited를 모두 false로 초기화하여 모든 노드에 대한 방문 여부 초기화
탐색 과정
- 루트 노드에서 탐색 시작, Stack에 루트 노드를 Push
- 아래 과정을 반복하고, Stack이 비어있다면 DFS 과정 종료
- Stack에서 노드를 Pop하여 현재 노드로 설정
- 현재 노드가 visited == true라면 a. 단계로 되돌아가 다음 노드를 Pop
- 현재 노드가 visited == false라면, visited = true로 설정하고 필요한 로직을 수행
- visited == false인 모든 이웃 노드를 Stack에 Push
재귀를 이용한 DFS 예시
예시 코드
기본형
노드 구조
/* 노드 구조 1 / \ 2 3 / \ / \ 4 5 6 7 */코드
import java.util.*; public class RecursiveDfsExample { private static Set<Node> visited = new HashSet<>(); public static void main(String[] args) { Node n_4 = new Node(4); Node n_5 = new Node(5); Node n_6 = new Node(6); Node n_7 = new Node(7); Node n_2 = new Node(2, n_4, n_5); Node n_3 = new Node(3, n_6, n_7); Node n_1 = new Node(1, n_2, n_3); dfs(n_1); } public static void dfs(Node current) { if(current == null) { return; // 루트 노드로 되돌아오거나 끝 노드일 경우 추가 탐색 종료 } if(visited.contains(current)) { return; // 이미 방문한 노드일 경우 pop } else { visited.add(current); System.out.print(current.data + " "); } if(!visited.contains(current.left)) { dfs(current.left); // 다음 노드 Stack push } if(!visited.contains(current.right)) { dfs(current.right); } } public static class Node { private int data; Node left; Node right; public Node(int data, Node left, Node right) { this.data = data; this.left = left; this.right = right; } public Node(int data) { this.data = data; } } }
스택을 이용한 DFS 예시
스택을 활용한 명시적 DFS는 visited 자료구조를 제어하는 타이밍이나 탐색 순서를 Stack을 통해 직접 제어해야 하기 때문에 재귀보다 훨씬 복잡하다.
예시 코드
기본형
노드 구조
/* 노드 구조 1 / \ 2 3 / \ / \ 4 5 6 7 */코드
import java.util.*; public class StackDfsExample { public static void main(String[] args) { Stack<Node> stack = new Stack<>(); Set<Node> visited = new HashSet<>(); Node n_4 = new Node(4); Node n_5 = new Node(5); Node n_6 = new Node(6); Node n_7 = new Node(7); Node n_2 = new Node(2, n_4, n_5); Node n_3 = new Node(3, n_6, n_7); Node n_1 = new Node(1, n_2, n_3); stack.push(n_1); // 1. while(!stack.isEmpty()) { // 2. Node current = stack.pop(); // 2-a. if(visited.contains(current)) { // 2-b. continue; } else { // 2-c. visited.add(current); System.out.print(current.data + " "); } // 2-d. if(current.right != null && !visited.contains(current.right)) { stack.push(current.right); } if(current.left != null && !visited.contains(current.left)) { stack.push(current.left); } } } public static class Node { private int data; Node left; Node right; public Node(int data, Node left, Node right) { this.data = data; this.left = left; this.right = right; } public Node(int data) { this.data = data; } } }
백트래킹
백트래킹
DFS 또는 BFS 알고리즘 등 탐색 알고리즘 중 왔던 길을 되돌아가는 기법을 사용하는 것을 말한다.
순수 DFS는 단순히 모든 노드를 탐색하기만 한다면, 백트래킹은 기존에 방문한 노드의 방문 상태를 해제하여 재방문하는 등, 탐색 순서와 조합을 유연하게 제어하는 접근이 가능하다.
예제 코드
1~N 중 k개 뽑아 합 출력하기 (Combination-조합, 순서 상관 없음)
아이디어
/* <N개의 카드 중 k개 뽑아 합 출력하기> 1, 2, 3, 4에 대해 3개 - 1 + 2 + 3 = 6 - 1 + 2 + 4 = 7 - 1 + 3 + 4 = 8 - 2 + 3 + 4 = 9 */코드
for 문의 초기값 설정을 통해 다음 노드 방향에 대한 가지치기
탐색하지 않아도 될 노드는 탐색하지 않음
public class DfsCombination { static int n, k; static int[] selected; public static void main(String[] args) { Scanner sc = new Scanner(System.in); n = sc.nextInt(); k = sc.nextInt(); selected = new int[k]; combination(0, 1); } static void combination(int start, int depth) { if (depth == k) { int sum = 0; for(int num : selected){ sum += num; } System.out.println(sum); return; } for (int i = start; i <= n; i++) { selected[depth] = i; combination(i + 1, depth + 1); } } }
1~N 중 k 자릿수 만들어 출력하기 (Permutation-순열, 순서 상관 있음)
아이디어
/* <1~N개의 카드 중 k개 뽑아 숫자 만들기 (순열)> 1, 2, 3, 4에 대해 3개 뽑기 1 2 /| \ /| \ 2 3 4 1 3 4 /| |\ |\ /| |\ |\ 3 4 2 4 2 3 3 4 1 4 1 3 ... - 123, 124, 132, 134, 142, 143 - 213, 214, 231, 234, 241, 243 - 312, 314, 321, 324, 341, 342 - 412, 413, 421, 423, 431, 432 */코드
public class DfsPermutation { static int n; static int k; static int[] selected; static boolean[] visited; public static void main(String[] args) { Scanner sc = new Scanner(System.in); n = sc.nextInt(); k = sc.nextInt(); visited = new boolean[n+1]; selected = new int[k]; permutation(0); } static void permutation(int depth) { if (depth == k) { System.out.println(Arrays.toString(selected)); return; } for (int i = 1; i <= n; i++) { if (!visited[i]) { visited[i] = true; selected[depth] = i; permutation(depth + 1); visited[i] = false; } } } }
N개 중 k 자릿수 만들어 출력하기 (Permutation 응용)
public class DfsPractice { static int k; static int[] nums; static int[] selected; static boolean[] visited; public static void main(String[] args) { Scanner sc = new Scanner(System.in); k = sc.nextInt(); nums = new int[]{1, 3, 5, 7, 9}; selected = new int[k]; visited = new boolean[nums.length]; dfs(0); } static void dfs(int depth) { if(depth == k) { for(int index : selected) { System.out.print(nums[index] + " "); } System.out.println(""); return; } for(int i=0; i < nums.length; i++) { if(!visited[i]) { visited[i] = true; selected[depth] = i; dfs(depth + 1); visited[i] = false; } } } }
LIST
