1.问题描述
一公司购买长钢条,将其切为短钢条出售,假设切割没有成本,公司希望知道最佳的切割方案!假设我们知道一段长度为i的钢条的价格为pi(i = 1,2,3...),钢条长度均为整英寸,下面给出一个价格表:
长度i | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
价格pi | 1 | 5 | 8 | 9 | 10 | 17 | 17 | 20 | 24 | 30 |
给定一长度为n的钢条和一张价格表(i =1, 2,3...n),求切割钢条的方案,使的利润最大,可以不进行切割
2.动态规划
动态规划与分治法比较类似,都是通过求解子问题的解,最终来求出原问题的解。分治法只要是将原问题分解成为若干个独立的子问题,通过子问题来求解原问题的解。而动态规划恰恰相反,动态规划是用来求解子问题重叠的情况,即不同的子问题之间具有相同的公共子问题,在这种情况下分治法会花费大量的时间来计算已经计算过的那部分重叠子问题,效率大大降低,而动态规划权衡时间和空间的因素,将已经计算过的子问题存入一张表内,下次遇到相同的子问题时,直接查表就可以得到结果,大大加快了速度
动态规划通常用来求解最优化问题,一般来说一个问题的最优解有多个,动态规划可以求解出其中的一个最优解,而不是最优解
我们通常按照下面的四个步骤来设计一个动态规划问题:
1.刻画一个最优解的结构特征。
2.递归的定义最优解的值
3.计算最优解时,一般采用自下而上的方法来求解
4.利用计算出的信息构造出一个最优解
下面我们来研究怎么用动态规划计算出一个最优解
3.问题分析
长度为n的钢条一共有2^(n-1)种切割方法,因为每一个长度为1的节点处都可以选择切或者不切
那么我们假设长度为n的钢条切成了k条钢条的时候,利润最大:
n = i1+i2+i3+...+ik;
ri = pi1+pi2+pi3+...+pik;
那么最大利润为:
rn = max(pn , r1+rn-1 , r2+rn-2, ...,ri+rn-i , rn-1+r1);(pn为不进行切割的时候的利润,ri+rn-i为在节点i处进行切割,ri为长度为i的钢条最大利润, rn-i为长度n-i的钢条的最大利润)!
这里我么通过组合两个相关子问题的最优解,并在所有可能的切割点上选取出最大的的利润,构成原问题的最优解。我们称钢条切割问题满足最优子结构的性质:问题的最优解由相关子问题的最优解组成,子问题可以独立求解!
我们不妨将上面的问题简化,得到一个简化以后的最优解的结构:
rn = max(p1+rn-1 , p2+rn-2 ,...,pn+r0);
4.代码实现
自顶向下的代码实现:
#include <iostream> #include <string> #include <limits.h> using namespace std; #define KIND_SIZE 11 /** 每种长度的基本价格 */ int price[]={0,1,5,8,9,10,17,17,20,24,30}; int dealMaxProfit(int n , int maxProfit[]); /** * 得到最大的利润 * @param n 钢条长度 * @return 最大利润 */ int getMaxProfit(int n) { if( n < 0 || n > KIND_SIZE) return 0; int maxProfit[KIND_SIZE];//记录每个长度下的最大利润是多少 for (int i = 0; i < KIND_SIZE ; ++i) { maxProfit[i] = INT_MIN; } maxProfit[0] = 0; dealMaxProfit(n , maxProfit); return maxProfit[n]; } /** * 将每个长度对应的比n小的钢条最大利润都保存在maxProfit里面 * @param n 钢条长度 * @param maxProfit 保存最大利润的数组 * @return 返回长度为n的最大利润 */ int dealMaxProfit(int n , int maxProfit[]) { if( n == 0) return 0; /** 表示之前已经算过了 */ if(maxProfit[n]!= INT_MIN) return maxProfit[n]; /** 没有算过那么就算一遍 */ int max = INT_MIN; for (int i = 1; i < n+1; ++i) { int temp = price[i]+dealMaxProfit(n-i, maxProfit); if(max < temp) max = temp; } maxProfit[n] = max; return max; } int main(int argc, char const *argv[]) { while(1) { int steelBarLen; cout<<"Enter the steel Bar Length(0-10)>"; cin >> steelBarLen; cout<<"Max profit is : "<<getMaxProfit(steelBarLen)<<endl; } return 0; }
自底向上的代码实现:
#include <iostream> #include <limits.h> #include <string> using namespace std; #define KIND_SIZE 11 /** 这个是每一个长度下的单价 */ int price[]={0,1,5,8,9,10,17,17,20, 24 ,30}; int dealMaxProfit(int n , int maxProfit[]); /** * 得到最大的利润 * @param n 钢条的长度 * @return 最大的利润 */ int getMaxProfit(int n) { if(n < 0 || n > KIND_SIZE) return 0; int maxProfit[KIND_SIZE]; for (int i = 0; i < KIND_SIZE; ++i) { maxProfit[i] = INT_MIN; } maxProfit[0] = 0; dealMaxProfit(n , maxProfit); return maxProfit[n]; } /** * 处理得到<n长度的利润 * @param n 钢条的长度 * @param maxProfit 最大的利润 * @return 对应长度为n的最大的利润 */ int dealMaxProfit(int n , int maxProfit[]) { if(n == 0) return 0; for (int i = 1; i <= n; ++i) { int max = INT_MIN; for (int j = 1; j <= i; ++j) {// 每次都是将最优子结构求出来,再求上层的 int temp = price[j]+maxProfit[i-j]; if(max < temp) max = temp; } maxProfit[i] = max; } } int main(int argc, char const *argv[]) { while(1) { int steelBarLen; cout<<"Enter the steel Bar Length(0-10)>"; cin >> steelBarLen; cout<<"Max profit is : "<<getMaxProfit(steelBarLen)<<endl; } return 0; }
但是上面的代码只是求解了最终的最大的利润,并没有算出具体的解决方案
我们不妨观察一下最优解的结构:
rn = max(p1+rn-1 , p2+rn-2 ,...,pn+r0);
最优的解是由子问题的最优解构成,钢条要么在长度为i处切割,要么不切割,所有我们就用一个数组来保存对应长度n应该从位置i(不截断情况i=n)截断的数据
即devidePos[n]=i;
于是我们将上面的算法进行改进,使其可以求出截断的方案
自顶向下的代码实现:
/** * 这个是自顶向下的求法 */ #include <iostream> #include <string> #include <limits.h> #include <stdio.h> using namespace std; #define KIND_SIZE 11 /** 每种长度的基本价格 */ int price[]={0,1,5,8,9,10,17,17,20,24,30}; int dealMaxProfit(int n , int maxProfit[] , int devidePos[]); /** * 得到最大的利润 * @param n 钢条长度 * @return 最大利润 */ int getMaxProfit(int n , int devidePos[]) { if( n < 0 || n > KIND_SIZE) return 0; int maxProfit[KIND_SIZE];//记录每个长度下的最大利润是多少 for (int i = 0; i < KIND_SIZE ; ++i) { maxProfit[i] = INT_MIN; devidePos[i] = i; } maxProfit[0] = 0; dealMaxProfit(n , maxProfit , devidePos); return maxProfit[n]; } /** * 将每个长度对应的比n小的钢条最大利润都保存在maxProfit里面 * @param n 钢条长度 * @param maxProfit 保存最大利润的数组 * @return 返回长度为n的最大利润 */ int dealMaxProfit(int n , int maxProfit[] , int devidePos[]) { if( n == 0) return 0; /** 表示之前已经算过了 */ if(maxProfit[n]!= INT_MIN) return maxProfit[n]; /** 没有算过那么就算一遍 */ int max = INT_MIN; int pos = n;xia for (int i = 1; i < n+1; ++i) { int temp = price[i]+dealMaxProfit(n-i, maxProfit ,devidePos); if(max < temp) { max = temp; pos = i; } } maxProfit[n] = max; devidePos[n] = pos; return max; } void printCutSolution(int n , int devidePos[]) { if(n < 0 || n >= KIND_SIZE) return ; if( n == devidePos[n]) { printf("%s\n", "not devide" ); return; } printf("%d steel bar devide into %d and %d \n", n , n - devidePos[n] , devidePos[n] ); printCutSolution(n - devidePos[n] , devidePos); } int main(int argc, char const *argv[]) { while(1) { int devidePos[KIND_SIZE]; int steelBarLen; cout<<"Enter the steel Bar Length(0-10)>"; cin >> steelBarLen; cout<<"Max profit is : "<<getMaxProfit(steelBarLen , devidePos)<<endl; printCutSolution(steelBarLen , devidePos); } return 0; }
自底向上的代码实现:
#include <iostream> #include <limits.h> #include <stdio.h> #include <string> using namespace std; #define KIND_SIZE 11 /** 这个是每一个长度下的单价 */ int price[]={0,1,5,8,9,10,17,17,20, 24 ,30}; int dealMaxProfit(int n , int maxProfit[] , int devidePos[]); /** * 得到最大的利润 * @param n 钢条的长度 * @return 最大的利润 */ int getMaxProfit(int n , int devidePos[]) { if(n < 0 || n > KIND_SIZE) return 0; int maxProfit[KIND_SIZE]; for (int i = 0; i < KIND_SIZE; ++i) { maxProfit[i] = INT_MIN; devidePos[i] = i; } maxProfit[0] = 0; dealMaxProfit(n , maxProfit , devidePos); return maxProfit[n]; } /** * 处理得到<n长度的利润 * @param n 钢条的长度 * @param maxProfit 最大的利润 * @return 对应长度为n的最大的利润 */ int dealMaxProfit(int n , int maxProfit[] , int devidePos[]) { if(n == 0) return 0; for (int i = 1; i <= n; ++i) { int max = INT_MIN; int pos=i; for (int j = 1; j <= i; ++j) {// 每次都是将最优子结构求出来,再求上层的 int temp = price[j]+maxProfit[i-j]; if(max < temp) { max = temp; pos = j; } } devidePos[i] = pos; maxProfit[i] = max; } } void printCutSolution(int n , int devidePos[]) { if(n < 0 || n >= KIND_SIZE) return ; if( n == devidePos[n]) { printf("%s\n", "not devide" ); return; } printf("%d steel bar devide into %d and %d \n", n , n - devidePos[n] , devidePos[n] ); printCutSolution(n - devidePos[n] , devidePos); } int main(int argc, char const *argv[]) { while(1) { int devidePos[KIND_SIZE]; int steelBarLen; cout<<"Enter the steel Bar Length(0-10)>"; cin >> steelBarLen; cout<<"Max profit is : "<<getMaxProfit(steelBarLen , devidePos)<<endl; printCutSolution(steelBarLen , devidePos); } return 0; }
之前我么在描述问题的时候假设过切割钢条不消耗费用,假设现在改变条件,钢条每切割一次,代价为c,那么最佳的决绝方案是什么呢?其实这个问题和上面的问题比较类似,我们只用每次减去代价c就可以了,代码实现如下:
#include <iostream> #include <limits.h> using namespace std; #define KIND_SIZE 11 #define COST 2 /** 切割的花费 */ /** 每种长度的基本价格 */ int price[]={0,1,5,8,9,10,17,17,20,24,30}; int dealMaxProfit(int , int *); int getMaxProfit(int n) { if(n <=0 || n >= KIND_SIZE) return 0; int maxProfit[KIND_SIZE]; for (int i = 0; i <KIND_SIZE; ++i) { maxProfit[i] = INT_MIN; } dealMaxProfit(n , maxProfit); return maxProfit[n]; } int dealMaxProfit(int n , int maxProfit[]) { maxProfit[0] = 0; for (int i = 1; i < KIND_SIZE; ++i) { int max = INT_MIN; for (int j = 1; j < i ; ++j) { int maxTemp = price[j]+maxProfit[i-j]-COST; if(max < maxTemp) max = maxTemp; } max = max>price[i]?max:price[i]; maxProfit[i] = max; } return maxProfit[n]; } int main(int argc, char const *argv[]) { while(1) { int steelBarLen; cout<<"Enter the steel Bar Length(0-10)>"; cin >> steelBarLen; cout<<"Max profit is : "<<getMaxProfit(steelBarLen)<<endl; } return 0; }