✅ DFS
DFS는 시작 정점에서 갈 수 있는 길을 끝까지 파고들어가며 탐색하는 알고리즘
더 이상 갈 곳이 없으면 되돌아오면서 다른 길을 탐색한다
- 한 방향으로 깊게 탐색
- 방문한 곳을 기록하여 무한루프 방지
- 재귀함수 or 스택으로 구현 가능
✅ 특징
| 사용 구조 | 스택 (보통 재귀로 구현) |
| 탐색 순서 | 한 방향으로 깊게 파고들기 |
| 주로 쓰는 상황 | 모든 경로 탐색, 연결 요소 개수 구하기 |
| 장점 | 구현이 간단하고 직관적임 |
| 단점 | 깊이가 너무 깊으면 재귀 제한에 걸릴 수 있음 |
✅ DFS 기본 구조 (2D 맵 기반)
void dfs(vector<vector<char>>& grid, int x, int y, int N, char color) {
// 방문 처리
grid[y][x] = 0;
// 상하좌우 방향
int dx[] = {0, 0, -1, 1};
int dy[] = {-1, 1, 0, 0};
for (int i = 0; i < 4; ++i) {
int nx = x + dx[i];
int ny = y + dy[i];
// 범위 확인 + 아직 같은 색인 곳만 탐색
if (nx >= 0 && nx < N && ny >= 0 && ny < N && grid[ny][nx] == color) {
dfs(grid, nx, ny, N, color); // 재귀 호출
}
}
}
✅ 단계별 설명
1. 함수 호출 시, 현재 위치 x, y, 색깔 color 받음
void dfs(..., int x, int y, ..., char color)
→ 현재 (x, y) 위치를 기준으로 같은 색을 가진 인접 칸들을 계속 탐색할 것임
2. 방문 처리
grid[y][x] = 0;
→ 현재 칸은 이미 방문했기 때문에 다시 방문하지 않도록 0으로 처리
3. 상하좌우 탐색
int dx[] = {0, 0, -1, 1};
int dy[] = {-1, 1, 0, 0};
→ 순서대로 위, 아래, 왼쪽, 오른쪽으로 탐색
→ x + dx[i], y + dy[i]로 새로운 좌표 만들기
4. 범위 및 조건 확인 후 재귀 호출
if (nx >= 0 && nx < N && ny >= 0 && ny < N && grid[ny][nx] == color) {
dfs(grid, nx, ny, N, color);
}
→ 새 좌표가 격자 범위 안에 있고
→ 아직 방문하지 않았고, 색이 같은 경우
→ → 재귀 호출로 계속 깊이 탐색
✅ 시각적으로 보면
시작점 (1, 1)에서 시작
→ (1,1)이 R이면, 그 주변의 R을 찾고
→ 또 그 주변의 R을 찾고...
→ 더 이상 없으면 백트래킹해서 다른 방향으로 이동
→ 한 색깔 영역을 전부 탐색하면 종료
✅ DFS vs BFS 비교 요약
| 구조 | 스택 / 재귀 | 큐 (queue) |
| 탐색 순서 | 깊게 파고들기 | 가까운 곳부터 차례대로 |
| 구현 방식 | 재귀, 혹은 수동 stack 사용 | queue 사용 |
| 장점 | 코드 간단, 구현 직관적 | 최단 거리 보장 |
| 단점 | 재귀 깊으면 스택 오버플로우 | 메모리 사용량 높을 수 있음 |
✅ Stack 을 활용한 DFS
| 재귀 호출이 깊으면 스택 오버플로우 위험 있음 | 특히 큰 입력(예: 1000x1000)일 때 |
| 반복문만으로 DFS 구현 가능 | 재귀를 피하고자 할 때 유용 |
| 명시적 스택 구조를 이해하는 데 도움 됨 | DFS 작동 방식이 더 잘 보임 |
✅ 기본 구조: 재귀 DFS → 반복문 + stack
#include <iostream>
#include <vector>
#include <stack>
using namespace std;
void dfs_stack(vector<vector<char>>& grid, int x, int y, int N, char color) {
int dx[] = {0, 0, -1, 1}; // 상하좌우
int dy[] = {-1, 1, 0, 0};
stack<pair<int, int>> st;
st.push({x, y});
grid[y][x] = 0; // 방문 처리
while (!st.empty()) {
//DFS는 스택이 빌 때까지 계속함. 하나 꺼내고 → 그 이웃들을 다시 넣고 → 하나 꺼내고 ... 이런 식으로 반복.
auto [cx, cy] = st.top();
st.pop();
for (int i = 0; i < 4; ++i) {
int nx = cx + dx[i];
int ny = cy + dy[i];
// 유효성 검사 + 아직 안 간 곳이면 방문
if (nx >= 0 && nx < N && ny >= 0 && ny < N && grid[ny][nx] == color) {
grid[ny][nx] = 0; // 방문 처리
st.push({nx, ny}); // 다음 탐색할 좌표
}
}
}
}
✅ 주요 흐름 비교
| 재귀 DFS | 스택 DFS |
| dfs(x, y) 함수 안에서 재귀 호출 | stack.push(x, y) 하고 반복문 사용 |
| 함수 콜 스택이 자동으로 관리됨 | stack 컨테이너로 직접 스택을 관리함 |
| 깊게 들어가면 시스템 스택에 쌓임 | 직접 스택에 좌표를 넣고 빼며 탐색함 |
✅ 예시 사용 코드
int countRegion(vector<vector<char>>& grid, int N) {
int cnt = 0;
for (int i = 0; i < N; ++i){
for (int j = 0; j < N; ++j){
if (grid[i][j] != 0){
dfs_stack(grid, j, i, N, grid[i][j]);
cnt++;
}
}
}
return cnt;
}
✅ 요약 비교
| stack | 방문할 위치를 기억해두는 자료구조 (재귀 대신) |
| 방문처리 | 방문한 위치는 0으로 바꾸어 중복 탐색 방지 |
| 4방향 확인 | 인접한 칸을 모두 살펴보고 조건 만족하면 push |
| while 반복 | 스택이 빌 때까지 반복하며 DFS 완성 |
| 왜 먼저 방문 처리 ? | 이미 넣은 좌표를 또 중복해서 넣는 걸 막기 위해서! |
| 재귀 DFS랑 순서가 완전히 똑같은가? | 대부분 동일하나, 스택 DFS는 넣는 순서에 따라 순서가 다를 수 있음 |
| grid[y][x] = 0? | 문자열이 아니라 숫자로 미리 바꾸면 더 명확해짐 (1을 0으로 바꾸는 식) |
✅ 팁: 재귀를 스택으로 바꾸는 요령
- dfs(x, y) → stack.push({x, y})
- return 없음 → while문 끝에서 자동종료
- 방문 순서 보장 필요 없다면 그냥 stack 쓰면 됨
✅ 마무리
- 스택 DFS는 재귀 DFS를 안전하게 바꾼 방식이에요.
- 스택을 직접 다루며 재귀의 내부 구조를 더 명확히 이해할 수 있어요.
- 큰 맵 탐색 문제에 특히 안정적입니다.
'이론공부 > 혼자 공부' 카테고리의 다른 글
| 그리디(Greedy) 알고리즘 | 탐욕법 (0) | 2025.10.28 |
|---|---|
| 다익스트라 알고리즘 (0) | 2025.10.28 |
| 모듈러 연산(Modular Arithmetic) (0) | 2025.10.08 |
| BFS (Breadth-First Search, 너비 우선 탐색) (1) | 2025.06.26 |
| DP(Dynamic Programming) (1) | 2025.06.19 |