软考考点之下午题算法2018上半年 钢条切割问题

如2018年上半年真题:

第4题:阅读下列说明和C代码,回答问题1和问题2,将解答填入答题纸的对应栏内。
【说明】
某公司购买长钢条,将其切割后进行出售。切割钢条的成本可以忽略不计,钢条的长度为整英
寸。已知价格表p,其中pi(i=1,2,...,m)表示长度为i英寸的钢条的价格。现要求解使销售收
益最大的切割方案。
求解此切割方案的算法基本思想如下:
假设长钢条的长度为n英寸,最佳切割方案的最左边切割段长度为i英寸,则继续求解剩余长度为
n-i 英寸钢条的最佳切割方案。考虑所有可能的i,得到的最大收益rn对应的切割方案即为最佳切
割方案。rn的递归定义如下:
r n
=max1≤ i ≤n(pi +r n-i)
对此递归式,给出自顶向下和自底向上两种实现方式。
【C代码】
/* 常量和变量说明
n:长钢条的长度
p[]:价格数组
*/
#define LEN 100
int Top_Down_ Cut_Rod(int p[],int n){ /*自顶向下*/
int r=0;//返回变量
int i;//
if(n == 0){//长度为0,返回0
return 0;
}
for(i=1; (1) ;i++){
int tmp = p[i]+Top_Down_Cut_Rod(p,n-i);    //表达的是现在的价格+剩下n-i段的价格
r=(r>=tmp)?r:tmp;
}
return r;
}
int Bottom_Up_Cut_Rod(int p[],int n){ /*自底向上*/
int r[LEN]={0};
int temp=0;
int i,j;
for(j=1;j<=n;j++){
temp=0;

for(i=1;    (2);     i++)

{

     temp =   (3)  ;//(temp>r[i]+r[j-i])?temp:r[i]+r[j-i];

}

(4)  ;//r[j]=(temp>r[j])?temp:r[j];
}
return r[n];
}

题:4.1 (8分)
根据说明,填充C代码中的空(1)~(4)。
题:4.2 (7分)
根据说明和C代码,算法采用的设计策略为(5)。
求解rn时,自顶向下方法的时间复杂度为(6);自底向上方法的时间复杂度为(7)(用O表示)
解:第一次读题就没看明白题意,不知所云,可见自己对算法的无知……。

如果断句正确的话,是能够脑补出本题的题意的,为什么偏得看答案或真正的扩展后才能真正的理解呢???????

空1,看递归公式  ,答对了,i<=n

自底向下算法,只是给出了核心算法,如果没有接触过分割钢条问题的,是不可能答出来的,需要脑补的东西实在是太多了

for(i=1;    (2);     i++)//空2 ,应该填i<j,先从小规模求分段下的收益

{

     temp =   (3)  ;//(temp>r[i]+r[j-i])?temp:r[i]+r[j-i];这里的实质是求各小段内的最优解

}

(4)  ;                     //r[j]=(temp>r[j])?temp:r[j];//与不分割的情况比较

对于递归算法的时间复杂度问题,得看仔细,刚开始就没考虑到递归这一块,可见自己知识体系的缺失?

详见,软考考点之如何估算一个算法的时间复杂度和空间复杂度

 

真正的分割钢条问题描述:

题目描述:给定一段长度为n英寸的钢条和一个价格表,求切割方案,使销售收益Rn最大。
注:若长度为n英寸的钢条的价格Pn足够大,最优解可能就是完全不需要切割。

长度(i)    1    2    3    4    5    6    7    8    9    10
价格(R)    1    5    8    9    10    17    17    20    24    30
给定一个长度为n的钢条,该钢条经过有效切割,最多能卖出多少钱?
 

长度为n英寸的钢条共有2^(n-1)种不同切割方案,因为在距离钢条左端 i (i=1, 2, … , n-1)英寸处,总是可以选择切割或者不切割。用普通的加法符号表示切割方案,因此7=2+2+3表示将长度为7的钢条切割为3段:2英寸,2英寸,3英寸。

更一般的,对于Rn(n≥1),可以用更短的钢条的最优切割收益来描述它:

Rn = max(Pn, R1+Rn-1, R2 + Rn-2, … , Rn-1 + R1)

第一个参数Pn对应不切割,直接出售长度为n的方案。
其他n-1个参数对应n-1种方案。对每个i=1,2,….,n-1,将钢条切割为长度为i和n-i的两段,接着求解这两段的最优切割收益Ri和Rn-i;(每种方案的最优收益为两段的最优收益之和)。
由于无法预知哪种方案会获得最优收益,必须考察所有可能的 i ,选取其中收益最大者。若不切割时收益最大,当然选择不切割
 

注意到:
为了求解规模为n的原问题,先求解子问题(子问题形式完全一样,但规模更小)。
即首次完成切割后,将两段钢条看成两个独立的钢条切割问题实例。
通过组合两个相关子问题的最优解,并在所有可能的两段切割方案中获取收益最大者,构成原问题的最优解。
称钢条切割问题满足最优子结构性质:
问题的最优解由相关子问题的最优解组合而成,而这些子问题可以独立求解。

 

