算法导论-动态规划-钢条切割

动态规划通常用于解决最优化问题,在这类问题中,通过做出一组选择来达到最优解。在做出每个选择的同时,通常会生成与原问题形式相同的子问题。当多于一个选择子集都生成相同的子问题时,动态规划技术通常就会很有效,其关键技术就是对每个这样的子问题都保存其解,当其重复出现时即可避免重复求解。

钢条切割问题

Serling公司购买长钢条,将其切割为短钢条出售。切割工序本身没有成本支出。公司管理层希望知道最佳的切割方案。假定我们知道Serling公司出售一段长为i英寸的钢条的价格为pi(i=1,2,…,单位为美元)。钢条的长度均为整英寸。图15-1给出了一个价格表的样例。

钢条切割问题是这样的:给定一段长度为n英寸的钢条和一个价格表pi(i=1,2,…n),求切割钢条方案,使得销售收益rn最大。注意,如果长度为n英寸的钢条的价格pn足够大,最优解可能就是完全不需要切割。

一、问题分析

长度为n英寸的钢条共有2n-1种不同的切割方案,因为在距离钢条左端i(i=1,2,…n)英寸处,总是可以选择切割或不切割。

如果一个最优解将钢条切割为k段(对某个),那么最优切割方案,将钢条切割为长度分别为i1,i2...ik的小段得到的最大收益为

对于。其中,pn对应不切割,对于每个i=1,2,…,n-1,首先将钢条切割为长度为i和n-i的两段,接着求解这两段的最优切割收益ri和rn-i(每种方案的最优收益为两段的最优收益之和)。当完成首次切割后,我们将两段钢条看成两个独立的钢条切割问题实例。通过组合两个相关子问题的最优解,并在所有可能的两段切割方案中选取组合收益最大者,构成原问题的最优解。

钢条切割问题还存在一种相似的但更为简单的递归求解方法:将钢条从左边切割下长度为i的一段,只对右边剩下的长度为n-i的一段继续进行切割,对左边的一段则不再进行切割。这样得到的公式为:。这样原问题的最优解只包含一个相关子问题(右端剩余部分)的解,而不是两个。

二、算法实现

1、朴素递归算法

自顶向下递归实现参考代码为:

int CutRod(const int *p, int n)
{
    if (n == 0)
    {
        return 0;
    }

    int q = -1;
    for (int i = 1; i <= n; ++i)
    {
        int tmp = CutRod(p, n - i);
        if (q < tmp)
        {
            q = tmp;
        }
    }

    return q;
}

分析:自顶向下递归实现的CutRod效率很差,原因在于CutRod反复地用相同的参数值对自身进行递归调用,即它反复求解相同的子问题。它的运行时间为。对于长度为n的钢条CutRod考察了所有2n-1种可能的切割方案。递归调用树共有2n-1个叶结点,每个叶结点对应一种可能的切割方案。

2、动态规划算法

朴素递归算法之所以效率很低,是因为它反复求解相同的子问题。因此,动态规划方法仔细安排求解顺序,对每个子问题只求解一次,并将结果保存下来。如果随后再次需要此子问题的解,只需查找保存的结果,而不必重新计算。因此,动态规划方法是付出额外的内存空间来节省计算空间。

动态规划有两种等价的实现方法。

(1)带备忘的自顶向下法

此方法仍按自然的递归形式编写过程,但过程会保存每个子问题的解(通常保存在一个数组或散列表中)。当需要一个子问题的解时,过程首先检查是否已经保存过此解。如果是,则直接返回保存的值,从而节省了计算时间;否则,按通常方式计算这个子问题。

(2)自底向上法

这种方法一般需要恰当定义子问题“规模”的概念,使得任何子问题的求解都只依赖于“更小的”子问题的求解。因此,我们可以将子问题按照规模顺序,由小至大顺序进行求解。当求解某个子问题时,它所依赖的那些更小的子问题都已求解完毕,结果已经保存。每个子问题只需求解一次,当我们求解它时,它的所有前提子问题都已求解完成。

说明:两种方法得到的算法具有相同的渐进运行时间,仅有的差异是在某些特殊情况下,自顶向下方法并未真正递归地考察所有可能的子问题。由于没有频繁的递归函数调用的开销,自底向上方法的时间复杂度函数通常具有更小的系数。

下面给出动态规划-带备忘的自顶向下过程参考代码:

 1 int MemoizedCutRodAux(const int *p, int n, int *r)
 2 {
 3     if (r[n] >= 0)
 4     {
 5         return r[n];            //首先检查所需的值是否存在
 6     }
 7
 8     int q = -1;
 9     if (n == 0)
10     {
11         q = 0;
12     }
13     else
14     {
15         for (int i = 1; i <= n; ++i)
16         {
17             int tmp = p[i] + MemoizedCutRodAux(p, n - i, r);
18             if (q < tmp)
19             {
20                 q = tmp;
21             }
22         }
23     }
24     r[n] = q;
25
26     return q;
27 }
28
29 int MemoizedCutRod(const int *p, int n)
30 {
31     int *r = new int[n + 1];
32     for (int i = 0; i <= n; ++i)
33     {
34         r[i] = -1;
35     }
36
37     return MemoizedCutRodAux(p, n, r);
38 }

下面给出动态规划-自底向上过程参考代码:

int BottomUpCutRod(const int *p, int n)
{
    int *r = new int[n + 1];
    r[0] = 0;

    for (int i = 1; i <= n; ++i)
    {
        int q = -1;
        for (int j = 1; j <= i; ++j)
        {
            int tmp = p[j] + r[i - j];
            q = q > tmp ? q : tmp;
        }
        r[i] = q;
    }

    return r[n];
}

说明:自顶向下与自底向上算法具有相同的渐进运行时间

最后给出重构解参考代码:

 1 #include <iostream>
 2 using namespace std;
 3
 4 void ExtendedBUCutRod(const int *p, int n, int *r, int *s)
 5 {
 6     r[0] = 0;
 7     for (int i = 1; i <= n; ++i)
 8     {
 9         int q = -1;
10         for (int j = 1; j <= i; ++j)
11         {
12             int tmp = p[j - 1] + r[i - j];
13             if (q < tmp)
14             {
15                 q = tmp;
16                 s[i] = j;
17             }
18         }
19         r[i] = q;
20     }
21 }
22
23 //r[]保存长度为i的钢条最大收益,s[]保存最优解对应的第一段钢条的切割长度
24 void PrintBUCutRod(const int *p, int n, int *r, int *s)
25 {
26     ExtendedBUCutRod(p, n, r, s);
27     cout << "长度为" << n << "的钢条最大收益为:" << r[n] << endl;
28
29     cout << "最优方案的钢条长度分别为:";
30     while (n > 0)
31     {
32         cout << s[n] << " ";
33         n = n - s[n];
34     }
35     cout << endl;
36 }
37
38 //Test
39 int main()
40 {
41     int n;
42     while (cin >> n)
43     {
44         int *p = new int[n];
45         for (int i = 0; i < n; ++i)
46         {
47             cin >> p[i];
48         }
49         int *r = new int[n + 1];
50         int *s = new int[n + 1];
51
52         PrintBUCutRod(p, n, r, s);
53     }
54
55     return 0;
56 }

一个测试案例为:

时间: 2025-01-16 15:17:16

算法导论-动态规划-钢条切割的相关文章

算法导论 动态规划 钢条切割问题的自底向上解法

