- 깊이 우선 탐색(DFS, Depth-First Search)은 그래프의 시작 노드에서 갈 수 있는 한 깊게 탐색하다가 더 이상 갈 곳이 없으면 이전으로 돌아오는 알고리즘임
- 스택(Stack) 또는 재귀를 사용하여 구현하며 모든 경우의 수를 탐색하거나 경로의 특징을 저장해야 할 때 유리함
DFS 알고리즘
알고리즘 개념
- 미로 찾기처럼 한 방향으로 갈 수 있을 때까지 계속 가다가 더 이상 갈 수 없게 되면 가장 가까운 갈림길로 돌아와서 다른 방향으로 다시 탐색을 진행하는 방식임
- 백트래킹(Backtracking)과 밀접한 관련이 있음
동작 흐름
- 초기 상태
- 시작 노드인 1번 노드를 방문하고 재귀 호출을 시작함
- 방문 현황:
[1]
- 깊게 탐색
- 1번 노드와 인접한 2번 노드로 이동
- 방문 현황:
[1 -> 2](1번은 호출 스택에 대기 중)
- 더 깊게 탐색
- 2번 노드와 인접한 3번 노드로 이동
- 방문 현황:
[1 -> 2 -> 3]
- 막다른 길 및 백트래킹
- 3번 노드에서 더 이상 방문하지 않은 인접 노드가 없다면 함수가 종료됨
- 탐색 위치가 3번에서 2번 노드로 돌아옴 (Back)
- 다른 분기 탐색
- 2번 노드에서 아직 방문하지 않은 다른 인접 노드(예: 4번)가 있다면 그 곳으로 이동함
- 방문 현황:
[1 -> 2 -> 4]
판단 기준
- 어떤 문제에서 DFS를 사용해야 하는지 판단하는 기준임
모든 경로 및 해 탐색
- 미로 찾기에서 도달 가능한 모든 경로의 수를 구하거나 조건을 만족하는 해가 존재하는지 확인할 때
경로의 특징 저장
- 경로상의 노드들을 기억해야 하거나 특정 조건을 만족하는 경로만 찾아야 할 때
- ex)
- 경로에 A노드가 포함되면 안 된다
- 경로의 합이 X가 되어야 한다
- ex)
연결성 확인 및 그룹화
- 연결 요소(Connected Component)의 개수를 세거나 서로 연결되어 있는지 확인할 때
순열 및 조합 구현
- N개 중 M개를 고르는 문제 등 조합론적 문제의 기초가 됨
동작 원리
-
DFS는 후입선출(LIFO) 방식의 탐색으로 가장 나중에 방문한 노드에서 다시 깊게 들어가는 방식임
- 초기화
- 시작 노드를 방문 처리함
- 재귀 호출 (Recursive Call)
- 현재 노드와 인접한 노드 중 방문하지 않은 노드가 있다면 그 노드를 방문 처리하고 즉시 재귀 호출로 들어감
- 백트래킹 (Backtracking)
- 더 이상 갈 곳이 없으면 함수가 종료(return)되어 이전 호출 지점인 부모 노드로 돌아감
풀이 접근법
- 문제를 만났을 때 DFS로 해결하기 위한 구체적인 접근 방법임
그래프 모델링
- 노드
- 현재 상태
- 간선
- 상태 전이 (이동)
- 연결 리스트(
ArrayList[])나 2차원 배열(int[][])로 표현 가능
상태 정의 및 방문 체크
visited배열이 가장 중요함- 단순 방문 체크
visited[next] = true- 한 번 방문하면 끝
- 경로 복원이 필요한 경우 (백트래킹)
- 재귀 함수는 반드시 종료 조건이 명확해야 함
1 2 3 4 5 6 7
void dfs(int node) { if (조건 만족) return; for (int next : graph[node]) { // ... } }
시간 복잡도
인접 리스트
- $O(V + E)$
- 모든 정점($V$)을 한 번씩 방문하고 모든 간선($E$)을 한 번씩 검사함
인접 행렬
- $O(V^2)$
- 모든 노드에 대해 $V$번 반복하여 인접 여부를 확인함
결론
- DFS도 BFS와 마찬가지로 $V$가 클 때는 인접 리스트 방식이 효율적임
Java 구현
- DFS는 주로 재귀(Recursion) 방식으로 구현함
기본 구현
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
import java.io.*;
import java.util.*;
public class Main {
// 문제에 필요한 변수 선언 (Static)
static boolean[] visited;
static ArrayList<Integer>[] graph;
static int N, M;
public static void main(String[] args) throws IOException {
// 입력 처리
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st = new StringTokenizer(br.readLine());
// 문제의 기본 파라미터 입력
N = Integer.parseInt(st.nextToken());
M = Integer.parseInt(st.nextToken());
// 초기화
visited = new boolean[N + 1];
graph = new ArrayList[N + 1];
for (int i = 1; i <= N; i++) graph[i] = new ArrayList<>();
// 그래프 구성
for (int i = 0; i < M; i++) {
st = new StringTokenizer(br.readLine());
int u = Integer.parseInt(st.nextToken());
int v = Integer.parseInt(st.nextToken());
graph[u].add(v);
graph[v].add(u); // 양방향의 경우
}
// 알고리즘 실행
dfs(1); // 시작 노드 1
}
private static void dfs(int node) {
visited[node] = true; // 현재 노드 방문 처리
// System.out.print(node + " ");
// 인접 노드 탐색
for (int next : graph[node]) {
if (!visited[next]) {
dfs(next);
}
}
}
}