DFS와 BFS는 정의만 외우면 계속 헷갈린다.
실제로 코테에서 중요한 건
“DFS는 깊이 우선, BFS는 너비 우선” 같은 문장 자체가 아니라,
문제를 봤을 때 어느 쪽이 더 자연스러운지 판단하는 기준이다.
정리하면 이렇다.
- 최단거리 / 레벨 / 한 칸씩 퍼지는 구조면 보통 BFS가 맞고
- 연결된 덩어리 처리 / 끝까지 파고들기 / 완전탐색이면 DFS가 더 자연스러운 경우가 많다
즉, DFS/BFS는 알고리즘 이름을 외우는 문제가 아니라
문제 구조를 보고 고르는 문제에 가깝다.
이 글에서는 그 기준만 정리해보려고 한다.
🔗 주제
DFS와 BFS 차이 정리
개념 요약
둘 다 탐색이다.
즉, 그래프나 격자에서 어떻게 방문할지를 정하는 방식이다.
차이는 방문 순서다.
DFS
- 한 방향으로 끝까지 들어간다
- 막히면 돌아온다
- 스택 흐름이랑 잘 맞는다
BFS
- 가까운 곳부터 넓게 퍼진다
- 먼저 들어간 걸 먼저 꺼낸다
- 큐 흐름이랑 잘 맞는다
이게 정의인데,
코테에서는 여기서 한 단계 더 나가서 봐야 한다.
✅ 핵심 아이디어
실전에서는 보통 이렇게 구분하면 된다.
BFS가 더 자연스러운 경우
- 최단거리 문제
- 시작점에서 몇 단계 떨어졌는지 중요할 때
- 한 칸씩 퍼지는 구조
- 같은 가중치로 이동할 때
DFS가 더 자연스러운 경우
- 연결된 영역 개수 세기
- 한 덩어리 전체를 방문 처리할 때
- 경로를 끝까지 따라가야 할 때
- 재귀 구조가 더 직관적일 때
즉, 핵심은
방문 순서보다 문제 목표다.
대표 코드
DFS 기본형
def dfs(graph, start):
need_visit = [start]
visited = []
while need_visit:
node = need_visit.pop()
if node not in visited:
visited.append(node)
need_visit.extend(graph[node])
return visited
이 구조는 리스트를 스택처럼 쓰는 형태다.
- 뒤에 넣고
- 뒤에서 뺀다
즉, 가장 최근에 넣은 노드를 먼저 처리한다.
BFS 기본형
from collections import deque
def bfs(graph, start):
q = deque([start])
visited = []
while q:
node = q.popleft()
if node not in visited:
visited.append(node)
q.extend(graph[node])
return visited
이 구조는 deque를 큐처럼 쓰는 형태다.
- 뒤에 넣고
- 앞에서 뺀다
즉, 먼저 들어간 노드를 먼저 처리한다.
생각해야 하는 부분
이 글에서 중요한 건
DFS/BFS를 정의로만 나누지 말고,
문제 유형이랑 연결해서 보는 것이다.
1. 최단거리면 BFS부터 보는 게 맞다
이건 거의 기준처럼 가져가도 된다.
예를 들면 백준 18352 특정 거리의 도시 찾기 같은 문제는
출발점에서 정확히 몇 칸 떨어졌는지가 중요했다.
이럴 때는 BFS가 자연스럽다.
왜냐면 BFS는 이렇게 진행되기 때문이다.
- 시작점
- 1칸 떨어진 노드들
- 2칸 떨어진 노드들
- 3칸 떨어진 노드들
즉, 레벨 순서대로 탐색한다.
그래서 최단거리나 거리 배열이 필요한 문제는
대체로 BFS부터 보면 된다.
2. 연결된 덩어리 개수 세기는 DFS가 잘 맞는다
예를 들면 얼음 얼려 먹기 같은 문제는
최단거리가 중요한 게 아니라
“연결된 0 덩어리가 몇 개인가”를 세는 게 핵심이었다.
이럴 때는 BFS도 가능하긴 하지만,
DFS 재귀가 더 직관적이다.
왜냐면 DFS는
지금 시작한 지점에서 연결된 곳을 전부 끝까지 밀어버리기에 좋기 때문이다.
즉, 이런 문제는 보통 이렇게 생각하면 된다.
- 새 시작점 찾기
- DFS로 그 덩어리 전체 방문 처리
- 개수 1 증가
이 패턴이면 DFS가 잘 맞는다.
3. 격자 문제는 BFS/DFS보다 먼저 “좌표 문제”로 봐야 한다
이건 많이 놓친다.
예를 들어 백준 2178 미로 탐색은 BFS 문제이긴 한데,
사실 먼저 봐야 하는 건
이게 일반 그래프가 아니라 좌표형 문제라는 점이다.
즉,
- 노드 = 번호가 아니라 (x, y)
- 인접 노드 = 상하좌우 직접 계산
- 범위 체크 먼저
이 구조가 먼저 잡혀야
그다음 BFS든 DFS든 들어간다.
즉, DFS/BFS를 고르기 전에
문제를 어떤 노드 구조로 볼 것인지를 먼저 정해야 한다.
4. DFS/BFS보다 자료구조가 같이 따라온다
이것도 중요하다.
DFS
보통 스택 흐름이라서
리스트 + pop()으로 많이 구현한다.
stack = []
stack.append(start)
node = stack.pop()
BFS
보통 큐 흐름이라서
deque + popleft()로 구현한다.
from collections import deque
q = deque([start])
node = q.popleft()
즉, DFS/BFS는 알고리즘 이름만 떠올릴 게 아니라
어떤 자료구조를 같이 쓸지까지 묶어서 생각하는 게 맞다.
예외처리 / 주의할 점
⚠️ 1. BFS가 항상 더 좋은 건 아니다
최단거리 문제면 BFS가 강한 건 맞는데,
그렇다고 BFS가 항상 정답은 아니다.
예를 들어 연결 요소 개수 세기 같은 문제는
DFS가 더 직관적인 경우가 많다.
즉, “탐색 = BFS” 식으로 단순하게 가면 안 된다.
⚠️ 2. DFS/BFS보다 먼저 노드 구조를 봐야 한다
이건 특히 격자 문제에서 많이 헷갈린다.
- 일반 그래프 → 인접 리스트
- 격자 → (x, y) 좌표 + dx, dy
- 출발점 하나 → 탐색 1번
- 출발점 여러 개 → 전체 순회 안에 탐색 포장
문제를 어떤 구조로 볼지 먼저 정하지 않으면
DFS/BFS 선택도 애매해진다.
⚠️ 3. 방문 처리 방식도 문제마다 다르다
방문 처리도 고정된 정답이 없다.
예를 들어
- 18352 → distance 배열
- 미로 탐색 → graph 자체를 거리값으로 덮어쓰기
- 얼음 문제 → 0을 1로 바꾸기
이렇게 문제마다 다 다르다.
즉, DFS/BFS를 고른 뒤에는
방문을 뭘로 기록할지까지 같이 설계해야 한다.
내 시행착오
이건 나도 초반에 꽤 헷갈렸던 부분이다.
1. DFS/BFS를 그냥 정의로만 외우려고 했다
처음엔 나도
- DFS = 깊이 우선
- BFS = 너비 우선
이 정도만 외우려고 했다.
근데 문제 풀 때는 그걸로 잘 안 풀린다.
실전에서는
“이 문제는 최단거리냐?”,
“이 문제는 덩어리 세기냐?”
이렇게 연결해서 봐야 훨씬 잘 잡힌다.
2. BFS/DFS를 고르기 전에 노드 구조를 안 봤다
예를 들어 미로 탐색 같은 문제는
BFS냐 DFS냐보다 먼저
이게 좌표 문제라는 걸 먼저 잡아야 했다.
그걸 놓치면 알고리즘 선택 이전에 구현부터 꼬인다.
3. DFS/BFS를 고르면 끝이라고 생각했다
실제로는 거기서 끝이 아니다.
그다음엔 항상 같이 생각해야 한다.
- 큐냐 스택이냐
- visited를 따로 둘 거냐
- graph를 덮어쓸 거냐
- distance 배열을 둘 거냐
즉, DFS/BFS는 시작이지 끝이 아니다.
4. “문제에 맞는 탐색”이라는 개념이 늦게 잡혔다
처음엔 탐색 문제를 보면
그냥 배운 DFS/BFS를 억지로 끼워 맞추려고 했다.
근데 지금 보면 순서가 반대였다.
- 문제를 먼저 보고
- 구조를 파악하고
- 거기에 맞는 탐색을 고른다
이 흐름이 맞았다.
한 줄 정리
DFS와 BFS는 정의로 외우는 게 아니라,
문제가 최단거리인지 / 연결 요소인지 / 좌표형인지에 따라 고르는 탐색 방식으로 보는 게 맞다.
'Coding Test > Python & Algorithm' 카테고리의 다른 글
| 다익스트라 알고리즘 정리 | BFS와 차이, 우선순위 큐 구현까지 (0) | 2026.04.28 |
|---|---|
| 파이썬 리스트 컴프리헨션 정리 | 코딩테스트에서 바로 쓰는 패턴 모음 (3) | 2026.04.23 |
| 파이썬 split과 map 정리 | 코딩테스트 입력 처리에서 자주 쓰는 이유 (0) | 2026.04.22 |
| 파이썬 deque 정리 | BFS에서 리스트 대신 deque를 쓰는 이유 (0) | 2026.04.20 |
