在讨论贪心算法时,我们先了解贪心算法与动态规划之间的区别与联系,后面我们将发现可以用0、1背包问题和部分背包问题来比较贪心算法和动态规划的关系。
我们知道,对于一个最优解问题,贪心算法不一定能够产生一个最优解。因为,如果想要采用贪心算法得到最优解需要满足两个条件:贪心选择性质、最优子结构。
- 贪心选择性质:一个全局最优解可以通过局部最优解来得到。that is to say, 当考虑如何做选择时,我们只考虑对当前问题最佳的选择而不考虑子问题的结果。
- 最优子结构:全局最优解包含子问题的最优解。
- 贪心算法和动态规划的区别:在动态规划中,每一步都要做出选择,但是这些选择都依赖于子问题的解。因此,解动态规划问题一般是自底向上,由子问题到问题。在贪心算法中,我们总是做出当前的最好选择,而这些选择都不是依赖子问题,选择后再解决选择之后出现的子问题。因此,解贪心算法问题一般是自顶向下,一个一个地做出贪心选择。
从上面的描述可知,动态规划中解决问题,需要先解决子问题,因此可能用到递归(当然可以将递归化为非递归),计算复杂度要比贪心算法高。从上面的理论解释可能比较抽象,我们可以用具体的实例来说明问题。我们用经典的0、1背包问题和部分背包问题来看看动态规划和贪心算法的区别。两个问题的描述如下:
用贪心选择算法来解决部分背包问题正如上面所说的思想,十分简单,在这里就不给予程序实现。我们主要讨论0、1背包问题的最优解。
下面我们例子来说明为什么贪心选择算法不能解0、1背包问题:假设背包容量为$116$,序号为$1-3$的物品的重量和价格分别为:$w[3]={100, 14, 10}, p[3]={20, 18, 15}$。其平均价值为${0.2, 18/14, 1.5}$,按照贪心算法的话,选择物品顺序为:$3, 2, 1$,最终的选择为$3, 2$,其价值为$33$,但是实际的最优方案为:选择$1, 2$,其价值为$38$。
从这个例子中可以看出,在0、1背包问题中,我们在选择是否要加入一个物品时,必须将把该物品加进去的子问题和不加进去的子问题进行比较(选择依赖子问题),这种方式的问题导致了许多重叠子问题,这是动态规划的一个特点。
下面我们用动态规划来解0、1背包问题:假设$f[i][j]$表示剩余物品为$i, i+1, \cdots , n$,容量为$j$时的最大价值,例如以上面的例子来说明,$f[0][10]$,表示物品为$1, 2, 3$,容量为$116$时的最大价值,$f[1][116]$,表示物品为$2, 3$,容量为$116$时的最大价值。我们目的是求$f[0][116]$,利用动态规划的思想,假设我们选择$1$号物品,则最大价值为$p[0]+f[1][116-100]$,如果不选$1$号,则最大价值为$f[1][116]$,因此选不选$1$号则需要比较两者的最大值。比较两者的最大值需要求$f[1][116]$和$f[1][116-100]$,这是重叠子问题。最终的表达式为:$f[i][j]=f[i+1][j]>f[i+1][j-w[i]]+p[i]$。这个表达式可以递归求解,当然也可以迭代求解。
具体程序实现如下:
1 |
|