算法导论之动态规划

   一直对动态规划懵懵懂懂,今天决定以写博客的方式加深自己对动态规划的理解。

    算法导论是这样解释动态规划的:动态规划与分治法相似,都是通过组合子问题的解来求解原问题答案,将问题划分为互不相交的子问题,递归的求解子问题,最后合并子问题的答案,得到原问题的答案,书面上有点文嗖嗖的,一句话就是大事化小,小事化无的思想。

    动态规划方法通常用来求解最优化问题,这类问题有很多可行解,每个解都有一个值,我们希望寻找最优值,我们称这样的解为问题的一个最优解,而不是最优解。

     动态规划通常有4个步骤,如下:

1.刻画一个最优解的结构特征。

2.递归的定义最优解的值。

3.计算最优解的值,通常采用自底向上的方法。

4.利用计算出的信息构造一个最优解。

   不多说了,我们直接用经典的钢条切割问题来展现动态规划的思想。

    问题是这样的,Serling公司购买了长钢条,将其切割为短钢条出售,切割工序本身没有成本支出。公司管理层希望知道最佳的切割方案。假设我们已经知道长度为i英寸的钢条价格,如下图所示

长度i 1 2 3 4 5 6 7 8 9 10
价格Pi 1 5 8 9 10 17 17 20 24 30


钢条切割问题是这样的给定一段长度为n的钢条和一个价格表pi(i=1,2,3......n),求切割钢条方案,使得收益最大。

考虑n=4的情况,从图表中明显可以看出当分割为2+2是收益是最大的为5+5=10,其他切割方案都是不是最好的。

注意到,为了求解规模为n的原问题,我们先求解形式完全一样,但规模更小的子问题。即当完成首次切割后,我们将两段钢条看成两个独立的钢条切割问题的实例。我们通过组合两个有关子问题的最优解,并在所有的可能的两段切割方案中选取组合收益最大者,构成原问题的最优解,则我们称钢条切割满足最优子结构性质。

我们可以直接得到递归求解方法:我么将钢条的左边切割为i,右边为n-i,然后对右边的n-i进行递归求解,这样不难得出自顶向下的递归实现:

int CUT(const int p[],int n)
{
    int i;
    if(n==0)
        return 0;
    int q=-10000;
     for(i=1;i<=n;i++)
         q=max(q,p[i]+CUT(p,n-i));
    return q;
}

其中p为数组,存放的是对应上图所示的是价格与长度之间的关系。当我执行这段代码的时候,执行n=10的时候,感觉不到速度的存在,当我执行n=20的时候,,在当我执行n=30的时候,时间无法忍受了,为,想象一下,当我执行n=100会出现什么后果,估计一个小时也出不来,我们仔细分析代码,不难得出该算法的执行时间为n的指数函数,原因是调用子问题的次数太多了,因此我们需要改进这种方法。

动态规划的思想就是付出额外的内存空间来节省计算时间,是典型的时空权衡的例子,但是在时间上的节省却是巨大的,可以将指数级的运行时间转换为多项式的运行时间,算法导论中有两种等价的实现方法。

1.带备忘的自顶向下法。此方法仍然按自然的递归形式编写,但过程会保留子问题的解,当需要一个解的时候,首先查看备忘录,如果已经解决,就直接取出来,如果没有,就计算,我们称这个过程是带备忘的,因为它记住了前面计算的结果。

2.自底向上法,这个方法有点抽象,我现在还不能完全领悟该方法,但是我查看大量的资料,真正的动态规划就是自底向上的计算方法。思想是这样的,从小问题逐步到大问题,当算到当前一步的时候,所有的前提子问题已经全部求解出来了,我们直接拿来使用就可以了。

首先看一下带备忘的动态规划算法:

int Memoized_CUT_ROD(const int p[],int n)
{
    int r[10000]={-1};
    return Memoized_CUT_ROD_AUX(p,n,r);
}
int Memoized_CUT_ROD_AUX(const int p[],int n,int r[])
{
    int i;
    int q=0;
    if(r[n]>0)
        return r[n];
    if(n==0)
        q=0;
    else
        {
            q=-1;
            for(i=1;i<=n;i++)
                q=max(q,p[i]+Memoized_CUT_ROD_AUX(p,n-i,r));
        }
    r[n]=q;
    return q;
}

当我执行n=30的时候,执行n=60的时候,,可见效率是尤其的高。而自底向上的动感规划算法更为简单:

int Bottom_CUT(const int p[],int n)
{
    int r[100]={-1},i,j,q;
    r[0]=0;
    for(j=1;j<=n;j++)
    {    
        q=-1;
        for(i=1;i<=j;i++)
            q=max(q,p[i]+r[j-i]);
        r[j]=q;        
    }
    return r[n];
}

效率与带备忘的算法一样,但是自底向上的算法有点难以理解通,如果有读者能够将这种思想清晰的表达出给我,在下感激不尽,好了,今天的动态规划算法告一段落,接下来写有关最长公共子序列(LCS)的问题,希望自己能够完全掌握动态规划的思想,并将其运用到编程当中去。

你可能感兴趣的:(算法导论之动态规划)