http://www.geeksforgeeks.org/dynamic-programming-set-2-optimal-substructure-property/


 Dynamic Programming - Set 2. Optimal Substructure Property

 Set 1에서 설명했 듯, Dynamic Programming으로 풀 수 있는 문제는 다음 두 주요 특성을 가진다.

 1) Overlapping Subproblems
 2) Optimal Substructure

 Set 1에서 이미 Overlapping Subproblem에 대해 설명했으며, 여기서는 Optimal Substructure에 대해 설명한다.


 2) Optimal Substructure: Optimal Substructure의 특징을 가진 문제는 그 subproblem들의 최적화된 솔루션으로 문제의 최적화된 솔루션을 구할 수 있다. 예를 들어 최단경로 문제는 다음과 같은 Optimal Substrucure 특징을 갖는다: 만약 노드 x가 원시노드 u와 목적지노드 v 사이의 최단 경로에 놓여진다면  u에서 v까지의 최단경로는 u에서 x까지의 최단경로와 x에서 v까지의 최단경로의 조합이 될 것이다. 플로이드-워셜 알고리즘벨먼-포드 알고리즘같은 일반적인 전체-쌍 최단경로 문제의 알고리즘이 Dynamic Programming의 전형적인 예이다.

 반면에 최장경로 문제는 Optimal Substructure 특징을 갖지 않는다. 다음 그래프의 두 노드간의  최장단순경로를 살펴보자.


 q에서 t로 가는 두 최장경로가 있다: q -> r ->t 와 q ->s->t. 최단경로와는 다르게, 이 최장경로들은 Optimal Substructure 특징을 갖지 않는다. 예를 들어, 최장경로 q->r->t은 q에서 r, r에서 t까지의 최장경로들의 조합이 아니다. 왜냐하면 q에서 r까지의 최장경로는 q->s->t->r이기 때문이다.



'Dynamic Programming' 카테고리의 다른 글

01. Overlapping Subproblems Property  (0) 2015.07.17

 http://www.geeksforgeeks.org/dynamic-programming-set-1/


 Dynamic Programming - Set 1. Overlapping Subproblems Property

 Dynamic Programming은 어려운 문제를 푸는 알고리즘의 유명한 예로, 복잡한 문제를 subproblem들로 나누어서 같은 subprobelm들의 결과를 저장하여 복잡한 계산의 반복을 피한다.

 Dynamic Programming으로 해결할 수 있는 문제는 다음 두 주요 특성을 가진다 

 1) Overlapping Subproblems
 2) Optimal Substructure


 1) Overlapping Subproblems:

 분할정복Divide and Conquer과 같이, Dynamic Programming은 subproblem들의 솔루션들을 결합한다. Dynamic Programming은 주로 같은 subproblem들의 솔루션이 계속해서 필요할 때 사용된다. Dynamic Programming에서 subproblem들의 복잡한 솔루션들을 한 테이블에 저장하여 재연산을 하지 않는다. 그래서 Dynamic Programming은 같은(overlapping) subproblem들이 없을 때 사용하기 힘들다. 반복할 필요가 없다면 솔루션을 저장할 필요가 없기 때문이다. 예를 들어, 이진 탐색Binary Search는 같은 subproblem들이 없다. 재귀로 풀어지는 많은 subproblem들로 이루어진 유명한 문제로는 피보나치 수가있다. 


/* 단순한 피보나치 수의 재귀 프로그램 */
int fib(int n)
{
   if ( n <= 1 )
      return n;
   return fib(n-1) + fib(n-2);
}


 fib(5)의 실행 재귀 트리 :

fib(5) / \ fib(4) fib(3) / \ / \ fib(3) fib(2) fib(2) fib(1) / \ / \ / \ fib(2) fib(1) fib(1) fib(0) fib(1) fib(0) / \ fib(1) fib(0)

 함수 fib(3)이 2번 호출 되는 것을 볼 수 있다. 만약 f(3)의 값을 저장했다면, 그것을 계산하는 대신 전에 저장했던 값을 다시 사용할 수 있다. 값을 저장하고 이 값을 재사용하는 방법에는 두 가지가 있다.

 a) Memoization (하향식):

 b) Tabulation (상향식)

 a) Memoization : 재귀형태와 비슷한 Memoization 된 문제는 솔루션을 계산하기 전에 탐색 테이블에서 그 반환 값을 탐색한다. 탐색 어레이는 NIL과 같은 초기값으로 초기화 되어있다. subproblem들의 솔루션을 필요로 할 때마다 먼저 탐색 테이블에서 탐색한다. 만약 이미 계산된 값이 없다면 값을 계산하고 결과를 탐색 테이블에 삽입하여 이후 재사용 가능하게 한다.

 Memoization 된 피보나치 수 :

/* Memoization 된 피보나치 수 */
#include<stdio.h>
#define NIL -1
#define MAX 100
 
int lookup[MAX];
 
/* 탐색 테이블 초기화 */
void _initialize()
{
  int i;
  for (i = 0; i < MAX; i++)
    lookup[i] = NIL;
}
 
/* 피보나치 수 구하기 */
int fib(int n)
{
   if(lookup[n] == NIL)
   {
    if ( n <= 1 )
      lookup[n] = n;
    else
      lookup[n] = fib(n-1) + fib(n-2);
   }
 
   return lookup[n];
}
 
int main ()
{
  int n = 40;
  _initialize();
  printf("Fibonacci number is %d ", fib(n));
  getchar();
  return 0;
}


 b) Tabulation : Tabulation 된 문제는 상향식으로 만든 테이블에서 마지막 값을 솔루션으로서 반환한다.

/* Tabulation 된 피보나치 수 */
#include<stdio.h>
int fib(int n)
{
  int f[n+1];
  int i;
  f[0] = 0;   f[1] = 1;
  for (i = 2; i <= n; i++)
      f[i] = f[i-1] + f[i-2];
 
  return f[n];
}
  
int main ()
{
  int n = 9;
  printf("Fibonacci number is %d ", fib(n));
  getchar();
  return 0;
}


 두가지 다 subproblem들의 해답을 저장한다. Memoization의 테이블은 요구에 따라 채워지나, Tabulation의 테이블은 처음부터 하나씩 전부 채워진다. Tabulation과 다르게, Memoization에서는 탐색테이블의 모든 공간이 채워질 필요는 없다. 예를 들어, Memoization 된 LCS문제는 모든 공간이 채워질 필요가 없다.

 Memoization, Tabulation, 기본재귀 버전의 40번째 피보나치 수를 구하는 실행 시간을 확인해보자.

 기본재귀
 Memoization
 Tabulation

 또한 Ugly Number의 메소드 2에서도 볼 수 있다


 연습:

 1) LCS문제를 Memoization 하라.

 2) Memoization과 Tabulation 중 어떤 것이 더 효율적인가?


'Dynamic Programming' 카테고리의 다른 글

02. Optimal Substructure Property  (0) 2015.07.18

+ Recent posts