일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
Tags
- os.path
- pandas
- 코딩 테스트
- selenium-wire
- r-string
- Airflow
- decorator
- PIP
- timestamp
- functools.wraps
- boto3
- [초급(예비) 개발자 오픈소스 실무 역량강화 교육]
- 생각
- 고차함수
- blinker
- Python
- ds_store
- sort v.s. sorted
- 순수함수
- reverse v.s. reversed
- slicing [::-1]
- 함수형 프로그래밍
- 쿼리
- OS
- sort(reverse=True) v.s. reverse
- CI/CD
- S3
Archives
- Today
- Total
공부일지
[코딩 테스트]Python을 이용한 BFS, DFS 구현 방법(반복문, 재귀)(인접행렬, 인접 리스트) 본문
Computer/Coding Tests
[코딩 테스트]Python을 이용한 BFS, DFS 구현 방법(반복문, 재귀)(인접행렬, 인접 리스트)
이르리의 공부일지 2025. 3. 27. 11:41

BFS, DFS 구현할 때 헷갈리던 부분 총정리!
갑니다~
사전지식
1. 알고리즘 BFS, DFS 개념
2. 자료구조 스택, 큐 개념
3. 알고리즘 재귀 개념
중요 차이점
BFS - 1. 반복문만(큐)
DFS - 1. 반복문(스택) 2. 재귀함수
입력 자료구조
1. 인접행렬
2. 인접리스트(코딩 테스트에서 많이 씀)
목차
0. 입력 예시
1. BFS 구현
2. DFS 구현
3. 인접행렬보다 인접리스트를 많이 쓰는 이유
4. 결론
입력예시
# 그래프 예시. 간단한 무방향 그래프
0
/ \
1 2
\ /
3
# 간선 목록
(0, 1), (0, 2), (1, 3), (2, 3)
입력 자료구조
# 인접 행렬
adj_matrix = [
[0, 1, 1, 0], # 0번 노드는 1, 2와 연결
[1, 0, 0, 1], # 1번 노드는 0, 3과 연결
[1, 0, 0, 1], # 2번 노드는 0, 3과 연결
[0, 1, 1, 0] # 3번 노드는 1, 2와 연결
]
# 인접 리스트
adj_list = [
[1, 2], # 0번 노드와 연결된 노드
[0, 3], # 1번 노드
[0, 3], # 2번 노드
[1, 2] # 3번 노드
]
BFS 구현
# 인접 행렬 + 큐 자료구조(반복문)
from collections import deque
def bfs_adj_matrix_queue(graph, start):
visited = [False] * len(graph)
queue = deque([start])
result = []
visited[start] = True
while queue:
node = queue.popleft()
result.append(node)
for neighbor in range(len(graph)):
if graph[node][neighbor] and not visited[neighbor]:
visited[neighbor] = True
queue.append(neighbor)
return result
# 인접 리스트 + 큐 자료구조(반복문)
from collections import deque
def bfs_adj_list_queue(graph, start):
visited = [False] * len(graph)
queue = deque([start])
result = []
visited[start] = True
while queue:
node = queue.popleft()
result.append(node)
for neighbor in graph[node]:
if not visited[neighbor]:
visited[neighbor] = True
queue.append(neighbor)
return result
DFS 구현
# 1. 인접행렬
# 1-1. 인접 행렬 + 스택 자료구조(반복문)
def dfs_adj_matrix_stack(graph, start):
visited = [False] * len(graph)
stack = [start]
result = []
while stack:
node = stack.pop()
if not visited[node]:
visited[node] = True
result.append(node)
for neighbor in range(len(graph) - 1, -1, -1):
if graph[node][neighbor] and not visited[neighbor]:
stack.append(neighbor)
return result
# 1-2. 인접 행렬 + 재귀
def dfs_adj_matrix_recursive(graph, start, visited=None, result=None):
if visited is None:
visited = [False] * len(graph)
if result is None:
result = []
visited[start] = True
result.append(start)
for neighbor in range(len(graph)):
if graph[start][neighbor] and not visited[neighbor]:
dfs_adj_matrix_recursive(graph, neighbor, visited, result)
return result
# 2. 인접 리스트
# 2-1. 인접 리스트 + 스택 자료구조(반복문)
def dfs_adj_list_stack(graph, start):
visited = [False] * len(graph)
stack = [start]
result = []
while stack:
node = stack.pop()
if not visited[node]:
visited[node] = True
result.append(node)
for neighbor in reversed(graph[node]):
if not visited[neighbor]:
stack.append(neighbor)
return result
# 2-2. 인접 리스트 + 재귀
def dfs_adj_list_recursive(graph, start, visited=None, result=None):
if visited is None:
visited = [False] * len(graph)
if result is None:
result = []
visited[start] = True
result.append(start)
for neighbor in graph[start]:
if not visited[neighbor]:
dfs_adj_list_recursive(graph, neighbor, visited, result)
return result
인접행렬보다 인접리스트를 많이 쓰는 이유
더보기
이유는 단순히 "간단해서"가 아니라, 성능 + 편리함 + 확장성이 좋아서 그렇다.
✅ 왜 인접리스트가 코테에서 더 자주 쓰일까?
1️⃣ 메모리 효율 (특히 희소 그래프에서 유리함)
- 인접행렬은 항상 NxN 크기의 2차원 배열 사용 → 메모리 낭비 많음
- 인접리스트는 연결된 노드만 저장 → 간선 적으면 아주 효율적
2️⃣ 연결 정보 탐색이 더 직관적
# 인접행렬
for neighbor in range(len(graph)):
if graph[node][neighbor]:
...
# 인접리스트
for neighbor in graph[node]:
...
→ 인접리스트가 바로 연결된 애들만 돌아서 빠르고 간단
3️⃣ 가중치 그래프도 처리하기 쉬움
- 인접리스트는 가중치를 이렇게 표현:
graph = [
[(1, 4), (2, 2)], # 0번 노드 → 1까지 비용 4, 2까지 비용 2
...
]
→ 다익스트라, 벨만포드 등에서 바로 사용 가능
→ 인접행렬은 가중치까지 넣으면 복잡하고 무겁고 느려짐
4️⃣ 코테에서는 입력이 간선 리스트로 주어짐
예: N M u1 v1 u2 v2 ...
- 이걸 인접리스트로 바꾸는 게 더 직관적:
graph = [[] for _ in range(N)]
for u, v in edges:
graph[u].append(v)
graph[v].append(u) # 무방향 그래프일 경우
❗ 예외는?
- 노드 수가 작고, 연결 여부 체크가 자주 필요한 문제
→ 인접행렬이 더 편한 경우도 있음
예:
- "i번 노드와 j번 노드가 연결되어 있는가?" 를 자주 물을 때
- 플로이드-워셜처럼 모든 정점 간 거리 다뤄야 할 때
결론
비교 항목 | 인접리스트 | 인접행렬 |
✅ 메모리 효율 | 👍 | 👎 |
✅ 구현 편의성 | 👍 | 중간 |
✅ 가중치 표현 | 👍 | 복잡함 |
✅ 대규모 그래프 | 👍 | 👎 |
✅ 연결 여부 확인 | 👎 (O(N)) | 👍 (O(1)) |
코테 기본 세팅은 대부분 인접리스트로 간다!
'Computer > Coding Tests' 카테고리의 다른 글
[코딩 테스트] Python을 통해 알아보는 이진탐색과 비트연산자 (0) | 2025.04.01 |
---|---|
[코딩테스트]Python의 리스트의 얕은 복사를 통해 알아보는 참조문제 (0) | 2025.03.29 |
[코딩 테스트]BFS로 보는 [Bool] v.s. [Node], visited 배열 쓸 때와 result 배열만 쓸 때의 차이(서로 관련 있음) (0) | 2025.03.26 |
[코딩 테스트]시간 재는 모듈 time v.s. timeit 차이 (0) | 2025.03.21 |
[코딩 테스트]정렬 문제로 알아보는 Python 자료구조 비교(tuple, namedtuple, struct, class +__slots__) (0) | 2025.03.21 |