DFS는 그래프 탐색 알고리즘 중 하나로, "가능한 한 깊이 들어간 후, 막히면 돌아와 다른 경로를 탐색하는" 방식이다.
아래와 같은 그래프에서 1번 노드부터 탐색을 시작한다고 가정한다.
간선: (1-2), (1-3), (2-4), (3-5)
시작 노드: 1
1
/ \
2 3
/ \
4 5
1에서 시작한다. (방문: 1)1과 연결된 2와 3 중 2로 깊게 들어간다. (방문: 1, 2)2와 연결된 4로 더 깊게 들어간다. (방문: 1, 2, 4)4는 더 이상 갈 곳이 없다. (막다른 길)2로 돌아온다(백트래킹). 2도 4 외에 방문하지 않은 곳이 없으므로 1로 돌아온다.1에서 아직 방문하지 않은 3으로 탐색을 시작한다. (방문: 1, 2, 4, 3)3과 연결된 5로 깊게 들어간다. (방문: 1, 2, 4, 3, 5)5는 더 이상 갈 곳이 없다. 3으로 돌아오고, 1로 돌아오며 탐색이 종료된다.최종 탐색 순서: 1 → 2 → 4 → 3 → 5
DFS의 핵심은 그래프(지도)와 탐색 방식(스택)을 분리해서 생각하는 것이다.
아래 코드의 ArrayList<Integer>[] graph는 DFS 탐색을 위한 '스택'이 아니다.
이것은 노드 간의 연결 상태(간선)를 저장하는 인접 리스트(Adjacency List) 방식으로, 탐색할 '지도' 그 자체이다.
// n+1개의 '방'을 가진 배열(건물)을 만든다.
ArrayList<Integer>[] graph = new ArrayList[n+1];
// 각 '방'을 초기화한다. (List를 넣는다)
for (int i = 1; i <= n; i++){
graph[i] = new ArrayList<>();
}
// 1번과 2번이 연결됨 (양방향)
graph[1].add(2);
graph[2].add(1);
지도를 탐색하는 방법은 두 가지이며, 둘 다 본질적으로 스택 원리를 따른다.
① 재귀 함수 (암묵적 스택)
가장 일반적이고 직관적인 구현 방식이다.
static int dfs(int node, ArrayList<Integer>[] graph, boolean[] visited, int count){
visited[node] = true; // 1. 현재 노드 방문 처리
count++;
for (int next : graph[node]) { // 2. 현재 노드와 연결된 노드들 순회
if (!visited[next]) { // 3. 아직 방문 안 한 곳이 있다면
// 4. 즉시 그곳으로 깊게 들어간다 (재귀 호출)
count = dfs(next, graph, visited, count);
}
}
// 5. (for문이 끝나면) 더 이상 갈 곳이 없으므로 함수가 종료되고,
// 이전 호출 지점으로 '돌아간다' (백트래킹)
return count;
}
이 코드에는 Stack 객체가 보이지 않지만, 함수가 자기 자신을 호출(dfs(next, ...) )할 때마다 자바의 호출 스택(Call Stack)이 사용된다.
return으로 종료되면 스택에서 해당 정보가 빠져나오며 이전 함수로 돌아간다. (Pop)② 반복 + 명시적 스택 (Iterative DFS)
재귀가 아닌 Stack 자료구조를 직접 만들어서 구현할 수도 있다.
Stack<Integer> stack = new Stack<>();
boolean[] visited = new boolean[n+1];
// 1. 시작 노드를 스택에 넣는다.
stack.push(1);
visited[1] = true;
while (!stack.isEmpty()) {
// 2. 스택의 최상단을 꺼낸다(방문).
int node = stack.pop();
// 3. 연결된 노드를 탐색한다.
for (int next : graph[node]) {
if (!visited[next]) {
visited[next] = true;
// 4. 방문할 곳을 스택에 넣는다 (나중에 방문)
stack.push(next);
}
}
}
인접 리스트로 지도를 그리고, 재귀 함수를 이용해 DFS를 구현한 예시
https://velog.io/@seha01130/백준JAVA-2606번-바이러스
ArrayList[])나 인접 행렬(int[][]) 등으로 저장한다. (지도를 그리는 행위)Stack 자료구조(명시적 스택)를 이용해 구현한다. (지도를 탐색하는 행위)결론부터 말하면, "일단 재귀로 시도하고, 스택 오버플로우가 나면 반복문으로 바꾼다" 가 가장 현실적인 전략이다.
DFS(깊이 우선 탐색)는 본질적으로 '재귀적인' 알고리즘이므로, 재귀로 짜는 것이 훨씬 자연스럽고 코드가 깔끔하다.
재귀는 컴퓨터의 '함수 호출 스택'을 암묵적으로 사용하는 방식이다.
언제 쓰나요?
장점:
단점:
반복문은 우리가 Stack 자료구조를 직접 만들어서 '함수 호출 스택'을 흉내 내는 방식이다.
언제 쓰나요?
장점:
Stack은 '힙(Heap)' 메모리 영역을 사용한다. 이 영역은 '함수 호출 스택' 영역보다 훨씬 커서, 웬만한 크기의 데이터는 모두 처리할 수 있다.단점:
push, pop 하고, 방문 처리를 언제 해야 할지(스택에 넣을 때? 뺄 때?) 직접 관리해야 해서 헷갈리기 쉽다.일단 재귀로 짠다.
제출해보고 StackOverflowError가 나는지 확인한다.
에러가 나면, 그때 가서 반복문 + 스택 버전으로 바꾼다.
평소에는 재귀로 풀고, "재귀 깊이가 너무 깊다"는 것이 문제의 핵심 함정일 때만 반복문+스택을 쓰는 것으로 결정.