动态规划(Dynamic Programming)与贪心算法(Greedy Algorithms)

动态规划

动态规划是用来解决优化问题的,即要作出一组选择以达到最优解。动态规划有两个标志:一是可以划分最优子问题,二是子问题有重叠性(可以自底向上存储这些子问题的解,把算法效率从指数时间降到多项式时间)。

例如,动态规划可以运用在矩阵相乘上。在矩阵连乘的时候,在中间增加括号是不影响最后结果的。也就是说 A * B * C = A * ( B * C )。假设A、B、C的维数为10 X 100, 100 X 5 和5 X 50,那么两种方法的运算次数为7500次和75000次,相差10倍!因此我们可以使用动态规划来确定一个最优次序以达到减小运算量的目的。而为了确定次序而消耗的时间对比做矩阵乘法来说往往是很小的。
现在我们把矩阵连乘问题分解。假设m[i,j]是计算矩阵A[i..j]所需的标量乘法运算次数的最小值,那么计算全部矩阵的最小代价为m[i,n]。再假设每个矩阵Ai是P(i-1) X Pi的,那么有:
m[i,j] = min{ m[i,k] + m[k+1,j] + P(i-1) * Pk * Pj }
m[i,j] = 0 when i = j
但是我们不知道k的值,只好把k从i到j-1进行遍历,然后取最小的m[i,j]。这样,我们就得到了子问题的最优解。但是,这种算法是指数时间的,它的效率与蛮力破解差不多。为了提高速度,我们建立两个表格m和s。其中m记录每一个m[i,j]的值。s则记录计算m[i,j]时取得最有代价处k的值。为了更加直观的分析,我们常常在纸上画出这两张表格。
在计算m表格的时候使用自底向上的推进方式。比如,要计算m[2,5]需要使用
m[2,2], m[3,5]
m[2,3], m[4,5]
m[2,4], m[5,5]
这三组值。由于使用之前已经计算好的值,所以不必重复计算了,效率可以提升到O(n^2)。
我们在建立m表格的同时也建立了s表格。通过s表格,我们就可以得到如何分解矩阵连乘的详细信息。

动态规划的另一个应用是在搜索领域。给定两个序列X = < x1,x2,…,xm >和Y = < y1,y2,…,yn >,如何找到其最大长度的公共子序列?(比如ABC和ACD的公共子序列是AC)这个问题叫最长公共子序列(Longest Common Subsequence, LCS)问题。如果使用蛮力破解法,逐一用X的子序列和Y对比,则因为X有2^m个子序列,结果必然是指数时间的。
定义前缀 Xi = < x1,x2,…,xi >。如矩阵连乘一样,定义c[i,j]为序列Xi和Yi的一个LCS的长度。同时建立表b[1..m, 1..n]表示与c相对应的最优解的构造。
考察Xi中的xi和Yj中的yj,若xi = yj,则把问题化为寻找c[i-1,j-1]。若xi != yj,则计算c[i,j-1]和c[i-1,j],因为最小子序列肯定是这两者之一。
c[i,j] = 0, when i = 0 or j = 0
c[i,j] = c[i-1,j-1] + 1, when xi = yi
c[i,j] = max( c[i,j-1], c[i-1,j] ), when xi != yi
自底向上计算c表中的每一个值,同时记录相对应的b值。把b表绘制成表格反向回溯就可以得到LCS值。易知,要求所有的c[i,j]需要对i和j进行遍历,时间复杂度是O(m*n)。

另一个类似的是最长公共子串的问题,给出伪代码如下:

function LCSubstr(S[1..m], T[1..n]) L := array(0..m, 0..n) z := 0 ret := {} for i := 1..m for j := 1..n if S[i] = T[j] if i = 1 or j = 1 L[i,j] := 1 else L[i,j] := L[i-1,j-1] + 1 if L[i,j] > z z := L[i,j] ret := {} if L[i,j] = z ret := ret ∪ {S[i-z+1..i]} return ret

贪心算法

贪心算法也是一个优化问题。它的思想是以局部最优来做每一个选择。贪心法比动态规划更快,但是不容易判断是否有效。因为只要问题存在最优子结构,动态规划就是可以用的,但是贪心算法就不一定了。贪心算法的关键性质是,一个全局最优解可以通过局部最优选择来达到。所以贪心策略通常是自顶向下的,不断将给定的问题归约为更小的问题。

背包问题(Knapsack Problems)可以说明贪心算法与动态规划的一些细微差别。假如一个贼在商店中发现n件物品,设第i件价值vi,重wi(vi,wi都是整数)。贼想拿走尽可能值钱的东西,但是他的背包只能装下重为W的东西。他应该带走哪几样?
第一种情况:商店里的物品是不可分的,贼要么把它拿走,要么把他留下。这种情况常被称为0-1问题。
第二种情况:商品可分,窃贼可以选择性地带走商品的一部分。
这两种情况都可以进行子问题的划分。很明显,在第二种情况下使用贪心算法是可以的,先对每件物品计算vi/wi,然后先拿比值最大的物品,拿完之后再拿比值第二大的。这样取满之后拿到的物品就是价值最大化的。因为可以按照vi/wi来排序,所以算法可以以O(n*logn)的时间运行。
但是对情况一,贪心算法是不适用的。举例来说,背包可容纳50磅重的东西。物品1重10磅,价值60元(每磅6元);物品2重20磅,值100元(每磅5元);物品3重30磅,值120元(每磅4元)。按照贪心算法,依次取1和2,得到价值总160元的物品。这时背包还剩20磅可用,却装不下30磅的物品3了,只能作罢。160元显然不是最大的价值。因为如果我们取物品2和3时,能得到总重量50磅,总价值220元的物品。因此,贪心算法给出了错误的优化结果。我们可以用动态规划来解决这种0-1问题。

 

参考文献:
Thomas H. Cormen, Charles E. Leiserson, Introduction to Algorithms, 2ed
http://en.wikipedia.org/wiki/Longest_common_substring

 

    原文作者:贪心算法
    原文地址: https://blog.csdn.net/wangxiaojun911/article/details/4693458
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