除上述解法,问题可化简为一种相似的递归:从左边切割下长度为 i 的一段,只对右边剩下的长度为 n-i 的一段进行继续切割(递归求解),对左边一段则不再进行切割。   这里其实就是软考题的描述的部分

即问题分解的方式为:将长度为n的钢条分解为左边开始一段,以及剩余部分继续分解的结果。(这样,不做任何切割的方案可以描述为:第一段长度为n,收益为Pn,剩余部分长度为0,对应收益为R0 = 0)。于是得到上面公式的简化版本:

收益公式

在此公式中,原问题的最优解只包含一个相关子问题(右端剩余部分的解),而不是两个。
 

算法设计(递归实现)

package com.bean.algorithmbasic;

public class CutRob {
    public static int[] prices = { 1, 5, 8, 9, 10, 17, 17, 20, 24, 30 };
    public static int solution(int length) {
        if (length == 0)
            return 0;
        int result = Integer.MIN_VALUE;
        for (int i = 1; i <= length; i++) {
            result = Math.max(result, prices[i - 1] + solution(length - i));//这里为什么是prices[i-1]呢?其实是因为数组prices是从0开始的
        }
        return result;
    }

    public static void main(String[] args) {

        long startTime = System.currentTimeMillis();

        for (int i = 1; i <= prices.length; i++)
            System.out.println("长度为" + i + "的最大收益为:" + solution(i));

        long endTime = System.currentTimeMillis();
        System.out.println("程序运行耗时(ms): " + (endTime - startTime));

    }
}

运行结果:

长度为1的最大收益为:1
长度为2的最大收益为:5
长度为3的最大收益为:8
长度为4的最大收益为:10
长度为5的最大收益为:13
长度为6的最大收益为:17
长度为7的最大收益为:18
长度为8的最大收益为:22
长度为9的最大收益为:25
长度为10的最大收益为:30
程序运行耗时(ms): 1

当给出的锯条长度更大时,递归求解的速度已经非常慢了。慢到几乎不能接受。
 

算法设计(动态规划实现—DP)这就是DP(动态规划,dynamic programming)

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

 

package com.bean.algorithmbasic;

import java.util.Arrays;

public class CutRobDP {

    public static int[] prices = { 1, 5, 8, 9, 10, 17, 17, 20, 24, 30 };

    /** 自顶向下 */
    public static int mem_cut_rod(int n) {
        int[] dp = new int[n + 1]; // 辅助数组dp
        Arrays.fill(dp, Integer.MIN_VALUE); // 初始化为负无穷
        return mem_cut_rod_aux(n, dp);
    }

    /** 自顶向下法的辅助函数 */
    private static int mem_cut_rod_aux(int n, int[] dp) {
        if (dp[n] >= 0)
            return dp[n]; // 如果子问题已经解过,直接返回
        int max = Integer.MIN_VALUE;
        if (n == 0)
            max = 0; // 如果长度为0,则最大收益为0
        else { // 长度若不为0
            for (int i = 1; i <= n; i++) // 找到最大收益
                max = Math.max(max, prices[i - 1] + mem_cut_rod_aux(n - i, dp));
        }
        dp[n] = max; // 把计算得到的最大收益存入结果
        return max; // 返回结果
    }

    public static void main(String[] args) {

        long startTime=System.currentTimeMillis();

        for (int i = 1; i <= prices.length; i++)
            System.out.println("长度为" + i + "的最大收益为:" + mem_cut_rod(i));

        long endTime=System.currentTimeMillis();

        System.out.println("程序运行耗时(ms): "+(endTime-startTime));
    }
}

 

自底向上法(bottom-up method)
该方法一般需要恰当定义子问题“规模”的概念,使得任何子问题的求解都只依赖于“更小的”子问题的求解。
因而可以将子问题按规模排序,按由小到大的顺序进行求解
当求解某个子问题时,所依赖的那些更小的子问题都已经求解完毕,结果已经保存。
每个子问题只求解一次,当求解它时(也是第一次遇到它),所有前提子问题都已经求解完成。
两种方法得到的算法具有相同的渐进运行时间,

仅有的差异是在某些特殊情况下,自顶向下方法并未真正递归地考察所有可能的子问题。
由于没有频繁的递归调用开销,自底向上的复杂度函数通常具有更小的系数
 

package com.bean.algorithmbasic;

public class CutRobDP2 {

    public static int[] prices = { 1, 5, 8, 9, 10, 17, 17, 20, 24, 30 };

    /** 自底向上法 */
    private static int bottom_up_cut_rod(int n) {
        int[] dp = new int[n + 1];
        dp[0] = 0;
        for (int j = 1; j <= n; j++) {
            int max = Integer.MIN_VALUE;
            for (int i = 1; i <= j; i++) {
                max = Math.max(max, prices[i - 1] + dp[j - i]);
            }
            dp[j] = max;
        }
        return dp[n];
    }

    public static void main(String[] args) {

        long startTime = System.currentTimeMillis();

        for (int i = 1; i <= prices.length; i++)
            System.out.println("长度为" + i + "的最大收益为:" + bottom_up_cut_rod(i));

        long endTime = System.currentTimeMillis();

        System.out.println("程序运行耗时(ms): " + (endTime - startTime));
    }
}

 

  • 7
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

guangod

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值