[Algorithm] 막대 자르기

2020-03-27

원본 : https://www.geeksforgeeks.org/cutting-a-rod-dp-13/

Cutting a Rod | DP-13

다음과 같이 단위 길이당 막대의 가격이 주어졌을 때 n 길이의 막대로 얻을 수 있는 최고가격을 구하라. 예를 들어 길이가 8인 막대가 주어졌을 때 최고 가격은 22이다. ( 길이가 2, 6인 막대로 자름)

length 1 2 3 4 5 6 7 8
price 1 5 8 9 10 17 17 20

단순한 방법으로 접근하면 길이의 모든 조합을 찾아서 가장 높은 가격을 계산한다. 하지만 complexity 가 많이 걸림. DP 방식으로 어떻게 풀 수 있는지 알아보자

1) 효율적인 구조:

각각 다른 위치에서 통나무를 자르고 나서 가격을 계산한뒤 최고 값을 구할 수 있다. 이를 재귀함수로 구현할 수 있다.

cutRod(n) 를 길이가 n 일 때 최고 값이라고 가정하면 점화식은 다음과 같다.

cutRod(n) = max(price[i] + cutRod(n-i-1)) for all i in {0, 1 .. n-1}
max(i길이의 값 + cutRod(n-i-1)) : price[i] 의 값과 나머지 길이의 가격의 최대 값의 합

2) 중복문제

다음과 같이 간단한 재귀함수를 구현할 수 있다.

int cutRod(int price[], int n) 
{ 
   if (n <= 0) 
     return 0; 
   int max_val = 0; 
  
   // Recursively cut the rod in different pieces and compare different  
   // configurations 
   for (int i = 0; i<n; i++) 
         max_val = max(max_val, price[i] + cutRod(price, n-i-1)); 
  
   return max_val; 
} 

막대의 길이가 4라고 가정하면 다음과 같은 재귀트리가 생성된다.

cR() ---> cutRod() 

                             cR(4)
                  /        /           
                 /        /              
             cR(3)       cR(2)     cR(1)   cR(0)
            /  |         /         |
           /   |        /          |  
      cR(2) cR(1) cR(0) cR(1) cR(0) cR(0)
     /        |          |
    /         |          |   
  cR(1) cR(0) cR(0)      cR(0)
   /
 /
CR(0)

위의 트리를 참고하면 cR(2) 같은 경우 두번 계산되게 된다. 트리의 depth 가 깊으면 깊을수록 이런 중복된 계산이 많아질 것이다. 따라서 이렇게 계산된 값들은 미리 저장해두는 것이 좋다.

int cutRod(int price[], int n) 
{ 
   int val[n+1]; 
   val[0] = 0; 
   int i, j; 
  
   // Build the table val[] in bottom up manner and return the last entry 
   // from the table 
   for (i = 1; i<=n; i++) 
   { 
       int max_val = 0; 
       for (j = 0; j < i; j++) 
         max_val = max(max_val, price[j] + val[i-j-1]); 
       val[i] = max_val; 
   } 
  
   return val[n]; 
} 

val[i] 는 i 길이 일때 최대값, 길이는 1부터 시작

Time Complexity 는 O(n2)로 첫번째 방법의 worst case 보다 낫다.

재귀함수 구현은 다음과 같음

int cutting_rod_recurv(int price[], int n, int cut)
{
    if( cut == n-1 )
    {
        return price[n-1];
    }

    return std::max( (price[cut] + price[n-1-cut-1]), cutting_rod_recurv(price, n, cut+1) );
}