인간이 문제를 풀어내는 방법을 생각해보자.
그렇다면 우리가 ‘문제의 스케일이 작은 경우에만 풀 수 있는 경우’를 예시와 함께 알아보자.
동적 계획법 설명에 있어 크게 중요하지 않은 문제니 전체적인 흐름만 확인하시고 생략해도 좋습니다.
위 3개의 기둥 중 왼쪽에 쌓인 원반을 오른쪽 기둥으로 모두 옮기는 문제이며 조건은 다음과 같다.
원반이 총 n개 쌓여 있을 때, 모두 오른쪽 기둥으로 옮기는 경우 원반은 총 몇 번 옮겨지는가?
만약 원반이 1개 있다면?
원반을 그냥 한 번 옮기면 된다.만약 원반이 2개라면?
첫 번째 원반을 가운데에 옮긴 뒤 두 번째 원반을 오른쪽 기둥에 넣고 첫 번째 원반을 위에 얹으면 된다.만약 원반이 3개라면?
이 때부터 조금씩 복잡해진다.여기서 중요한 것은 실제 횟수를 구하는것이 중요한 게 아닌!
우리가 풀 수 있는 영역과 감이 잘 오지 않는 영역을 구분하여 결합하는 것이다.
위 문제에서 우리가 풀 수 있는 영역은 원반이 3개 정도일 때고, 그 이상은 구하기 쉽지 않다.
따라서 문제의 크기를 n부터 우리가 잘 아는 1,2,3 개 정도일 때로 축소하는 것이다.
뭐야?! 문제를 재귀로 푸는게 다인데? 동적 계획법이 뭐가 특별하다는 거죠?
→ 동적 계획법은 재귀로 풀어야 하는 문제의 규모가 커질 때 특별해진다.
동적 계획법이 무엇인지, 재귀와 다른것은 무엇인지, 어느때 효과가 있는지 예제와 함께 알아보자.
피보나치 수열이 무엇인지는 해당 링크에서 개요를 참조!
피보나치 수열을 구하는 코드는 다음과 같다. (재귀를 사용)
def fibo(n):
if n == 0 or n == 1: return n
else: return fibo(n-1) + fibo(n-2)
간단한 재귀 함수 코드이다. 그런데 위 코드는 문제가 있다.
바로 쓸데없는 연산을 너무 많이 한다는 것.
5번째 피보나치 수를 구하는 경우를 생각해보자
→ 이 과정에서 3번째 피보나치 수를 두 번 구하게 된다.
혹시 “에이~ 한 번 정도 더 연산하는건데 뭐 어때요~” 라고 생각했다면 다음과 같은 경우를 생각해보자.
피보나치 수열을 구할 때 마다 따로 저장해두자!
…
이게 전부다.
그렇다면 피보나치 수열에 동적 계획법을 적용하여 코드를 작성해보자!
def fibo(F:list, n):
if F[n] > 0:
return F[n]
else:
F[n] = fibo(F, n-1) + fibo(F, n-2)
return F[n]
fibo_list = [0 for _ in range(1000)]
fibo_list[0] = 1
fibo_list[1] = 1
새로운 피보나치 함수는 리스트를 인자로 받아,
리스트에 구하려는 피보나치 수가 이미 구해져 있다면 참조하고,
그렇지 않다면 새로 구하여 리스트에 저장하는 방식이다.
어떤 문제를 풀 때, 이전에 풀었던 문제의 답을 활용할 수 있다면 활용하는 것이 바로 동적 계획법이다!
그 문제를 어떻게 작게 쪼개어 다시 합칠지는 문제를 푸는 사람이 생각해보아야할 문제이다.
여타 다른 알고리즘들은 어떤 어떤 상황에서 어떤 어떤 기법을 쓰면 빨리, 정확히 풀 수 있더라~라는 느낌을 주지만, 동적 계획법은 다소 뜬 구름 잡는 느낌이 든다.
다음 포스팅에서는 동적 계획법의 예시를 보며 문제 해결을 할 때 어떻게 사용할 수 있는지 확인해보자.