一、前言
问题来源《算法导论》第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();
}
执行结果: