钢条切割(动态规划)
递归求解
钢条切割问题是《算法导论》上动态规划部分的第一个例题。这里就以算法导论上的例子来讨论这个问题。
例如当我们切割n=4的钢条时,切割情况如下:
用递归方法解决这个问题时,代码如下(以n=4为例):
#include <bits/stdc++.h>
using namespace std;// 价格数组
int val[11] = {0,1,5,8,9,10,17,17,20,24,30};int cut_rod(int p[],int n)
{if(n == 0) return 0; // 边界条件int q = -1;for(int i = 1;i <= n;i++)q = max(q,p[i]+cut_rod(p,n-i));return q;
}int main()
{cout << cut_rod(val,4) << endl;return 0;
}
其中 for(int i = 1;i <= n;i++) q = max(q,p[i]+cut_rod(p,n-i)) :
p[i]+cut_rod(p,n-i)为前 i 个长度不切割,后面 n-i 个长度切割所能取得的最大值。
动态规划求解
实际运行的时候我们会发现当n的规模偏大的时候,再使用递归的话,程序很有可能超过一个小时的运行时长。所以我们展示如何将这个钢条切割问题转换为一个更高效的动态规划算法。
朴素递归算法之所以效率很低,是因为它反复求解相同的子问题。因此动态规划方法仔细安排求解顺序,对每个子问题只求解一次,并将结果保存下来。所以我们看出:动态规划是付出额外的空间来节省计算时间。
这一题可以用一个数组 f[] ,其中 f[i] 表示长度为 i 的钢条能得到的最大的价值。因此状态转移方程就能写为:
其中,val(j) 表示前 j 个长度不切割的钢条的价值。
代码实现:
#include <bits/stdc++.h>
using namespace std;const int N = 11;int n;
int val[11] = {0,1,5,8,9,10,17,17,20,24,30}; // 价格数组
int f[N];int main()
{n = 10;for(int i = 1;i <= n;i++){for(int j = 1;j <= i;j++){f[i] = max(f[i],val[j]+f[i-j]);}}cout << "最大价值:" << f[n] << endl;return 0;
}
运行结果:
构造最优解
我们还可以拓展动态规划算法,使之对每个子问题不仅保存最大价值,而且给出切割方案。
对于长度为j的钢条,我们保存最优解对应的第一段钢条的切割长度x[j]。接着就可以循环求出长度为j-x[j]的切割方案。
代码实现:
#include <bits/stdc++.h>
using namespace std;const int N = 11;int n;
int val[11] = {0,1,5,8,9,10,17,17,20,24,30}; // 价格数组
int f[N];
int x[N];int main()
{n = 10;for(int i = 1;i <= n;i++){for(int j = 1;j <= i;j++){if(f[i] < val[j]+f[i-j]) x[i] = j; // 记录第一次切割的长度f[i] = max(f[i],val[j]+f[i-j]);}}cout << "最大价值:" << f[n] << endl;// 构造最优解cout << "切割方案:";int ll = n;while(ll > 0){cout << x[ll] << " ";ll -= x[ll];}cout << endl;return 0;
}
运行结果: