Programming/알고리즘 & 코테
[DFS/BFS] 알고리즘 개념
kk_______yy
2023. 12. 18. 15:01
DFS (깊이 우선 탐색 알고리즘)
: 깊이 우선 탐색, 그래프에서 깊은 부분을 우선적으로 탐색하는 알고리즘
* 그래프
- 노드(Node)(=정점) 와 간선(Edge) 으로 이루어져 있음
- 그래프 탐색 : 하나의 노드를 시작으로 다수의 노드를 방문하는 것
- '두 노드는 인접하다' : 두 노드가 간선으로 연결되어 있음
그래프의 표현 방식
인접 행렬(Adjacency Matrix)
: 2차원 배열에 각 노드가 연결된 형태를 기록하는 방식
- 파이썬에서는 2차원 리스트로 구현
- 연결되지 않은 노드끼리는 무한(Inf) 비용으로 작성
- 차이점 : 모든 관계를 저장, 노드 개수 많을수록 메모리 불필요하게 낭비, 노드 연결 정보 얻는 속도 빠름
ex) 인접 행렬 방식 예제
INF = 999999999 # 무한의 비용 선언
# 2차원 리스트를 이용해 인접 행렬 표현
graph = [
[0, 7, 5],
[7, 0, INF],
[5, INF, 0]
]
print(graph)
# [[0, 7, 5], [7, 0, INF], [5, INF, 0]]
인접 리스트(Adjacency List)
: 리스트로 그래프의 연결 관계를 표현하는 방식
- '연결 리스트' 자료구조 이용, 파이썬은 기본 자료형이 append()와 메소드 제공
- 차이점 : 연결된 정보만을 저장하므로 메모리 효율적 사용, 노드 연결 정보 얻는 속도 느림
ex) 인접 리스트 방식 예제
# 행(Row)이 3개인 2차원 리스트로 인저 리스트 표현
graph = [[] for _ in range(3)] # [[], [], []] 형태
# 노드 0에 연결된 노드 정보 저장(노드, 거리)
graph[0].append((1, 7))
graph[0].append((2, 5))
# 노드 1에 연결된 노드 정보 저장(노드, 거리)
graph[1].append((0, 7))
# 노드 2에 연결된 노드 정보 저장(노드, 거리)
graph[2].append((0, 5))
print(graph)
# [[(1, 7), (2, 5)], [(0, 7)], [(0, 5)]]
동작 과정
최대한 깊숙이 들어가서 노드를 방문한 후, 다시 돌아가 다른 경로로 탐색
- 탐색 시작 노드를 스택에 삽입하고 방문 처리
- 스택 최상단 노드에 방문하지 않은 인접 노드가 있으면, 그 인접 노드를 스택에 넣고 방문 처리.
방문하지 않은 인접 노드가 없으면 스택에서 최상단 노드 꺼냄 - 2의 과정을 더 이상 수행할 수 없을 때까지 반복
* 방문 처리 : 스택에 한 번 삽입되어 처리된 노드가 다시 삽입되지 않게 체크. 각 노드 한 번씩만!
* 일반적으로 방문하지 않은 노드 중 번호 낮은 순서부터 처리
- DFS는 스택 자료구조에 기초하므로 구현이 간단
- N개 데이터에 대해 O(N)의 시간 소요
- 실제 구현은 재귀 함수 이용하면 매우 간결
# DFS 예제
# DFS 메서드 정의
# 변수 : graph 각 노드 정보(아래 정의), v 현재 노드(함수 호출시)d 방문 정보(아래 정의)
def dfs(graph, v, visited):
# 현재 노드를 방문 처리
visited[v] = True
print(v, end=' ')
# 현재 노드와 연결된 다른 노드를 재귀적으로 방문
for i in graph[v]:
if not visited[i]: # 현 v노드와 연결된 것 중, 방문하지 않은 노드 탐색(반복)
dfs(graph, i, visited)
# 각 노드가 연결된 정보를 리스트 자료형으로 표현(2차원 리스트)
# 인덱스 번호(9행) == 노드 번호
graph = [
[],
[2, 3, 8],
[1, 7],
[1, 4, 5],
[3, 5],
[3, 4],
[7],
[2, 6, 8],
[1, 7]
]
# 9개 노드의 방문 정보를 리스트 자료형으로 표현(1차원 리스트)
visited = [False] * 9
# 정의된 DFS 함수 호출
dfs(graph, 1, visited)
# 1 2 7 6 8 3 4 5
BFS (너비 우선 탐색 알고리즘)
: 너비 우선 탐색, 가까운 노드부터 탐색하는 알고리즘
* 큐 자료구조 이용
- 인접한 노드를 반복적으로 큐에 넣도록 알고리즘 작성
- 자연스럽게 먼저 들어온 것이 먼저 나가게 됨
- 가까운 노드부터 탐색 가능
동작 과정
- 탐색 시작 노드를 큐에 삽입하고 방문 처리
- 큐에서 노드를 꺼내, 해당 노드의 인접 노드 중에서 방문하지 않은 노드를 모두 큐에 삽입하고 방문 처리
- 2의 과정을 더 이상 수행할 수 없을 때까지 반복
- BFS는 큐 자료구조에 기초하므로 구현이 간단
- deque 라이브러리를 사용하는 것이 좋음
- N개 데이터에 대해 O(N)의 시간 소요
* 코테에서는 보통 BFS(깊이 우선)이 DFS(너비 우선)보다 조금 더 빠르다
# DFS 예제
from collections import deque
# BFS 메서드 정의
# 변수 : graph 각 노드 정보(아래 정의), start 시작 노드(함수 호출시), d 방문 정보(아래 정의),
# queue 넣었다뺐다 큐, v 막 방문처리한(큐에서 막 꺼낸) 노드
def bfs(graph, start, visited):
# 큐(Queue) 구현을 위해 deque 라이브러리 사용
queue = deque([start])
# 현재 노드를 방문 처리
visited[start] = True
# 큐가 빌 때까지 반복
while queue:
# 큐에서 하나의 원소를 뽑아 출력
v = queue.popleft()
print(v, end=' ')
# 해당 원소와 연결된, 아직 방문하지 않은 원소들을 큐에 삽입
for i in graph[v]:
if not visited[i]:
queue.append[i]
visited[i] = True
# 각 노드가 연결된 정보를 리스트 자료형으로 표현(2차원 리스트)
# 인덱스 번호(9행) == 노드 번호
graph = [
[],
[2, 3, 8],
[1, 7],
[1, 4, 5],
[3, 5],
[3, 4],
[7],
[2, 6, 8],
[1, 7]
]
# 9개 노드의 방문 정보를 리스트 자료형으로 표현(1차원 리스트)
visited = [False] * 9
# 정의된 BFS 함수 호출
bfs(graph, 1, visited)
# 1 2 3 8 7 4 5 6
DFS vs DFS
DFS | BFS | |
동작 원리 | 스택 | 큐 |
구현 방법 | 재귀 함수 이용 | 큐 자료구조 이용 |
배열 ↔ 그래프
ex)
3 x 3 형태의 2차원 배열, 각 데이터 좌표라고 하자.
좌표의 상하좌우로만 이동할 수 있다면, 좌표의 형태를 다음의 그래프 형태로 바꿔 생각할 수 있다.
* 코테의 2차원 배열에서 탐색 문제는 그래프 형태로 바꿔 생각하면 풀기 쉽다.