动态规划之-Rod Cutting 问题(1)

动态规划之-Rod Cutting 问题(1)

在学习图论算法过程中,接触到Floyd算法时候,教程中特别提到了动态归还算法的基本概念,原本以为动态规划就是一个简单的算法,而后参考《算法导论》中的相关章节,发现动态规划是相当复杂的一类算法思想。本文属于学习过程中的心得,难免会有错误或有理解不正确地方,欢迎各位指出。

动态规划定义

动态规划(Dynamic Programming ,DP)问题是在20世纪50年代,由Richard Bellman提出的一种计算方法,在数学和计算机程序领域有这广泛的应用。

其思想和分治方法(Divide and Conquer)有点类似,通过对子问题的求解,然后把子问题汇总起来,从而得到最终解。分治方法,每次都会产生全新的子问题,当参数到达结束条件后,然后回退,汇总子问题结果,从而求得整个问题的解;动态规划方法,允许部分子问题重合,再求解过程中可以直接调用重合的问题,直接进行计算。

同时注意到动态规划和贪心算法也有相似之处,贪心算法在求解过程中,不需要穷尽所有的子问题,而是选择当前的某个最优解,沿着这个路径求解直至找到最优的解;动态规划执行的不漏网任何子问题的策略,扎扎实实穷尽当前的所有子问题,然后选择最优解。从某种意义上理解,贪心算法为动态规划算法的特例,但是在许多情况下,贪心算法仍然十分有效。

动态规划问题的关键要素和求解步骤

判断某个问题能否采用动态规划,其中有两个关键的要素,分别为 最优子结构(optimal substructure)和重叠子问题(overlapping sub-problem)。教材中给出了求解动态规划的一般化四个步骤,简称为CRCC

  • 寻找最优化问题的子结构(Characterize the substructure of optimal solution )

  • 对最优化解的值进行迭代定义(Recursively define the value of optimal solution)

  • 求解最优化的值(Compute the value of optimal solution)

  • 构建最优化的解的信息(Construct an optimal solution from the computed information)

本问题利用四个步骤对Rod-cutting 问题进行解析,同时也加入自己学习过程中的理解和体会。

问题描述:

某钢厂采购原材料钢棒进行加工,假定原钢棒的长度为n,需要进行不同长度的切割,切割完成后再进行出售,已知不同钢棒长度的价格,为了简化问题,假定每次切割费用不计。问了计算方便,我们给出不同长度钢棒(实体钢棒)的单独售价,表示不用切割的钢棒价格,用列表表示。

length(i)12345678910
price(i)1589101717202430

方法1:

a-1) 寻找最优化问题的子结构,首先我们对问题进行分析,确定其为最优化问题,要求给出每次加工后,钢棒的出售利润最大化,假定钢棒的长度为n,那么问题转换为如下, r(n)表示利润:

假定在第i(1<=i<=n)处进行切割,那么本次切割后的出售的利润为:

r(n)=r(i)+r(n-i)

对于n的普遍情况,可以求出最大值的方程为:
r ( n ) = m a x { p ( n ) , r ( 1 ) + r ( n − 1 ) , r ( 2 ) + r ( n − 2 ) . . . r ( n − 1 ) + r ( 1 ) } r(n)=max \left\{p(n), r(1)+r(n-1),r(2)+r(n-2)...r(n-1)+r(1)\right\} r(n)=max{p(n),r(1)+r(n1),r(2)+r(n2)...r(n1)+r(1)}
其中p(n)表示整个钢棒无需切割,整块出售的情况,后面r(i)+r(n-i)表示切割成两部分,第一部分的长度为i,第二部分的长度为(n-i), 并且这两部分均取最优值,最后从列表中求解所有值的最大值。我们发现,最优子结构包括两部分,而且这两部分形式类似, 他们分别为r(i)和r(n-i),我们利用上面的关系式可以求解出第二部的递归关系式。

b-1) 利用上面的关系式,我们对其进行浓缩,可以得到类似递归性质的关系式。
r ( n ) = m a x { p ( n ) , r ( i ) + r ( n − i ) , 1 ≤ i ≤ n } r(n)=max \left\{p(n), r(i)+r(n-i) , 1≤i≤n\right\} r(n)=max{p(n),r(i)+r(ni),1in}
c-1) 利用b-1关系式,我们采用最原始的递归方法,对其求解

/**
@param p, 价格数组
@param n, 钢棒的长度
*/
int cut_rod_2(int *p, int n)
{
    
    int i;
    int j;
    int m;
    if(n==0)
    {
        return 0;
    }
    int q=0;

    for(i=1;i<=n;i++) 
    {
        q=p[i]; //每次需要从不切割开始,q表示长度为i的钢棒不切割,所得到的最大利润
        
        //利用循环,对比当前最优化的利润r(i),与分切成r(j) 与 r(i-j)之和,取较大的值
        for (j = 1; j < i; j++) 
        {
            m=cut_rod_2(p,j)+cut_rod_2(p,i-j);
            q=max_revenue(q,m); //max_revenue is the compare function
        }
    }

    return q;
}

在比较关系式中,每次都设定长度为i的钢棒不进行切割,然后与i长度范围内的不同切割值进行比较,计算两边切割长度j和i-j之间的最佳值。

d-1) 由于在递归过程中,未记录切割位置的点,所以需要引入以为数组s,记录不同长度的钢棒的最优切割位置。

