算法导论小结(8)-动态规划与贪心算法

来源:互联网 发布:淘宝返利红包链接 编辑:程序博客网 时间:2024/05/15 23:47

By:             潘云登

Date:          2009-7-23

Email:         intrepyd@gmail.com

Homepage: http://blog.csdn.net/intrepyd

Copyright: 该文章版权由潘云登所有。可在非商业目的下任意传播和复制。

对于商业目的下对本文的任何行为需经作者同意。


写在前面

1.          本文内容对应《算法导论》(第2版)》第15章。

2.          主要介绍了动态规划算法的基本概念、适用问题,以及求解步骤。最后,简单地比较分析了贪心算法的基本思想。

3.          希望本文对您有所帮助,也欢迎您给我提意见和建议。


动态规划

基本概念

和分治法一样,动态规划(dynamic programming)是通过组合子问题的解而解决整个问题的。分治法是指将问题划分成一些独立地子问题,递归地求解各子问题,然后合并子问题的解而得到原问题的解。与此不同,动态规划适用于子问题不是独立的情况,也就是各子问题包含公共的子子问题。在这种情况下,若用分治法则会做许多不必要的工作,即重复地求解公共的子问题。动态规划算法对每个子子问题只求解一次,将其结果保存在一张表中,从而避免每次遇到各个子问题时重新计算答案。


动态规划适用问题

动态规划通常应用于最优化问题,即要做出一组选择以达到一个最优解。适合动态规划方法的最优化问题必须具备两个要素:最优子结构和重叠子问题。

²         最优子结构

用动态规划求解优化问题的第一步是描述最优解的结构。如果问题的一个最优解中包含了子问题的最优解,则该问题具有最优子结构。通常,可以利用自顶向下的思想来寻找最优子结构,即缩小原问题的规模,判断子问题的最优解是否是组成原问题最优解的一部分。

         可以用无权最短路径和无权最长简单路径的例子,体会最优子结构的含义。假设p是从u到v的无权最短路径,那么p必定包含一个中间顶点w(可能是u或v)。如果p1、p2分别是u到w和w到v的无权最短路径,可以肯定,p将由p1和p2构成。因此,无权最短路径问题具有最优子结构。相反,假设p是u到v的无权最长简单路径,p1和p2分别是u到w和w到v的无权最长简单路径。因为p1路径中可以包含顶点v,而p2必定包含顶点v,所以当p1和p2合并时将形成环路,从而不能构成原问题的最优解p。用另一种方式看,在求解一个子问题时,对资源的使用(如这里的顶点)使得它们无法被另一个子问题所用。因此,无权最长简单路径问题不具有最优子结构,不能利用动态规划求解。

²         重叠子问题

适用于动态规划求解的最优化问题必须具有的第二个要素是子问题的空间要很小,也就是用来求解原问题的递归算法课反复地解同样的子问题,而不是总在产生新的子问题。对两个子问题来说,如果它们确实是相同的子问题,只是作为不同问题的子问题出现的话,则它们是重叠的。仍然以无权最短路径为例,两个相邻顶点间的无权最短路径,可以是包含这两个顶点的任意三个顶点的无权最短路径问题的重叠子问题。


动态规划求解步骤

动态规划算法的设计可以分为如下四个步骤:

1)   描述最优解的结构。通过对最优解结构的分析,判断如何对原问题进行最优子结构划分。

2)   利用子问题的最优解来递归定义一个最优解的值,这时递归求解的重要依据。

3)   按自底向上的方式计算最优解的值。下层子问题的最优解通常被记录在表格中,供上层求解时查询。

4)   由计算出的结果构造一个最优解。实际应用中,为了描述如何得到这个最优解,通常在自底向上的求解过程中,把每一个子问题中所作的选择保存在一个表格中。


最长公共子序列

以最长公共子序列为例,体会动态规划的求解过程。给定一个序列X=<x1, x2, …, xm>,另一个序列Z=<z1, z2, …, zk>是X的一个子序列,如果存在X的一个严格递增下标序列<i1, i2, …, ik>,使得对所有的j=1, 2, …, k,有xij=zj。在最长公共子序列(LCS,longest common subsequence)问题中, 给定了两个序列X=<x1, x2, …, xm>和Y=<y1, y2, …, yn>,希望找出X和Y的最大长度公共子序列Z=<z1, z2, …, zk>。

1)     描述最优解的结构。尝试缩小原问题的规模,寻找最优子结构。这里,如果xm= yn,那么zk =xm= yn且Zk-1是Xm-1和Yn-1的LCS;如果xm≠yn,那么zk≠xm蕴含Z是Xm-1和Y的一个LCS;如果xm≠yn,那么zk≠yn蕴含Z是Xm和Yn-1的一个LCS。

2)     利用上面的最优子结构,定义最优解的递归式。如果i=0,或j=0,那么c[i, j]=0;如果i, j>0且xi=yi,那么c[i, j]=c[i-1, j-1]+1;如果i, j>0且xi≠yi,那么c[i, j]=max(c[i, j-1], c[i-1, j])。这里,c[i, j]为序列Xi和Yi的一个LCS的长度。

3)     自底向上地计算LCS的长度,同时,记录LCS的元素。

4)     根据上一步的结果,直接得到LCS及其长度。

/*

 * x_array,x_length: X序列及其长度

 * y_array,y_length: Y序列及其长度

 * b_array,b_length: 记录LCS的元素,b_length=min(x_length,y_length)

 * 返回LCS长度

 */

int dynamic_lcs(int *x_array, int x_length,

                   int *y_array, int y_length,

                   int *b_array, int b_length)

{

    int i, j, k, lcs_length;

    int **c_array=NULL;

   

    c_array = malloc(sizeof(int *)*(x_length+1));

    for(i=0; i<x_length+1; i++)

                  c_array[i] = malloc(sizeof(int)*(y_length+1));

   

    for(i=0; i<x_length+1; i++)

                  c_array[i][0] = 0;

    for(j=0; j<y_length+1; j++)

                  c_array[0][j] = 0;

   

    k = 0;

    for(i=0; i<x_length; i++)

    {

                  for(j=0; j<y_length; j++)

                  {

                        if(x_array[i] == y_array[j])

                        {

                                      c_array[i+1][j+1] = c_array[i][j]+1;

                                      b_array[k++] = x_array[i];

                        }

                        else if(c_array[i+1][j] >= c_array[i][j+1])

                                      c_array[i+1][j+1] = c_array[i+1][j];

                        else

                                      c_array[i+1][j+1] = c_array[i][j+1];

                  }

    }

    lcs_length = c_array[x_length][y_length];

 

    for(i=0; i<x_length+1; i++)

                  free(c_array[i]);

    free(c_array);

 

    return lcs_length;  

}


贪心算法基本思想

贪心算法是通过做一系列的选择来给出某一问题的最优解。对算法中的每一个决策点,做一个当时看起来是最佳的选择。这一点是贪心算法不同于动态规划之处。在动态规划中,每一步都要做出选择,但是这些选择依赖于子问题的解。因此,解动态规划问题一般是自底向上,从小子问题处理至大子问题。贪心算法所做的当前选择可能要依赖于已经做出的所有选择,但不依赖于有待于做出的选择或子问题的解。因此,贪心算法通常是自顶向下地做出贪心选择,不断地将给定的问题实例归约为更小的问题。贪心算法划分子问题的结果,通常是仅存在一个非空的子问题。

原创粉丝点击