Rod cutting

问题

问题描述

Serling Enterprises buys long steel rods and cuts them into shorter rods, which it then sells. Each cut is free. The management of Serling Enterprises wants to know the best way to cut up the rods.

We assume that we know, for i = 1, 2,...., the price pi in dollars that Serlingnterprises charges for a rod of length i inches. Rod lengths are always an integral number of inches.

问题形式化定义

We denote a decomposition into pieces using ordinary additive notation, so that 7 = 2 + 2 + 3 indicates that a rod of length 7 is cut into three pieces—two of length 2 and one of length 3. If an optimal solution cuts the rod into k pieces, for some 1 ≦ k ≦ n, then an optimal decomposition
n = i1 + i2 +...+ ik of the rod into pieces of lengths i1,i2,...,ik provides maximum corresponding revenue rn = pi1 + pi2 +...+ pik.

Input:

  • a rod of length n inches
  • a table of prices pi for i = 1,2,...,n.

Output:

  • an optimal decomposition
    n = i1 + i2 +...+ ik.

算法分析

算法思想

首先这个算法是可以进行遍历求解的,对于一个长度为n的rod进行切割,切割方法至多有2n-1种(可以在rod上的n-1个位置上决定是否切割)。当然了,这样的判断考虑了重复情况,例如,3 = 1 + 2和3 = 2 + 1在本质上是相同的。那么对于去除相同的情况,即只能以每次切割出的rod的长度不递减,那么,这是的解空间的数量可以有划分函数给出,其结果虽然小于2n-1,但也要比n的任何多项式结果要大的多,这里我们将不考虑这种情况,而是使用前一种解空间的判断。

其解空间为指数级,优化问题需要遍历整个解空间,该算法的时间复杂度为指数级,这是我们不愿意看到的,因此,希望使用其他方法进行处理。

这里将直接给出优化解的代价递归方程:

  • rn = max(pn, r1+rn-1, r2+rn-2,..., rn-1+r1).
  • rn = max(1 < = i < = n)(pi+ rn-i).

其实,这两个递归方程都与切割方式相对应,第一个对应:切割成两个rod,两半rod再进行切割为更小的rod,第二个,切割成两个rod,前一个rod将是成品,不在进行切割,而后一个rod可以进行再切割。显然,虽然方式不同,但两个递归方程所反映的本质实际上是相同的。

算法伪代码

由两种方式构成的动态规划算法的代价方程都是:rn = max( 1 < = i < = n )( pi+ rn-i )

s[i]表示:长度为i的rod cutting的最优解中,所截取的成品rod长度为s[i],而对于i - s[i]的rod则继续进行处理。(这里可以参照rn = max(1 < = i < = n)(pi+ rn-i) 所对应的切割方式进行理解)

top-down with memoization

MEMOIZED-CUT-ROD(p,n)
1. let r[0...n] and s[0...n] be a new array
2. for i = 0 to n
3.    r[i] = -∞
4. return MEMOIZED-CUT-ROD-AUX(p,n,r,s) and s
MEMOIZED-CUT-ROD-AUX(p,n,r,s)
1. if r[n] >= 0
2.    return r[n]
3. if n == 0
4.    q = 0 
5. else
6.    q = -∞
7.    for i = 1 to n
8.        m = p[i] + MEMOIZED-CUT-ROD-AUX(p,n-i,r)
9.        if q < m
10.          q = m
11.          s[n] = i
12. r[n] = q
13. return q

对于该算法而言,使用了摊还分析中聚集方法的思想,将整个子问题求解的序列当作一个整体来进行看待,尽可能地使得时间复杂度更加精确,更接近该操作的特点。

Because a recursive call to solve a previously solved subproblem returns immediately, MEMOIZED-CUT-ROD solves each subproblem just once. It solves subproblems for sizes 0,1,2,..,n. To solve a subproblem of size i,the for loop of lines 6–7 iterates i times. Thus, the total number of iterations of this for loop, over all recursive calls of MEMOIZED-CUT-ROD, forms an arithmetic series, giving a total of O(n2) iterations, just like the inner for loop of BOTTOM-UPCUT-ROD.

bottom-up method

BOTTOM-UP-CUT-ROD(p,n)
1. let r[0...n] and s[0...n] be a new array 
2. r[0] = 0 
3. for j = 1 to n 
4.     q = -∞
5.     for i = 1 to j 
6.        if q < p[i] + r[j - i] 
7.           q = p[i] + r[j - i]
8.           s[j] = i
9.     r[j] = q 
10. return r[n] and s

对于此算法的时间复杂度分析,显然该循环中有双层嵌套循环,外层循环次数为n。内层循环次数为一个等差数列为:1,2,...,n,所以在整个程序的运行过程中,内层循环的执行次数为n(n+1)/2 = O(n2)。其实,对于精度要求并不是很高的情况来讲,双层嵌套循环的时间复杂度分析,可以直接考虑外层循环的总次数乘以内层循环的最大次数,可以直接估计其时间复杂度。

构造优化解的伪代码

PRINT-CUT-ROD-SOLUTION(p,n)  
1. (r,s) = MEMOIZED-CUT-ROD(p,n);
/* (r,s) = BOTTOM-UP-CUT-ROD(p,n)*/
2. while n > 0 
3.     print s[n]  
4.     n = n - s[n]  

代码

#include<stdio.h>
#include<stdlib.h>
int MEMOIZED_CUT_ROD_AUX(int* p, int n, int* r, int* s);
void PRINT_CUT_ROD_SOLUTION(int* p, int n);
int* MEMOIZED_CUT_ROD(int* p, int n, int* r, int* s);
void BOTTOM_UP_CUT_ROD(int* p, int n, int* r, int* s);

int MEMOIZED_CUT_ROD_AUX(int* p, int n, int* r, int* s) {
    int q;
    if (*(r+n) >= 0)
        return r[n];
    if (n == 0)
        q = 0;
    else {
        q = INT_MIN;
        for (int i = 1; i <= n; i++) {
            int m = *(p+i) + MEMOIZED_CUT_ROD_AUX(p, n - i, r, s);
            if (q < m) {
                q = m;
                *(s+n) = i;
            }
        }
    }
    r[n] = q;
    return r[n];
}

void PRINT_CUT_ROD_SOLUTION(int* p, int n) {
    int m = n;
    int* r = (int*)malloc((n + 1) * sizeof(int));
    if (!r) {
        printf("创建数组失败!\n");
        exit(1);
    }
    int* s = (int*)malloc((n + 1) * sizeof(int));
    if (!s) {
        printf("创建数组失败!\n");
        exit(1);
    }
    MEMOIZED_CUT_ROD(p, n, r, s);
    while (m > 0) {
        printf("%d  ", *(s + m));
        m = m - *(s + m);
    }
    printf("\n");
    m = n;
    BOTTOM_UP_CUT_ROD(p, n, r, s);
    while (m > 0) {
        printf("%d  ", *(s + m));
        m = m - *(s+m);
    }
}

void BOTTOM_UP_CUT_ROD(int* p, int n,int *r,int *s) {
    *r = 0;
    for (int j = 1; j <= n; j++) {
        int q = INT_MIN;
        //s[j] = INT_MIN;
        for (int i = 1; i <= j; i++) {
            if (q < *(p+i) + *(r+j - i)) {
                q = *(p + i) + *(r + j - i);
                *(s+j) = i;
            }
        }
        r[j] = q;
    }
}

int* MEMOIZED_CUT_ROD(int* p, int n,int* r, int* s) {
    for (int i = 0; i <= n; i++) {
        *(r+i) = INT_MIN;
    }
    MEMOIZED_CUT_ROD_AUX(p, n, r, s);
    return s;
}

int main()
{  
    int n = 7;
    int p[11] = { 0,1,5,8,9,10,17,17,20,24,30 };
    PRINT_CUT_ROD_SOLUTION(p,n);
    return 0;
}
posted @ 2020-09-13 23:02  zqybegin  阅读(307)  评论(0编辑  收藏  举报