깊이 우선 탐색 (Depth-First Search, DFS)은 그래프의 모든 노드를 방문하는 알고리즘 중 하나이다. DFS는 현재 노드와 인접한 노드를 재귀적으로 탐색하며, 그래프의 깊숙한 곳까지 우선 탐색한다. 이런 방식 때문에 "깊이 우선"이라는 이름이 붙었다.
DFS방식은 한 노드의 자식을 타고 끝까지 순회한 후, 다시 돌아와서 다른 형제들의 자식을 타고 내려가며 순회한다.ㅈ
파이썬에서 제공하는 딕셔너리와 리스트 자료 구조를 홀용하여, 그래프를 포현할 수 있다.
graph = dict()
graph['A'] = ['B', 'C']
graph['B'] = ['A', 'D']
graph['C'] = ['A', 'G', 'H', 'I']
graph['D'] = ['B', 'E', 'F']
graph['E'] = ['D']
graph['F'] = ['D']
graph['G'] = ['C']
graph['H'] = ['C']
graph['I'] = ['C', 'J']
graph['J'] = ['I']
graph
"""
{'A': ['B', 'C'],
'B': ['A', 'D'],
'C': ['A', 'G', 'H', 'I'],
'D': ['B', 'E', 'F'],
'E': ['D'],
'F': ['D'],
'G': ['C'],
'H': ['C'],
'I': ['C', 'J'],
'J': ['I']}
"""
DFS는 스택을 사용하여 구현될 수 있다. 재귀 함수를 사용하는 방법도 사실상 시스템 스택을 사용하는 것이므로, 재귀를 이용한 구현도 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)
# 각 노드가 연결된 정보를 표현 (2차원 리스트)
graph = [
[], # 0번 노드는 없다고 가정
[2, 3, 8], # 1번 노드와 연결된 노드들
[1, 7], # 2번 노드와 연결된 노드들
# ...
]
# 각 노드가 방문된 정보를 표현 (1차원 리스트)
visited = [False] * 9 # 인덱스 0은 사용하지 않기 위해
# 정의된 DFS 함수 호출
dfs(graph, 1, visited)
def dfs(graph, start_node):
visited, need_visit = list(), list()
need_visit.append(start_node)
while need_visit:
node = need_visit.pop()
if node not in visited:
visited.append(node)
need_visit.extend(graph[node])
return visited
코드의 동작 방식을 단계별로 살펴보자.
이러한 방식으로 DFS를 반복문을 사용하여 구현하게 되면, 재귀를 사용한 DFS에 비해 스택 오버플로우의 위험이 없으므로 대규모 그래프에 대해서도 안정적으로 탐색이 가능하다.
그러나 이 코드의 방식은 전통적인 재귀를 사용한 DFS와는 약간 다른 방식이므로, 특정 문제 상황에 따라서는 결과의 순서가 달라질 수 있다.
이 DFS 알고리즘은 노드의 수가 V, 간선의 수가E일 때, 시간 복잡도는 인접 리스트 방식으로 표현된 그래프일 경우 이다.
(코드에서, while need_visit은 V+E번 만큼 수행해야 한다.)