DFS/BFS 개념
꼭 필요한 자료구조 기초
탐색(search)이란 많은 양의 데이터 중에서 원하는 데이터를 찾는 과정을 의미한다. 대표적인 탐색 알고리즘으로 DFS와 BFS를 꼽을 수 있는데 이를 이해하려면 기본 자료구조인 스택과 큐, 재귀 함수를 이해하여야 한다. 자료구조란 데이터를 표현하고 관리하고 처리하기 위한 구조를 의미한다. 그 중 스택과 큐는 자료구조의 기초 개념으로 다음의 두 함수로 구성된다.
삽입(Push) : 데이터를 삽입한다.
삭제(Pop) : 데이터를 삭제한다.
물론 오버플로와 언더플로를 고민해야하는데 오버플로는 특정한 자료구조가 수용할 수 있는 데이터의 크기를 이미 가득 찬 상태에서 삽입 연산을 수행할 때 발생한다. 즉, 저장 공간을 벗어나 데이터가 넘쳐흐를 때 발생한다. 반면 특정한 자료구조에 데이터가 전혀 들어 있지 않은 상태에서 삭제 연산을 수행하면 데이터가 전혀 없는 상태인 언더플로가 발생한다.
스택(Stack)
스택은 박스 쌓기에 비유할 수 있다. 선입후출 혹은 후입선출(둘이 같은 의미이다.)방식으로 사용된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
stack = []
stack.append(5)
stack.append(2)
stack.append(3)
stack.append(7)
stack.pop()
stack.append(1)
stack.append(4)
stack.pop()
print(stack) #최하단 원소부터 출력
print(stack[::-1]) #최상단 원소부터 출력
1
2
[5,2,3,1]
[1.3.2.5]
파이썬에서 스택을 이용할 때에는 별도의 라이브러리를 사용할 필요가 없다. 기본 리스트에서 append()
와 pop()
메서드를 이용하면 스택 자료구조와 동일하게 동작하며 append()
는 가장 뒤쪽에 데이터를 삽입, pop()
은 리스트의 가장 뒤쪽에서 데이터를 꺼낸다.
큐(Queue)
큐는 대기줄에 비유할 수 있다. 먼저 온 사람이 먼저 먼저 나가는 선입선출방식으로 사용된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from collections import deque
#큐(Queue) 구현을 위해 deque 라이브러리 사용
queue = deque()
queue.append(5)
queue.append(2)
queue.append(3)
queue.append(7)
queue.popleft()
queue.append(1)
queue.append(4)
queue.popleft()
print(queue) #먼저 들어온 순서대로 출력
queue.reverse() #다음 출력을 위해 역순으로 바꾸기
print(queue) #나중에 들어온 원소부터 출력
1
2
deque([3,7,1,4])
deque([4,1,7,3])
파이썬으로 큐를 구현할 때는 collections
모듈에서 제공하는 deque
자료구조를 활용하자. deque
의 장점은 데이터를 입출력하는 속도가 리스트 자료형보다 효율적이다. list(queue)
를 하면 리스타 자료형이 반환된다.
재귀 함수(Recursive Function)
재귀 함수는 피보나치처럼 자기자신을 다시 호출하는 함수를 의미한다.
1
2
3
4
5
def recursive_function():
print('재귀 함수를 호출합니다.')
recursive_function()
recursive_function()
이런 함수는 재귀(Recursion)의 최대 깊이를 초과하게 된다. 따라서 무한대로 재귀 호출을 진행할 수 없기 때문에 적절한 제한을 둬야 한다.

재귀 함수의 종료 조건
재귀 함수를 문제 풀이에서 사용하게 된다면, 재귀 함수가 언제 끝날지, 종료 조건을 꼭 명시해야 한다. 그렇지 않으면 함수가 무한 호출될 수 있다. 컴퓨터 내부에서 재귀 함수의 수행은 스택자료구조를 이용한다. 함수를 계속 호출했을 ?때 가장 마지막에 호출한 함수가 먼제 수행을 끝내야 그 앞의 함수 호출이 종료되기 때문이다. 컴퓨터 구조 측면에서 보자면 연속해서 호출되는 함수는 메인 메모리의 스택 공간에 적재되므로 재귀 함수는 스택 자료구조와같다는 말이 틀린 말이 아니다. 따라서 스택 자료구조를 활용해야 하는 상당수 알고리즘은 재귀 함수를 사용해 구현할 수 있다. (DFS같은 알고리즘)
재귀함수를 이용하는 예제로 팩토리얼을 구현해보자
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#반복적으로 구현한 n!
def factorial_iterative(n):
result = 1
#1부터 n까지의 수를 차례대로 곱하기
for i in range(1,n+1):
result *= i
return result
#재귀적으로 구현한 n!
def factorial_recursive(n):
if n<=1:
return 1
return n * factorial_recursive(n-1)
print(factorial_iterative(5))
print(factorial_recursive(5))
1
2
120
120
실행 결과는 동일하지만 재귀 함수가 수학의 점화식을 그대로 소스코드로 옮겨서 더 간단하다.
Comments