暂先不看问题自己,先来了解一下什么叫动态规划。从英文的dynamic programming来看彷佛并无“规划”的意思在里边。可是,这里的programming并不是指的是编程,而是指的一种表格法,这种表格法旨在一步步详细分解问题,使之细化并最终得到问题的解。因此咱们称之为“规划”。ios
动态规划和分治法相似,都是将大问题分解成小的子问题。但分治法自己的小问题每每是独立的,而动态规划的小的子问题依赖于大问题。算法
动态规划方法一般用来求解最优化问题(optimization problem)。这类问题一般能够有不少个解,例如钢条切割能够有不少中切割方法一样达到最大收益。编程
一般按以下4个步骤设计一个动态规划算法:
1. 刻画一个最优解的结构特征。
2. 递归定义最优解的值
3. 计算最优解的值,一般采用自底向上的方法。
4. 利用计算出的信息构造一个最优解。
函数
长度为n英寸的钢条共有2^n-1种不一样的方法(这里认为对称切割属于不一样的方法)。工具
这里以n = 9 为例,则总共有256种切割方案。优化
分别查看几种方案:设计
第一种:code
收益: 1 + 20 = 21递归
第二种:内存
收益: 10 + 10 = 20 比第一种少1
第三种:
收益: 1 * 9 = 9 显然是收益最少的
综合来看,收益最大的应该是 17 + 8 = 25,即分红6和3
算法实现:
1. 自顶向下递归实现
2. 带备忘的自顶向下法
3. 自底向上法
几种方法的比较:
切钢条只是一个引子,切的过程就是对应动态规划的不一样的规模下子问题的求解过程。用不用递归并非动态规划的本质。递归只是一种方法或者工具,而不是一种思想。自底向上的方法就没有用到函数递归。
朴素的递归算法之因此效率很低,是由于它反复求解相同的子问题。比方长度为33的钢条能够有2^32 = 4294967296种切割方法。用朴素的递归方法,须要求解这么大的一个规模,且不说频繁调用函数所产生的花销,要计算10亿次以上的加法和比较,这自己就很消耗时间。
基于此,动态规划方法仔细地安排求解顺序,对每个子问题都只求解一次,并将值保存起来。若是以后再有求此子问题即可以查询其值而不是从新再求一遍。带备忘的动态规划法须要额外的内存开销,可是节省的时间倒是可观的:可能将一个指数时间的解转化为多项式时间的解。
C++代码实现:
#include<iostream> #include<climits> #include<ctime> #include<cstdlib> int price[] = {1,5,8,9,10,17,17,20,24,30,32,33,33,39,41,44,45,45,45,48,50,55,60,70,78,79,79,88,90,91,92,92,92}; int max(int a,int b) { return a>b?a:b; } int cut_rod(int p[],int n) { if(n == 0) return 0; int q = INT_MIN; for(int i = 1; i <= n; i++){ q = max(q,p[i-1] + cut_rod(p,n-i)); } return q; } int cut_rod_memoized(int p[],int n,int r[]) { if(r[n-1] >= 0) return r[n-1]; int q; if(n == 0){ q = 0; } else{ q = INT_MIN; } for(int i=1;i <= n; i++) q = max(q,p[i-1]+cut_rod_memoized(p,n-i,r)); r[n-1] = q; return q; } int cut_rod_memoized_core(int p[],int n) { int r[n]; for(int i=0;i < n;i++){ r[i] = INT_MIN; } return cut_rod_memoized(p,n,r); } int bottom_up_cut_rod(int p[],int n) { int r[n]; for(int j = 1;j <= n; j++){ int q = INT_MIN; for(int i = 1; i <= j; i++){ if(j-1 -i >= 0){ q = max(q,p[i-1] + r[j-1 - i]); } else{ q = max(q,p[i-1]); } } r[j-1] = q; } return r[n-1]; } int main(int argc,char* argv[]) { if(argc != 2) return -1; time_t start_time = time(NULL); int condition = atoi(argv[1]); switch(condition){ case 0:std::cout<<cut_rod(price,sizeof(price)/sizeof(int))<<std::endl; break; case 1:std::cout<<cut_rod_memoized_core(price,sizeof(price)/sizeof(int))<<std::endl; break; case 2:std::cout<<bottom_up_cut_rod(price,sizeof(price)/sizeof(int))<<std::endl; break; default:break; } time_t end_time = time(NULL); std::cout<<"use:"<<end_time-start_time<<"seconds"<<std::endl; return 0; }
单纯的递归实现须要大概100秒的时间才能算出来,而另两种只须要不到1秒时间。这就能够看出动态规划的威力了。