[Dynamic] 다이나믹 프로그래밍

Dynamic Programming


  • 컴퓨터적 사고력을 물어보기 매우 적합한 알고리즘이다.
  • 하나의 문제는 단 한 번만 풀도록 하는 알고리즘을 말한다.
  • 예를 들어 일부 분할 정복 기법의 경우, 동일한 문제를 다시 푼다는 단점을 가진다.
  • 대표적인 예시로 피보나치 수열이 있다.


피보나치 수열

  • 1 1 2 3 5 8 13 …
  • i번째 숫자를 구하기 위해 i-1과 i-2의 합을 구하는 수열이다.
  • 피보나치 수열의 점화식 : D[i] = D[i - 1] + D[i - 2]


img

  • 위 이진트리를 예를 들어보자.
  • 노드 15의 값을 구하려면 14와 13을 구해야 한다.
  • 14와 13도 각각 값을 구하려면 13과 12, 12와 11을 구해야 한다.
  • 이런식으로 높이가 n이라고 치면, 한 칸 내려갈 때마다 구해야 할 값이 2배씩 증가하게 된다.
  • 따라서 위 경우, 병합 정렬처럼 단순 분할 정복 기법을 사용하면 O(2^n) 수준으로 시간 복잡도가 커지기에, 매우 비효율적일 수 있다.
  • 게다가, 이미 구한 값을 또 다시 반복 계산을 하는 경우가 매우 많이 생겨 비효율적이다.


다이나믹 프로그래밍 특징

  • 다음 가정을 만족할 때 사용 가능하다.
    • 큰 문제를 작은 문제로 나눌 수 있다.
    • 작은 문제에서 구한 정답은 그것을 포함하는 큰 문제에서도 동일하다.


  • 즉, 큰 문제가 있으면 그것을 잘게 나누어서 해결하고, 전체의 답은 나중에 구하는 방식이다.
  • 이 과정에서, 이미 계산한 결과는 배열에 저장함으로써 나중에 동일 계산을 할 때 단순히 값을 반환만 하는 방법을 사용한다.
  • 위 방법을 ‘메모이제이션(Memoization)’이라고 한다.


Source Code 1

#include <stdio.h>

int d(int x) {
	if(x == 1) return 1;
	if(x == 2) return 1;
	return d(x - 1) + d(x - 2);
}

int main(void) {
	printf("%d", d(10));
}
  • 피보나치 수열을 예로 들어 확인했다.
  • 하지만 이 경우, 분할 정복을 이용한 재귀호출이기 때문에 시간복잡도는 O(2^n)이 된다.
  • 따라서 x 값이 커지면 기하 급수적으로 커지기 때문에, 해당 코드를 수행하는데 정말 오랜시간이 걸린다.
  • 예를 들어 x에 50을 넣으면, 2^50 즉, 1,000,000,000,000,000개가 넘는 계산량을 컴퓨터가 처리해야 하는 것이다.


Source Code 2

#include <stdio.h>

int d[100];

int fibonacci(int x) {
	if(x == 1) return 1;
	if(x == 2) return 1;
	if(d[x] != 0) return d[x];
	return d[x] = fibonacci(x - 1) + fibonacci(x - 2);
}

int main(void) {
	printf("%d", fibonacci(30));
}
  • 위와 같은 방법을 사용하면, 계산된 결과는 d 배열에 저장된다.
  • 따라서 한 번 이미 계산이 되었다면 다시 계산이 수행될 필요 없이, 값만 반환하면 된다.
  • 위 연산의 시간 복잡도는 O(n)으로 확 줄어들게 된다!
  • 즉, 구해야 하는 노드의 높이(n)만큼만 계산하고, 나머지 값은 이미 계산이 진행된 배열에서 값만 반환하기 떄문이다.


모든 자료 및 사진 출처

깃허브




© 2020.07. by Sanggoe

Powered by Sanggoe