方法2:

a-2) 我们在寻求最优化子结构的过程中,发现可以优化子结构的数量,从两个子问题结构优化到一个子问题结构,
r ( n ) = m a x { p ( n ) , r ( 1 ) + r ( n − 1 ) , r ( 2 ) + r ( n − 2 ) , r ( i ) + r ( n − i ) , . . . r ( n − 1 ) + r ( 1 ) } r(n)=max \left\{p(n), r(1)+r(n-1),r(2)+r(n-2),r(i)+r(n-i),...r(n-1)+r(1)\right\} r(n)=max{p(n),r(1)+r(n1),r(2)+r(n2),r(i)+r(ni),...r(n1)+r(1)}
如果我们参考下图不难发现,如果我们把第一项r(i)更换为p(i), 那么我们会发现下列关系与上述关系式等效:

r ( n ) = m a x { p ( n ) , p ( 1 ) + r ( n − 1 ) , p ( 2 ) + r ( n − 2 ) , p ( i ) + r ( n − i ) , . . . p ( n − 1 ) + r ( 1 ) } r(n)=max \left\{p(n), p(1)+r(n-1),p(2)+r(n-2),p(i)+r(n-i),...p(n-1)+r(1)\right\} r(n)=max{p(n),p(1)+r(n1),p(2)+r(n2),p(i)+r(ni),...p(n1)+r(1)}
接下来我们就会头脑中就出现疑问,为什么这两个关系等效呢? 我们可以这样理解,对于第一项r(i),其实它已经被前面的关系式涵盖了大多数情况除了, p(i)+r(n-i)这个情况。我们以r(2)+r(n-2)为例进行相关说明,其实r(2)+r(n-2)可以由于r(1)+r(1)+r(n-2)组成或r(2)+r(0)+r(n-2)构成;由于之前的子式子为r(1)+r(n-1),它当中必然包含r(1)+[r(1)+r(n-2)]的情况;对于r(2)+r(0)+r(n-2)情形实际上就等价为p(2)+r(n-2),因为之前的关系式中为包含先采用整体为p(2)的情况。

b-2) 递归定义最优问题的值

如果我们进一步定义r(0)=0,那么上述关系式就进一步简化为:
r ( n ) = m a x { p ( i ) + r ( n − i ) , 1 ≤ i ≤ n } r(n)=max \left\{p(i)+r(n-i), 1≤i≤n\right\} r(n)=max{p(i)+r(ni),1in}
那么我们就可以在关系中只要关注r(n-i)即可,问题将大大简化。

c-2) 利用上述关系式,我们进行最原始的求值计算,求解递归问题的值

int cut_rod_1(int *p, int n)
{
    int q;
    int i;

    if(n==0) //the revenue will be zero if the total length is zero
    {
        return 0; 
    }
    q=INT_MIN;

    for (i = 1; i <= n; i++) 
    {
        q=max_revenue(q,p[i]+cut_rod_1(p,n-i)); //max_revenue is the compare function
    }

    return q;
}

d-2) 由于在递归过程中,未记录切割位置的点,所以需要引入以为数组s,记录不同长度的钢棒的最优切割位置。

叙说到此,其实我们还未谈及动态规划的核心问题,如何减少运算时间,优化问题的求解。前面我们提到过,动态规划的特点之一就是重复子问题,对于重复的子问题,我们实际上有两种策略:

  • 每次都对所有子问题进行计算

  • 利用数组或哈希表对子问题计算结果进行记录,每次遇到相同的子问题,查表即可

让我们对方法2中的子问题求解进行分析,来说明这两种情况,如果对所有子问题都进行求解,不管子问题是否有重复。如果假定钢棒的长度为n=4,我们就会得到下面的递归树:

在这里插入图片描述

我们需要调用16次递归函数,然后完成计算,对于上述递归树,如果某个父节点的长度定义为s, 其子节点的长度定义为t, 那么s-t的长度就是第一次切割的长度位置。假定父节点为④,子节点为②,那么父节点的长度为4 inch(s),子节点的长度2 inch(t), 第一次切割位置为2 inch(s-t),其它的情况可以以此类推。

我们对上述子问题进行仔细观察,寻求相同的子问题求解过程,我们就会发现相同颜色圆圈中的子问题互相重复。

在这里插入图片描述

如果我们能在求解左侧子树过程中(④→③子树),对子树的值进行记忆,那么求解过程就可以大大简化,我们用有色圆圈代替,便得到下面结果。

在这里插入图片描述

对于圈内无标记的情况,其过程为直接查询结果,其时间复杂度为O(1),这样就把计算次数从16次降低为5次(不考虑直接查询的情况),那么就像相当把指数级的复杂度降低为多项式级的复杂度,对于n=4的情况,复杂度由
T ( 2 n ) 变化为 T ( n ) T(2^n) 变化为 T(n) T(2n)变化为T(n)
如何实现重复子问题的查询,以及Top-down和bottom-up的形式问题,我们留在下一篇幅中进行介绍。

本文总结:

通过算法导论的学习,逐渐了解动态规划的基本概念,以及动态规划的基本要素和求解过程。作为动态规划的学习的第一步,深感DP的深奥和难以理解。希望通过文字记录,深化学习效果。

以上,谢谢

参考文献:

  1. 《Introduction to algorithm, 4th. edition》

  2. https://en.wikipedia.org/wiki/Dynamic_programming

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值