탐색이란?
탐색
이란 많은 양의 데이터 중에서 원하는 데이터를 찾는 과정을 의미한다. 대표적인 탐색 알고리즘으로 DFS
와 BFS
를 꼽을 수 있다. 해당 이론을 이해하기 위해서는 기본 자료구조인 스택
과 큐
에 대한 이해가 전제되어 있어야 한다.
DFS란?
깊이 우선 탐색
이라고 부르며, 그래프에서 깊은 부분을 우선적으로 탐색하는 알고리즘이다. 먼저 그래프의 기본 구조를 알아야 한다. 그래프는 노드
와 간선
으로 표현되며 이때 노드를 정점
이라고도 말한다. 그래프 탐색이란 하나의 노드를 시작으로 다수의 노드를 방문하는 것을 말한다. 또한 두 노드가 간선으로 연결되어 있다면 ‘두 노드는 인접하다’라고 표현한다.
인접 행렬
2차원 배열로 그래프의 연결 관계를 표현하는 방식이다. 파이썬에서는 2차원 리스트
로 구현할 수 있다. 연결이 되어 있지 않은 노드끼리는 무한의 비용이라고 작성한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
# 인접 행렬 방식 예제
# 무한 비용 선언
INF = 999999999
# 2차원 리스트를 이용해 인접 행렬 표현
graph = [
[0, 7, 5],
[7, 0, INF],
[5, INF, 0]
]
print(graph)
인접 리스트
리스트로 그래프의 연결 관계를 표현하는 방식이다. 노드에 연결된 노드에 대한 정보를 차례대로 연결하여 저장한다. 인접 리스트는 연결 리스트
라는 자료구조를 이용해 구현한다. 파이썬의 2차원 리스트
를 이용한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 행이 3개인 2차원 리스트로 인접 리스트 표현 -> graph = [[], [], []]
graph = [[] for _ in range(3)]
# 노드 0에 연결된 노드 정보 저장(노드, 거리) -> graph = [[(1, 7), (2, 5)], [], []]
graph[0].append((1, 7))
graph[0].append((2, 5))
# 노드 1에 연결된 노드 정보 저장(노드, 거리) -> graph = [[(1, 7), (2, 5)], [(0, 7)], []]
graph[1].append((0, 7))
# 노드 2에 연결된 노드 정보 저장(노드, 거리) -> graph = [[(1, 7), (2, 5)], [(0, 7)], [(0, 5)]]
graph[2].append((0, 5))
print(graph)
위의 두 방식에는 어떠한 차이가 있을까?
- 메모리 측면에서 보자면 인접 행렬 방식은 모든 관계를 저장하므로 노드 개수가 많을수록 메모리가 불필요하게 낭비된다.
- 반면에 인접 리스트 방식은 연결된 정보만을 저장하기 때문에 메모리를 효율적으로 사용한다.
- 하지만 이와 같은 속성 때문에 인접 리스트 방식은 인접 행렬 방식에 비해 특정한 두 노드가 연결되어 있는지에 대한 정보를 얻는 속도가 느리다.
스택을 활용한 DFS 동작 과정
- 탐색 시작 노드를 스택에 삽입하고 방문 처리를 한다.
- 스택의 최상단 노드에 방문하지 않은 인접 노드가 있으면 그 인접 노드를 스택에 넣고 방문 처리를 한다. 방문하지 않은 노드가 없으면 스택에서 최상단 노드를 꺼낸다.
- 2번의 과정을 더 이상 수행할 수 없을 때까지 반복한다.
Tip
- 방문 처리는 스택에 한 번 삽입되어 처리된 노드가 다시 삽입되지 않게 체크하는 것을 의미한다. 방문 처리를 함으로써 각 노드를 한 번씩만 처리할 수 있다.
- 코테에서는 낮은 순서부터 처리하도록 명시하는 경우가 종종 있다. 따라서 관행적으로 번호가 낮은 순서부터 처리하도록 구현하는 편이다.
기출문제
DFS 예제
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
# DFS 메서드 정의 (재귀 함수 사용)
def dfs(graph, v, visited):
# 현재 노드를 방문 처리
visited[v] = True
print(v, end=' ')
# 현재 노드와 연결된 다른 노드를 재귀적으로 방문
for i in graph[v]:
if not visited[i]:
dfs(graph, i, visited)
graph = [
[],
[2, 3, 8],
[1, 7],
[1, 4, 5],
[3, 5],
[3, 4],
[7],
[2, 6, 8],
[1, 7]
]
# 각 노드가 방문된 정보를 리스트 자료형으로 표현 (1차원 리스트)
visited = [False] * 9
# 정의된 DFS 함수 호출
dfs(graph, 1, visited)
음료수 얼려먹기
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
32
33
34
35
36
# 세로, 가로
n, m = map(int, input().split())
# 2차원 리스트의 맵 정보 입력 받기
graph = []
for i in range(n):
graph.append(list(map(int, input())))
# DFS로 특정한 노드를 방문한 뒤에 연결된 모든 노드들도 방문
def dfs(x, y):
# 주어진 범위를 벗어나는 경우 즉시 종료
if x <= -1 or x >= n or y <= -1 or y >= m:
return False
# 현재 노드를 아직 방문하지 않았다면
if graph[x][y] == 0:
# 해당 노드 방문 처리
graph[x][y] = 1
# 상, 하, 좌, 우 위치도 모두 재귀적으로 호출
dfs(x - 1, y)
dfs(x, y - 1)
dfs(x + 1, y)
dfs(x, y + 1)
return True
return False
# 모든 노드에 대하여 음료수 채우기
result = 0
for i in range(n):
for j in range(m):
# 현재 위치에서 DFS 수행
if dfs(i, j) == True:
result += 1
# 정답 출력
print(result)