正式应用动态规划. 适用于动态规划解决的问题应拥有以下两个要素: 1. 最优子结构(最佳选择) 2.子问题重叠(最终的最优解的每个分部步骤,都是当前最优的子解.与贪心算法试图通过局部最优解来组合成最优解的思想相似) 下面第一版代码中,依旧存在与上一篇第一版代码相同的问题--只能求解p数组中给出的最大限度.N>=10,代码就不能够求解出正确答案.(代码中你们都懂的- -卖个萌(O(∩_∩)O哈哈~) 主要用第一版代码记录思路. 共给出两个功能函数. Max()和Bottom_Cut_Rod(),作

算法导论--动态规划(钢条切割)

钢条切割问题 现有一段长度为n英寸的钢条和一个价格表pi,求切割方案使销售利益最大rn最大 长度为n英寸的钢条共有2n?1种不同的切割方案,因为可以每个整英寸的位置都可以决定切割或者不切割. 为了得到rn最大,可以把这个问题分成子问题求解,先切一刀,再考虑余下的部分的最大收益即求 rn=max{pk+rn?k}(k=1,2,3-n-1), pk部分不进行继续切割,直接作为一个整体售出 ; rn?k部分继续切割,考虑所有的情况,分成子问题. 求出所有k值对应的收益最大者作为rn 也有可能不进行任何

算法导论--动态规划(矩阵链乘法)

矩阵链乘法问题 给定一个n个矩阵的序列?A1,A2,A3...An?,我们要计算他们的乘积:A1A2A3...An.因为矩阵乘法满足结合律,加括号不会影响结果.可是不同的加括号方法.算法复杂度有非常大的区别: 考虑矩阵链:?A1,A2,A3?.三个矩阵规模分别为10×100.100×5.5×50 假设按((A1A2)A3)方式,须要做10?100?5=5000次,再与A3相乘,又须要10?5?50=2500,共须要7500次运算: 假设按(A1(A2A3))方式计算.共须要100?5?50+10

算法导论--动态规划(最长公共子序列)

最长公共子序列问题(LCS) 给定两个序列X=?x1,x2,x3...xm?和Y=?y1,y2,y3...xn?,求X和Y的最长公共子序列. 例如:X=?A,B,C,B,D,A,B?,和Y=?B,D,C,A,B,A?,的最长公共子序列为?B,C,B,A?,长度为4: 对于此问题,可以采用暴力求解的方式来比对,即穷举出X的所有子序列,用每个子序列与y做一 一比较.假如X序列共有m个元素,对每个元素可以决定选或不选,则X的子序列个数共有2m个,可见与长度m呈指数阶,这种方法效率会很低. 动态规划 前

算法导论--动态规划(装配线调度)

装配线问题: 某个工厂生产一种产品,有两种装配线选择,每条装配线都有n个装配站.可以单独用,装配线1或2加工生产,也可以使用装配线i的第j个装配站后,进入另一个装配线的第j+1个装配站继续生产.现想找出通过工厂装配线的最快方法. 装配线i的第j个装配站表示为Si,j,在该站的装配时间是ai,j 如果从 Si,j装配站生产后,转移到另一个生产线继续生产所耗费的时间为ti,j 进入装配线花费时间ei,完成生产后离开装配线所耗费时间为xi 令f*表示通过生产所有路线中的最快的时间 令fi[j]表示从入

动态规划 钢条切割问题的朴素解法

第一版代码 : #include <iostream> using namespace std; int max(int a,int b) { if(a>=b)return a; else return b; } int cut_rod(int *p,int n) { int q=NULL; if(n==0)return 0; else for(int i=0;i<n;i++) { q=max(q,p[i]+cut_rod(p,n-1-i)); } return q; } int

算法导论动态规划 15.1-3 钢条切割问题

算法导论的第一个动态规划问题--钢条切割 我们知道钢条的长度和价格为: 长度i 1 2 3 4 5 6 7 8 9 10 价格pi 1 5 8 9 10 17 17 20 24 30 书上的两种方法已经很清楚,这里主要说一下课后练习里面15-3钢条成本加上切割成本,即要切割的次数最少.15-4返回切割方案 #include<fstream> #include<iostream> using namespace std; int main() { int const N = 11;

算法导论---------动态规划之钢条切割

动态规划方法通常用来求解最优化问题.动态规划算法设计步骤: 1.刻画一个最优解的结构特征. 2.递归定义最优解的值. 3.计算最优解的值,通常采用自底向上的方法. 4.利用计算出的信息构造一个最优解. 动态规划的实现方法: 带备忘的自顶向下法:此方法仍按自然的递归形式编写过程,但过程会保存每个子问题的解(通常保存在一个数组或散列表中).当需要一个子问题的解时,过程首先检查是否已经保存过此解.如果是,则直接返回保存的值,从而节省了计算时间:否则,按通常方式计算这个子问题. 自底向上法:这种方法一般

动态规划 钢条切割问题

#include <stdio.h> /* *钢条切割问题: *问题描述 假设公司出售一段长度为i英寸的钢条的价格为Pi(i = 1, 2, ...单位:美元),下面给出了价格表样例: 长度i 1 2 3 4 5 6 7 8 9 10 价格Pi 1 5 8 9 10 17 17 20 24 30 切割钢条的问题是这样的:给定一段长度为n英寸的钢条和一个价格表Pi,求切割方案,使得销售收益Rn最大. */ //假定价格一开始已经给出了,用全局的写死 #define max(x,y) (x)>