DFS 와 BFS 는 그래프를 탐색하는 방법으로, 어떤 기준으로 그래프를 읽어가느냐에 따른 차이가 있다.
그래프란?
- 정점(node)과 그 정점을 연결하는 간선(edge)로 이루어진 일종의 자료구조
- 정점 : 특별한 하나의 객체
- 간성 : 두 개의 노드를 연결하는 선, 두 노드간의 관례를 나타낸다. 방향이나 비중을 가질 수 있다.
- 그래프를 탐색한다 = 하나의 정점을 시작으로 차례대로 모든 정점을 한 번씩 방문하는 것을 말한다!
DFS : Depth-First Search
루트 노드에서 탐색을 시작할 때, 연결되어 갈 수 있는 데까지 최대한 깊게 끝까지 탐색하는 방식이다.

찾아보니 미로찾기를 할 때, 한 방향으로 가다가 막히면 다시 가장 가까운 갈림길로 돌아와서 그 곳부터 다른 방향으로 탐색을 진행하는 것과 같은 방식이라고 설명하고 있다.
스택과 재귀함수로 구현될 때, 스택의 개념을 쓴다는 것은 같지만 완전히 똑같지는 않다.
- 함수에서는 순서를 변경 할 수 없지만, 스택을 사용할 때 순서를 지정해서 하고 싶다면 정렬을 반대한다던지의 방법으로 변경해서 사용할 수 있다.
예시)
1-2-3-4로 스택이 쌓이고 만약 2와 3, 4에도 연결된 노드들이 있더라도 제일 마지막인 4와 연결된 것부터 탐색하고 3과 연결된 노드를 탐색하고 2와 연결된 노드를 탐색하게 된다.
만약 탐색 자체를 2부터 하고 싶다면, 정렬을 반대로 해서 처음부터 스택이 1-4-3-2 로 쌓이도록 하여 탐색할 수도 있다.
BFS : Breadth-First Search

루트 노드에서 시작해서 인접한 노드를 먼저 탐색하고, 멀리 떨어져 있는 노드는 나중에 탐색하는 방식이다.

두 가지 방법 모두 모든 노드를 탐색한다는 점에서 시간복잡도는 동일하다.
(다음 노드를 방문하였는지 확인하는 시간 + 각 노드를 방문하는 시간) 으로 계산된다.
N 노드, E 간선 일 때,
- 인접 리스트 : O(N+E)
- 인접 행렬 : O(N^2)
일반 적으로 E(간선)의 크기가 N^2 에 비해 상대적으로 적기 떄문에, 인접 리스트 방식이 효율적이다.
기본적으로 입력으로 주어지는 그래프를 그리고, 그래프 만큼의 방문 기록을 확인 할 수 있는 visited 리스트를 false로 만들어서 방문했던 노드는 재방문 하지 않기 위해 방문 여부를 남힌다. (안해주면 무한 재방문이 될 수도 있음)
def dfs(idx):
stack = []
# 시작점을 스택에 쌓아준다. _ 방문해야 할 노드 저장
stack.append(idx)
# 스택이 존재하는 동안 반복함
while stack:
#지금 방문한 노드 = 시작점 이후에는 스택에서 제일 마지막에 저장 한 노드
current = stack.pop()
# 확인용 출력
print(f"{current} 방문!")
# 방문 했다면 True 로 변경해준다.
visited[current] = True
#지금 방문 노드의 인접한 노드를 탐색하는데,
for adj in graph[current]:
# 방문하지 않았는지 확인 후,
if not visited[adj]:
# 연결되어 있는 노드를 스택에 쌓아준다.
stack.append(adj)
# 재방문을 막기 위해 방문 처리
visited[adj] = True
# 그래프 초기 설정
n = 5
graph = [[] for _ in range(n + 1)]
graph[1].append(2)
graph[1].append(4)
graph[2].append(4)
graph[4].append(1)
graph[4].append(5)
graph[5].append(3)
# graph = [[], [2, 4], [4], [], [1, 5], [3]]
# 방문 여부 확인용 리스트 (인덱스를 1부터 사용하기 위해 n+1 까지)
visited = [False] * (n + 1)
# dfs 그래프 탐색
dfs(1)
요렇게 쓰면 출력 결과는
# 결과값
1 방문! # 1 방문, stack = [2, 4]
4 방문! # 1-4 으로 방문, stack = [2, 5]
5 방문! # 4-5 으로 방문, stack = [2, 3]
3 방문! # 5-3 으로 방문, stack = [2]
2 방문! # 마지막 남은 노드 방문
def dfs(idx):
if visited[idx]: # 방문한 노드는 다시 방문할 필요 없으므로 체크해줌
return
visited[idx] = True # 노드 방문 처리
# 확인용 출력
print(f"{idx} 방문!")
for i in graph[idx]: # 주변 노드들에 대한 탐색을 위해 재귀함수로 호출
dfs(i)
# 그래프 초기 설정 _이 부분은 동일
n = 5
graph = [[] for _ in range(n + 1)]
graph[1].append(2)
graph[1].append(4)
graph[2].append(4)
graph[4].append(1)
graph[4].append(5)
graph[5].append(3)
visited = [False] * (n + 1)
dfs(1)
def bfs(idx):
from collections import deque
queue = deque()
queue.append(idx)
# 노드 방문 처리
visited[idx] = True
# 큐가 비어 있을 때까지 탐색
while queue:
# 큐에 들어온 순서대로 제일 앞의 노드를 뽑아서
idx = queue.popleft()
print(f"{idx} 방문!")
for i in graph[idx]:
# 방문하지 않았을 때
if not visited[i]:
# 큐에 추가
queue.append(i)
visited[i] = True
# 그래프 초기 설정_ 이 부분은 모두 같음
n = 5
graph = [[] for _ in range(n + 1)]
graph[1].append(2)
graph[1].append(4)
graph[2].append(4)
graph[4].append(1)
graph[4].append(5)
graph[5].append(3)
# graph = [[], [2, 4], [4], [], [1, 5], [3]]
visited = [False] * (n + 1)
# 탐색 시작
bfs(1)
그럼 결과는 아래처럼 나오게 된다!
# 결과값
1 방문! # q = [2, 4]
2 방문! # q = [4]
4 방문! # q = [5]
5 방문! # q = [3]
3 방문!
참고자료
DFS, BFS의 설명, 차이점
항해99 강의자료