贪心算法--数据结构与算法之美--CH37
文章目录
1. 引言
数据结构与算法之美的前述总结,已经基本将所有常用的数据结构和算法学习完了。
本节以及后边几个总结,将就4个算法思想进行学习:贪心算法,分治算法,回溯算法,动态规划。这几种算法并不是具体的算法,而是解决问题的一种思想,思想这个东西不是记忆就能够掌握的,需要不断实战和总结,共勉。
2. 什么贪心算法
定义贪心算法之前,先看一个案例:假设有一个可容纳 100kg的背包,现有 5 种豆子,每种的总量和总价值各不相同。为了让背包所装豆子的总价值最大,应该如何选择装哪些豆子?每种豆子又该装多少呢?
显然,同等空间,单价越高价值越大。因此肯定是按照单价从高到低一次装入:20kg 黑豆、30kg 绿豆、50kg 红豆。
以上思路本质上就是贪心算法的思想:在空间大小固定(限制值)的情况下,需要所装豆子价值(期望值)最大,则每一步选择同等限制值贡献下,期望值贡献高的那个。
3. 什么情况下可以用贪心算法
3.1 贪心算法有效
如上分析,对贪心算法进行一个总结,什么情况下适合用贪心算法。
- 题目中定义了限制值和期望值。
- 每次选择同等限制值贡献情况下,期望贡献最大的数据。(或者同等期望值贡献下,限制值贡献较小的。)
- 举例验证,一般情况下贪心算法正确性显而易见。
3.2 贪心算法失效
有些情况下,贪心算法就不work了,如图寻找s到t的最短路径。
贪心算法选择了S-A-E-T,实际最短路径S-B-D-T,本质上由于“前面的选择,影响了后面的选择”,因此贪心算法不work了。
这个问题用动态规划,能够得到正确结果,这也是动态规划和贪心算法的区别所在,动态规划就是解决这种前置操作影响后置操作的问题。
4. 贪心算法实战分析
4.1 分糖果
有 m 个糖果和 n 个孩子,要把糖果分给孩子,但是糖果少孩子多(m<n),所以糖果只能满足部分孩子。每个糖果的大小分别是 s1,s2,s3,……,sm,同时 n 个孩子对糖果的需求分别是 g1,g2,g3,……,gn。
只有糖果的大小大于等于需求的时候,孩子才得到满足,问题是,如何分配糖果,能尽可能满足最多数量的孩子?
解答:
- 限制条件是糖果数量m,期望是得到满足的孩子。
- 将孩子按需求从小到大排序,同等期望贡献下,限制值损失最小。则考虑用按需求从小到大分配,分配给能满足其需求的最小的糖果。
- 依次类推,直到糖果分完或无法再分。
4.2 钱币找零
有 1 元、2 元、5 元、10 元、20 元、50 元、100 元面额的纸币,张数分别是 c1、c2、c5、c10、c20、c50、c100。现在要用这些钱来支付 K 元,最少要用多少张纸币呢?
解答:
- 期望值是K元,限制值是纸币数量。
- 同等期望希望限制值最小,则先用大面值的,然后用小面值,最后1元补齐。
- 这是直觉下的最优解,大部分情况下也是最优的,但是不排除特殊情况。
4.3 区间覆盖
如下图,假设有 n 个区间,起始端点和结束端点分别是 [l1, r1],[l2, r2],[l3, r3],……,[ln, rn]。从 n 个区间中部分区间,满足两两不相交(端点相交的情况不算相交),最多能选出多少个区间呢?
解答:
- 期望值区间数量最多,限制值区间宽度。
- 这里区间宽度取n个区间中最左端点lmin,最右端点rmax,因此区间宽度为[lmin,rmax]。
- 每次选择左端点与前面不覆盖,右端点尽量小的区间,如下图。
4.4 霍夫曼编码
霍夫曼编码基本思想:按照字符频率,字符频率越高,编码越短。
如图,假设一段文本共6个字符,出现频率:
编码技巧如下:
- 把每个字符看作一个节点,并辅带频率放到优先级队列中。
- 从队列中取出频率最小的两个节点 A、B,然后新建一个节点 C,频率设置为两个节点频率之和,作为节点 A、B 的父节点。
- 再把 C 节点放入到优先级队列中。
- 重复1,2,3,直到队列中没有数据。
如下图: - 给每一条边加上一个权值,指向左子节点的边统统标记为 0,指向右子节点的边,统统标记为 1,从根节点到叶节点的路径就是叶节点对应字符的霍夫曼编码。
最终编码如下图所示:
5. 贪心算法的思考
我理解贪心算法的本质就是"在满足限制条件下,只考虑当前最优的步骤,而不顾全大局",这也是其可能出现局部最优解的根因。
以思考题为例:在一个非负整数 a 中,我们希望从中移除 k 个数字,让剩下的数字值最小,如何选择移除哪 k 个数字呢?
解答:
- 移除k个数字,则需要k个步骤,只考虑每步最优。
- 第一步:一定是移除1个数字使得当前剩余数字最小,因此要从高位到低位,选择相邻两个高位大于低位的移除。比如123654,选到6移除得12354。
- 第二步:在得到的12354,递归使用第一步思路。选到5,得1234。依次类推,直到k步。