钢条切割

一、前言

问题来源《算法导论》第15章。

 

二、题目

给定一段长度为n英寸的钢条和一个价格表p(i=1, 2, .., n),求切割钢条方案,使得销售收益r,最大。注意,如果长度为n英寸的钢条的价格p,足够大,最优解可能就是完全不需要切割。图15-1给出了一个价格表的样例。

 

三、思路

3.1:朴素递归算法

方法一:朴素递归算法

自顶向下切割,第一切割长度第一段为0,1,...,n/2,第二段为n,n-1,...n/2。第一段价值为p(i),继续将第二段进行重复切割。找出所有收益最大值即为所求。

这里需要理解为什么切割的第一段价值是p(i),而第二段需要重复切割过程?因为某一种切割有最大收益,第一段中必定包含第一次切割(不切割为0),后续的切割只需要对第二段进行前面重复切割步骤,那最优切割会包含再其中。

 

3.2 动态规划算法

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

方法二: 动态规划,带备忘的自顶向下法

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

方法三:动态规划,自底向上法

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

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

时间复杂度:O(n2);

四、实现编码

//==========================================================================
/**
* @file : CutRod.h
* @title: 钢条切割
* @purpose : 给定一段长度为n英寸的钢条和一个价格表p(i=1, 2, .., n),求切割钢条方案,使得销售收益r,最大。注意,如果长度为n英寸的钢条的价格p,足够大,最优解可能就是完全不需要切割。
*
*/
//==========================================================================
#pragma once
//#pragma warning( disable : 26454 )

#include <iostream>
using namespace std;

const int gRod[] = { 0,1,5,8,9,10,17,17,20,24,30 };

// 方法一:朴素递归算法
int CutRod(const int* p, int n)
{
    if (n == 0)
    {
        return 0;
    }

    int q = p[n];   //不切割价值
    for (int i = 1; i <= n/2; ++i)
    {
        int tmp = p[i] + CutRod(p, n - i);
        q = tmp > q ? tmp : q;
    }

    return q;
}

// 方法二:动态规划,带备忘的自顶向下法
int MemoizedCutRodAux(const int* p, int n, int* r)
{
    if (r[n] >= 0)
    {
        return r[n];            //首先检查所需的值是否存在
    }

    int q = p[n];   //不切割价值
    if (n == 0)
    {
        q = 0;
    }
    else
    {
        for (int i = 1; i <= n/2; ++i)
        {
            int tmp = p[i] + MemoizedCutRodAux(p, n - i, r);
            q = tmp > q ? tmp : q;
        }
    }
    r[n] = q;

    return q;
}

int MemoizedCutRod(const int* p, int n)
{
    int* r = new int[n + 1];
    for (int i = 0; i <= n; ++i)
    {
        r[i] = -1;
    }

    return MemoizedCutRodAux(p, n, r);
}

// 方法三:动态规划,自底向上法
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];
}

// 以下为测试代码
#define NAMESPACE_CUTROD namespace NAME_CUTROD {
#define NAMESPACE_CUTRODEND }

//
// 测试 用例 START
NAMESPACE_CUTROD

void test(const char* testName, int len, int expect)
{
    if (len < 0 || len > sizeof(gRod) / sizeof(gRod[0]))
    {
        cout << testName << ", len:" << len << ", 参数错误" << endl;
        return;
    }
    
    int result1 = CutRod(gRod, len);
    int result2 = MemoizedCutRod(gRod, len);
    int result3 = BottomUpCutRod(gRod, len);

    if (result1 == expect)
    {
        cout << testName << ", len:" << len << ", solution_1 passed." << endl;
    }
    else
    {
        cout << testName << ", len:" << len << ", solution_1 failed. result1: " << result1 << endl;
    }

    if (result2 == expect)
    {
        cout << testName << ", len:" << len << ", solution_2 passed." << endl;
    }
    else
    {
        cout << testName << ", len:" << len << ", solution_2 failed. result1: " << result2 << endl;
    }

    if (result3 == expect)
    {
        cout << testName << ", len:" << len << ", solution_3 passed." << endl;
    }
    else
    {
        cout << testName << ", len:" << len << ", solution_3 failed. result1: " << result3 << endl;
    }
}

void Test1()
{
    int len = 0;
    int expect = 0;

    test("Test1()", len, expect);
}

void Test2()
{
    int len = 1;
    int expect = 1;

    test("Test2()", len, expect);
}

void Test3()
{
    int len = 2;
    int expect = 5;

    test("Test3()", len, expect);
}

void Test4()
{
    int len = 3;
    int expect = 8;

    test("Test4()", len, expect);
}

void Test5()
{
    int len = 4;
    int expect = 10;

    test("Test5()", len, expect);
}

void Test6()
{
    int len = 5;
    int expect = 13;

    test("Test6()", len, expect);
}

void Test7()
{
    int len = 6;
    int expect = 17;

    test("Test7()", len, expect);
}

void Test8()
{
    int len = 7;
    int expect = 18;

    test("Test8()", len, expect);
}

void Test9()
{
    int len = 8;
    int expect = 22;

    test("Test9()", len, expect);
}

void Test10()
{
    int len = 9;
    int expect = 25;

    test("Test10()", len, expect);
}

void Test11()
{
    int len = 10;
    int expect = 30;

    test("Test11()", len, expect);
}

NAMESPACE_CUTRODEND
// 测试 用例 END
//

void CutRod_Test()
{
    NAME_CUTROD::Test1();
    NAME_CUTROD::Test2();
    NAME_CUTROD::Test3();
    NAME_CUTROD::Test4();
    NAME_CUTROD::Test5();
    NAME_CUTROD::Test6();
    NAME_CUTROD::Test7();
    NAME_CUTROD::Test8();
    NAME_CUTROD::Test9();
    NAME_CUTROD::Test10();
    NAME_CUTROD::Test11();
}

执行结果:

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值