贪心算法解决问题的步骤
- 当我们看到这类问题的时候,首先要联想到贪心算法:针对一组数据,我们定义了它的限制值和期望值,希望从中选出几个数据,在满足限制值的条件下,期望值最大。
- 我们尝试下这个问题是否可以用贪心算法解决: 每次选择当前情况下,在对限制值同等贡献量的情况下,对期望值贡献最大的数据。
- 举几个例子代入问题,看看产生的结果是否是最优: 大部分情况下,举个例子验证下就可以了。严格地证明贪心算法的正确性,是非常复杂的,需要涉及比较多的数学推理。而且,从实践的角度来说,大部分能用贪心算法解决的问题,贪心算法的正确性都是显而易见的,也不需要严格的数学的推导证明。
学习贪心算法
关键是多实践。在使用中找到感觉。
1. 分糖果问题
我们有 m 个糖果和 n 个孩子,我们现在要把糖果分给这些孩子吃,但是糖果少,孩子多 (m < n), 所以糖果只能分给一部分孩子。
每个糖果的大小不一样,分别是 s1, s2, s3, ……, sm。除此之外,每个孩子对糖果的需求也是不一样的,只有糖果大小大于或者等于孩子对糖果大小的需求的时候,孩子才能得到满足。假设这 n 个孩子对糖果大小的需求分别是 g1, g2, g3, ……, gn。
问题是,如何分配糖果,尽可能满足最多数量的孩子。
对问题分析,限制值是这堆糖果,贡献值是得到满足的孩子。尝试贪心算法,如果对一个孩子来说,一个小的糖果可以满足,就绝不用更大的糖果来满足他。
设计算法:对孩子按照需求从小到大排序,对糖果按照大小从小到大排序,遍历孩子,用最小的糖果满足当前的孩子,直到糖果用完或者孩子遍历完。
2. 钱币找零
假设我们有 1 元、2 元、 5 元、10 元、20 元、50 元、100 元这些面额的纸币,它们的张数分别是 c1、c2、c5、c10、c20、c50、c100。现在我们要用这些钱来支付 K 元,最少要用多少张纸币呢?
分析问题:限制值是纸币的张数,希望越少越少,贡献值是纸币的总面额,要达到 K 元。
设计贪心算法:每次从剩余纸币中选择单张对总面额贡献率最大且不会使得总面额超过 K 元的纸币。
另外要注意的是,若是找到最后 c1 的张数不够的话,就要使用动态规划算法了。
3. 区间覆盖
假设我们有 n 个区间,区间的起始端点和结束端点分别是 [l1, r1], [l2, r2], [l3, r3], ……, [ln, rn]。我们从这 n 区间中选出一部分区间,这部分区间满足两两不相交(端点相交的情况不算相交),最多能选出多少个区间呢?
假设这 n 个区间的最左端点是 lmin,最右端点是 rmax。这个问题就相当于,在总可用区间 rmax - lmin 中,从左到右,每次消耗最少的空间,增加一个覆盖区间。而这个消耗最少空间,计算公式是 左端点下标+区间长度。
4. 哈夫曼编码
哈夫曼编码,是一种压缩算法,压缩率通常在 20% ~ 90% 之间。它的算法思想是通过用不等长的编码来表示每个字符,使得最终文本占用的存储空间更少。
实现哈夫曼编码的过程是,对所有字符辅带着频率放在优先级队列中,我们从队列中取出频率最小的两个节点 A、B,新建一个节点 C 作为 AB 两个节点的父节点,频率设置为这两个节点的频率之和。最后把 C 节点放入优先级队列中。重复这个过程,直到队列中没有数据。最后得到一棵树,我们给这棵树每条边画上一个权值,指向左子树的边标记为 0 ,指向右子树的边标记为 1。这样下来,从根节点到叶节点的路径就是叶节点字符对应的哈夫曼编码。