动态规划是将复杂的问题分成数个简单的子问题,然后再去解决。它们的区别在于,分治法关注的子问题不相互“重叠”,而动态规划关注的子问题,多是相互“重叠”的。不同子问题具有公共的子子问题的求解也是递归进行,将其划分为更小的子子问题。
比如在快速排序中,我们将数据分成两部分,这两部分再分别快速排序的递归思想,也就是将整个问题的排序划分为子问题子数组的排序,但是这两个子数组的排序之间并没有相互联系,a子数组的排序不会因为b子数组的排序而得到任何“好处”或者“坏处”。
动态规划运用表格方法通过组合子问题的解来进行求解原问题,动态规划不同于分治法,分治法产生大量重复计算,而动态规划仅仅是每个子子问题求解一次,保存在一个表格当中,避免大量重复计算。
钢管切割问题:
问题核心:有一段长度为i的钢管,整段出售的价格为Pi,求适当的切割钢条方案使得获利最大
收益
Rmax=Pi1+Pi2+Pi3+Pi4+······Pik
我们讲长度为i的钢管生成切割成为k段,每段的最大利益为Pik
钢管的切割问题满足最优子结构:问题的最优解由相关子问题的最优解组合而成,这些问题时可以独立求解
我们进行一个更加一般化的描述:
进而
具体证明方法就是反正法:假设Pi1,Pi2,Pi3段切割方案使得Rmax最大,如果Pi2+Pi3不是最大,那么就可以找到另外一种切割方法,使得Rmax也不可能为最大,所以无论你最终结果切割多少段,你都可以先分为两端。这就是最优子结构的关键,大问题的最优解一定由小问题的最优解组合构成。
进而
直观考虑递归方案:
#include <bits/stdc++.h>
using namespace std;
int p[11]={
0,1, 5, 8, 9, 10, 17, 17, 20, 24, 30};//储存钢管长度售价信息,长度为i+1的钢管的价值
int CUT_ROD(int p[] ,int length);
int main()
{
cout<<CUT_ROD(p,10)<<endl;
return 0;
}
int CUT_ROD(int p[] ,int length ) //自顶向下递归实现
{
if(length==0) return 0;
int Rmax=0;
for(int i=1;i<=length;i++)
Rmax=max(Rmax,p[i]+CUT_ROD(p,length-i));//i=1 to length 每个问题都可以分为两个问题的最优解
return Rmax;
}
递归返回调用自身去计算每一个子问题,即使子问题被计算过了,也要大量计算
从父节点s到子节点的边表示从钢条左端切下长度为s-t的一段,然后继续继续递归求解剩余规模为t的子问题,一般来说递归调用树共有2n个结点,其中2n-1个叶结点,所以时间复杂度为T(2n)
动态规划的好处就是每个子问题求解一次,将其保存,付出额外的内存空间来节省计算时间,典型的时空权衡案例,时间上节省巨大将一个指数级别的时间转化为了一个多项式的时间。
如果子问题的数量是输入规模的多项式的函数,我们在多项式的时间内求解出每一个子问题,动态规划总运行时间是多阶。
单纯的递归可以转化为带备忘的自顶向下的递归方法:
int store[11]={
0};//申明一个数组来储存最优解
int MEMOIZED_CUT_ROD_AUX(int p[], int length)
{
if(store[length]!=0) return store[length];
if(length==0) return 0;
int Rmax=0;
for(int i=1;i<=length;i++)
Rmax=max(Rmax