여기를 클릭하면 문제를 볼 수 있습니다.
본 문제는 주어진 재귀함수를 사용하여 풀 수 있습니다.
하지만 주어진 함수를 그대로 사용할 경우 중복되는 연산이 있으므로 불필요한 시간이 소요됩니다.
fibonacci(5)로 예를 들어보겠습니다.
위 이미지는 fibonacci(5);
를 하였을 때 호출하는 함수의 흐름을 간단하게 도식화 한 것 입니다.
살펴보면 재귀적 특성 때문에 동일한 연산을 하는 fibonacci(0), fibonacci(1), fibonacci(2), fibonacci(3)
이 여러번 호출되는 것을 볼 수 있습니다.
위에서도 언급했다시피 재귀의 특성에 의해 동일한 연산을 하는 함수를 여러 번 호출 하는 것은 불필요한 시간이 소요됩니다.
그러므로 불필요한 시간 소요를 줄이기 위해서는 동일한 연산을 하는 함수를 여러 번 호출하지 않아야 합니다. 그렇다면 어떻게 해야 한 번만 호출할 수 있을까요?
그것은 바로 정보를 기록하는 것입니다! fibonacci(0)
과 fibonacci(1)
이 호출되었을 때 0과 1을 출력한 횟수를 기록한다면 fibonacci(2)
에서는 굳이 fibonacci(1)
과 fibonacci(0)
을 호출하지 않고서 기록된 정보를 가지고 구할 수 있습니다. 그리고 그것을 또 기록하면 되는 것이죠.
그러면 fibonacci(3)
에서는 fibonacci(2)
와 fibonacci(1)
을 굳이 호출하지 않고 기록된 정보만 가지고 구할 수 있습니다.
이와 같은 방법으로 동일한 연산을 하는 함수를 반복적으로 호출하지 않고도 기록된 정보만 가지고 0과 1의 출력횟수를 구할 수 있게 되었습니다.
이와 같은 방법을 memoization
이라고 합니다. memoization
은 정보를 기록함으로써 불필요한 연산을 줄이는 방법을 뜻하며 dynamic programming(동적 계획법)
의 핵심이 되는 기술입니다.
이제 정보를 기록하는 과정을 자세히 살펴보겠습니다.
이전과 동일하게 fibonacci(5)
를 예로 설명하겠습니다.
fibonacci(5)
를 호출하면 fibonacci(0)
부터 fibonacci(5)
까지 0과 1의 출력횟수를 각각 저장하는 변수가 필요합니다.fibonacci(0)
과 fibonacci(1)
에 출력횟수를 기록합니다.fibonacci(2)
부터는 재귀함수의 특징을 사용하여 출력횟수를 구합니다. 본래의 fibonacci(2)
는 fibonacci(1)+fibonacci(0)
입니다. 하지만 이미 fibonacci(1)
과 fibonacci(0)
의 출력횟수를 기록하였기 때문에 함수를 호출할 필요 없이 기록된 정보를 더하기만 하면 됩니다.fibonacci(3)
또한 fibonacci(2)
와 같이 구하면 됩니다. fibonacc(2)
의 출력횟수와 fibonacci(1)
의 출력횟수를 더하면 되는 것이죠.fibonacci(4)
와 fibonacci(5)
도 동일한 방법으로 구해줍니다.총 5번의 연산만으로 fibonacci(5)
의 0과 1 출력횟수를 구할 수 있게 되었습니다.
만약 memoization
을 하지 않고 재귀함수로 구했다면 연산횟수는 15번의 연산이 필요합니다. memoization
덕분에 무려 3배나 연산횟수를 줄일 수 있게 되었네요.
이게 바로 memoization
이 중요한 이유입니다.
이제 이를 코드로 나타내봅시다.
#include <iostream>
using namespace std;
int f[41][2] = {{0, }, }; // 출력횟수를 기록하기 위한 배열
void fibonacci(int n)
{
if (n == 0) // fibonacci(0)일 때는 0의 출력횟수를 기록
f[n][0] = 1;
else if (n == 1) // fibonacci(1)일 때는 1의 출력횟수를 기록
f[n][1] = 1;
else // 그 외에는 fibonacci(n-1)의 출력횟수+fibonacci(n-2)의 출력횟수
{
f[n][0] = f[n-1][0]+f[n-2][0];
f[n][1] = f[n-1][1]+f[n-2][1];
}
}
int main(void)
{
int t;
scanf("%d", &t);
int n;
int j = 0;
for(int i=0; i<t; i++) // 주어진 t만큼
{
scanf("%d", &n); // n을 입력받아
for(; j<=n; j++) // 0부터 n까지 finbonacci() 연산
fibonacci(j);
printf("%d %d\n", f[n][0], f[n][1]); // 출력횟수를 출력!
j = n+1; // t=2, n=3, 5 라면 첫 번째 시행에서 0부터 3까지의 fibonacci()를 수행,
// 그러면 5를 구하기 위해서는 0부터 5까지가 아니라 4부터 5까지만 구하면 됨!
// 그러므로 for문의 시작을 n+1로 해준다.
}
return 0;
}
요시, 다이나믹 프로그래밍 시즌!🏏