《Thinking In Algorithm》14.由背包问题了解动态规划和贪心

上篇博客中我们详细的讲解了动态规划问题,但是呢,其实对动态规划的理解还不够深入,今天我就找了一个非常经典的背包问题来进一步的学习动态规划,顺带也把与动态规划类似的贪心算法学了下。

本文讲解的顺序会是,先解决问题,再总结其算法的原理。

先看第一个问题。

1. 0-1背包问题

给定n种物品和一个背包。物品i的重量是Wi,其价值为Vi,背包的容量为C。应如何选择装入背包的物品,使得装入背包中物品的总价值最大?

分析:什么叫0-1背包问题,就是指选择物品的时候,要么选,要么不选,不能选择商品的一部分,后面我们会提到部分背包问题。

首先拿到这个问题,你第一会想到什么方法来解决呢?动态规划啊,没错。那解决动态规划问题的关键是什么?还记得上篇博客中我们讲的解决该类问题的步骤么,前两个步骤是什么呢?

第一:找状态;第二:得出动态方程。

那怎么找状态呢?一般遇到n这种东西,我们会给n取个很小的值来分析,这里我们取n=3.即由三个宝石,体积为5,4,3. 价值为20,10,12.背包体积为10.首先我们用背包装下前两个宝石,恩,能装下。但装第三个宝石的时候就不够,此时就要分析第三个宝石要不要装。那我们就分两种情况来讨论。第一:不装,此时背包装有价值30的宝石。第二:装,装入进去的话那么此时背包只剩下10-3=7的体积来装前两个宝石了,此时问题就转为n<=2时,体积为7的背包能装入的最大价值了。

不知道你们发现了没,上面取的元素数量虽然只有3个,但就已经有点问题包含的意思在里面了。上面我们问题的方式,就是我们要找的状态。

状态d(i,j)表示前i个 宝石装到剩余体积为j的背包中能达到的最大值

好,状态出来了,状态方程呢?首先要知道状态方程就是状态怎么转移的方程。那么上面的状态时怎么转移的呢,看我们上面的分析,当分析n=3时,即前3个宝石,是怎样转到计算前两个珠宝的问题的。好,先别晕,看下面。

前三个宝石: d(3,10)=max{d(2,10) , d(2,7)+12},这个式子就是我们前面分析第三个宝石装与不装的方程了。说到这里是不是有点状态方程的眉头了呢。好下面就写出状态方程来分析下。

d(i, j)=0 ;  (i==0)                         //当i=0时即没有宝石,那么不就是背包装的价值为0么。

d(i, j)=max{ d(i-1, j), d(i-1,j-V[i-1]) + W[i-1] }  (i>0) 

这样的话我们就利用动态规划的方法解决了0-1背包问题。但是他能用贪心算法来解决吗?答案是否定的。如下图:


因为虽然他具有最优结构,但必须还要满足贪心算法的性质:即贪心算法的每一步都是最优的,不需要考虑到子问题。就像上面的0-1背包问题,你选了第一个宝石时,你不能能确定第二个宝石就是最优解,即使你把他们的价值排成有序也是不行的,因为有可能装不下。那怎样的问题是贪心算法可以解决的呢?看下面


2. 部分背包问题

 [问题]给定n种物品和1个背包,背包允许的最大重量为Capacity。物品i的重量为weight[i],价值为value[i]。与0-1背包问题(每种物品只有装入背包或不装入背包两种选择)不同的是,在选择物品i装入背包时,可以只装入物品i的一部分。 问应当怎样选择物品装入背包,使背包中的物品的总价值最大。

分析:对与这道题我们就可以利用贪心算法来解决,即每次都能确保局部最优解是全局最有解决。

首先我们按照value/weight的大小顺序来排列这些物品,然后先装值最大的直到全部装完,再装第二大的。。这样直到背包不能再装下物品。


其实背包问题有很多,有人总结的很好:各种背包问题


3. 贪心算法

经过上面的背包问题,我们对贪心算法有了一定的了解,下面我们就来列出贪心算法的一些特性。

贪心算法一般有五部分:

  1. A candidate set, from which a solution is created
  2. A selection function, which chooses the best candidate to be added to the solution
  3. A feasibility function, that is used to determine if a candidate can be used to contribute to a solution
  4. An objective function, which assigns a value to a solution, or a partial solution, and
  5. A solution function, which will indicate when we have discovered a complete solution
贪心算法虽然能大大降低时间复杂度,但是他并不是适合所有问题,如上面的0-1背包问题,因为他很容易造成过早的选择,因而没法得到最优解。那怎样的问题适合贪心算法呢?他必须满足两种特性

贪心选择特性:局部的最优选择就是全局的最优选择,即贪心算法的每一步选择都是确保是全局最优的。注意与动态规划的区别,动态规划没有这一个特性,因为动态规划只保证了局部的最优解,并不能保证当前做的决定就是全局最优解。

最优子结构如果一个问题的最优解包含其子问题的最优解,我们就称此问题具有最优子结构性质。这一特性动态规划也有。注意这个特性与上面的贪心选择特性的区别。最优子结构值的是全局的最优能够推出局部的最优,而上面的特性是局部的最优即在全局里面也是最优的。


4. Huffman编码

Huffman编码广泛的应用于数据压缩。我们把数据看成是一个字符序列。Huffman的贪心算法根据每个字符出现的频率,构造出字符的最优二进制表示。

假定我们希望解压一个10万字符的数据文件,如图给出文件中所出现的字符和他们出现的频率。也就是说,文件中只出现了6个不同字符,其中字符a出现了45000次。


首先来看下定长编码,每一个字符都用三位来表示,所以总共需要300 000个二进制位来编码文件。是否有更好的编码方案呢?

我们来看下变长编码,高频率的字符我们用1位来表示,低频的则用多位。这样算下来,则(45 · 1 + 13 · 3 + 12 · 3 + 16 · 3 + 9 · 4 + 5 · 4) · 1,000 = 224,000 bits

与定长编码相比节约了25%的空间。其实这是此文件的最优编码,那我们是怎样做到的呢,这就涉及到贪心算法了。

要使用贪心算法那么就必须最从贪心算法的两个特性 1.贪心选择特性 2. 最优子结构。 这里我们就不证明了,算法导论上有证明。

首先给出伪代码

HUFFMAN(C)
1  n ← |C|
2  Q ← C
3  for i 1 to n - 1
4       do allocate a new node z
5          left[z] ← x ← EXTRACT-MIN (Q)
6          right[z] ← y ← EXTRACT-MIN (Q)
7          f [z] ← f [x] + f [y]
8          INSERT(Q, z)
9  return EXTRACT-MIN(Q)   ▹Return the root of the tree.

C表示字符序列,Q是最小优先队列。EXTRACT-MIN(Q)表示从最小优先队列中取出最小元素。f表示元素出现的频率。

具体流程可以看下图


其实Huffman编码不难理解,不过最好还是要自己去实现一遍,下面这道题,是今年腾讯春季招聘的笔试题。

[题目]某段文本中各个字母出现的频率分别是{a:4,b:3,o:12,h:7,i:10},使用哈夫曼编码,则哪种是可能的编码:(A)

A  a(000)  b(001)  h(01)  i(10)  o(11)
B  a(0000)  b(0001)  h(001)  o(01)  i(1)
C  a(000)  b(001)  h(01)  i(10)  o(00)

D  a(0000)  b(0001)  h(001)  o(000)  i(1)


好吧,先讲到这里,其实感觉动态规划和贪心算法还是有点难度的,最好到网上找大量的题做一下,结合下理论仔细推敲下。如:各种背包问题

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值