동적계획법을 이해하고, 점화식을 세워 동적계획법 문제에 적용할 수 있다.
이미 계산한 값을 활용하지 않고, 중복된 연산을 하는 경우가 허다합니다. 이를 극복하는 알고리즘이 바로 동적계획법입니다.
이미 저희에게 친숙한 피보나치 수를 한 번 살펴보겠습니다.
https://www.acmicpc.net/problem/10870
피보나치 수 5를 살펴보겠습니다.
재귀함수를 이용했다면, 다음과 같이 문제를 풀었을 것 입니다.
#include <iostream>
using namespace std;
int n;
int finbonacci(int x) {
if (x == 0)
return 0;
if (x == 1)
return 1;
return finbonacci(x - 1) + finbonacci(x - 2);
}
int main(void) {
cin >> n;
cout << finbonacci(n);
return 0;
}
시간복잡도를 확인해보겠습니다.

f(4)의 함수 호출 과정입니다.
이렇게 되면 시간복잡도가 이 됩니다.
n이 커지면 커질 수록 많은 연산이 불필요하게 중복되기 때문입니다.
이 중복을 피하기 위해서는 어떻게 하면 될까요?
크게 두 가지 방법을 볼 수 있습니다.
#include <iostream>
using namespace std;
long long dp[100] = {0, 1,};
long long fibonacci(int n) {
if (n <= 1) {
return n;
}
if (dp[n] == 0)
dp[n] = fibonacci(n - 1) + fibonacci(n - 2);
return dp[n];
}
int main(void) {
int n;
cin >> n;
cout << fibonacci(n);
}
위 dp배열에는 계산된 피보나치 값이 저장됩니다. 이미 값이 저장되어 있으면 계산을 할 필요가 없어, 배열의 값을 반환해주고, 저장되지 않았다면 계산 후 dp에 넣어주어 같은 계산을 하지 않도록 합니다.
#include <iostream>
using namespace std;
long long dp[100];
int n;
int main(void) {
cin >> n;
dp[0] = 0;
dp[1] = 1;
for (int i = 2; i <= n; i++) {
dp[i] = dp[i - 1] + dp[i - 2];
}
cout << dp[n];
return 0;
}
피보나치 수를 계산하는 데에 필요한 수는 직전과 이전 두 가지 뿐입니다. 이를 이용해 차근차근 쌓아 올라가는 방법입니다. 우리가 실제 피보나치 함수를 손으로 직접 계산하는 것과 비슷한 방법으로 진행됩니다.
이 동적계획법에서는 두가지 접근방법이 사용될 수 있고, 접근 방법에 따라서 각각에 알맞는 기법이 사용됩니다.
1. 연산 결과를 기록하기의 방법입니다.dp배열에 계산 결과를 메모했었죠? 이렇게 한번 계산 한 값을 메모하는 기법을 메모이제이션(memoization)이라고 합니다.2. 계산한 결과로 다음 연산을 수행하기와 같은 방법이며,그럼 두 방법중에 어떤 방법이 좋은 방법일까요?
당연하게도, 문제 상황에 따라 다릅니다.
탑다운의 방식의 장점은 큰 문제의 최적해에 필요한 부분문제만을 구해내면 된다는 것입니다.
바텀업의 경우, 큰 문제 이전의 모든 부분문제를 구해내야 하기에 불필요한 계산을 더 하게 될수도 있죠.
그럼 피보나치와 같이 큰 문제의 최적해가 어차피 모든 부분문제를 구해야 찾아지는 구조라면, 둘의 차이가 없는걸까요?
탑다운 방식의 단점은 그 구현 방법인 “재귀” 자체에 있습니다. 프로그램에서는 함수 자체를 실행하는 데에 따른 오버헤드가 발생하게 됩니다. 함수를 실행할 때 함수 호출과 값을 반환하는 과정에서 메모리와 시간을 소모하게 되는데, 호출 횟수가 적다면 별 의미 없지만, 재귀로 인해 아주 많은 호출이 일어나게 될 경우 아주 큰 차이가 발생하게 될 수 있습니다.
실제로 바텀업 방식을 통해 반복문으로 구현하면 맞았습니다를 받게 되지만, 탑다운 방식을 통해 재귀로 구현하면 메모리 초과를 받게 되는 경우가 있습니다.
오늘은 동적계획법에 대해 알아보았습니다. 동적계획법은 중복 계산을 줄일 수 있고, 효율적인 시간 복잡도를 가질 수 있다는 장점이 있지만, 메모리 사용량을 잘 생각해야하는 알고리즘입니다.