最近开始学习王争老师的《数据结构与算法之美》,通过总结再加上自己的思考的形式记录这门课程,文章主要作为学习历程的记录。
首先来看一个例子,假设我们有一个可以容纳100kg物品的背包
为了使背包中所装物体的总价值最大,如何选择在背包中装哪些豆子?这个问题的解决是将单价从高到低排列,它的本质借助的是贪心算法。
总结一下贪心算法解决问题:
第一步,当我们看到这类问题时,首先要联想到贪心算法。针对一组数据,我们定义了限制值和期望值,希望从中选出几个数据,在满足限制值的情况下,期望值最大。
第二步,我们尝试看下这个问题是否可以用贪心算法解决。每次选择当前情况下,在对限制值同等贡献量的情况下,对期望值贡献最大的数据。
第三步,我们举几个例子看下贪心算法产生的结果是否是最优的。大部分情况下,举几个例子验证一下就可以了。
实际上,用贪心算法解决问题的思路,并不总能给出最优解。举个例子,在一个有权图中,从顶点S开始,找一条到顶点T的最短路径(路径中边的权值和最小)。贪心算法的解决思路是,每次都选择一条跟当前顶点相连的权最小的边,直到找到顶点T。
按照这个思路,我们求出的最短路径是S-A-E-T,路径长度是1+4+4=9.
但这种贪心的选择方式,最终求得路径并不是最短路径,因为路径S-B-D-T才是最短路径,因为这条路径的长度是2+2+2=6.在这个问题上,贪心算法不工作的主要原因是前面的选择会影响后面的选择。即便第一步选择最优的走法,但有可能因为这一步选择,导致后面选择都很糟糕。
我们有m个糖果和n个孩子,m<n。要将糖果分给孩子吃,每个糖果的大小不等,这m个糖果的大小分别是S1、S2、S3、…、Sm。除此之外,每个孩子对糖果大小的需求不一样,只有糖果的大小大于等于孩子的对糖果大小的需求时,孩子才得到满足假设这n个孩子对糖果大小需求为g1、g2、g3、…、gn,如何分配糖果才能满足最多数量的孩子?
这个问题可以抽象成从n个孩子中,抽取一部分孩子分配糖果,让满足的孩子的个数最大。这个问题的限制值就是糖果个数m。
采用贪心算法,我们可以从需求小的孩子开始分配糖果。因为满足一个需求大的孩子跟满足一个需求小的孩子,对期望值的贡献是一样的。我们每次从剩下的孩子中,找出对糖果大小需求最小的,然后发给他剩下的糖果中能满足他的最小的糖果,这样得到的分配方案,也就是满足的孩子个数最多的方案。
以力扣455题为例,假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。对每个孩子 i ,都有一个胃口值 gi ,这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干 j ,都有一个尺寸 sj 。如果 sj >= gi ,我们可以将这个饼干 j 分配给孩子 i ,这个孩子会得到满足。你的目标是尽可能满足越多数量的孩子,并输出这个最大数值。(你可以假设胃口值为正。一个小朋友最多只能拥有一块饼干。)
class Solution(object):
def findContentChildren(self, g, s):
num = 0
g = sorted(g)
s = sorted(s)
for ele in s:
if num=g[num]:
num+=1
return num
假设我们有1元、2元、5元、10元、20元、50元、100元这些面额的纸币,它们的张数分别是C1、C2、C5、C10、C20、C50、C100.现在要用这些钱来支付K元,最少要用多少张纸币?
在贡献相同期望值(纸币数目)的情况下,我们希望多贡献点金额。这样可以让纸币数更少。
假设我们有 n 个区间,区间的起始端点和结束端点分别是 [l1, r1],[l2, r2],[l3, r3],……,[ln, rn]。我们从这 n 个区间中选出一部分区间,这部分区间满足两两不相交(端点相交的情况不算相交),最多能选出多少个区间呢?
这个问题的解决思路:假设这 n 个区间中最左端点是 lmin,最右端点是 rmax。这个问题就相当于,我们选择几个不相交的区间,从左到右将 [lmin, rmax] 覆盖上。我们按照起始端点从小到大的顺序对这 n 个区间排序。我们每次选择的时候,左端点跟前面的已经覆盖的区间不重合的,右端点又尽量小的,这样可以让剩下的未覆盖区间尽可能的大,就可以放置更多的区间。
假设有1个包含1000个字符的文件,每个字符占1个byte(1个byte=8bit),存储这1000个字符就一共需要8000bits,需要一种更节省空间的存储方式。
假设通过统计这1000个字符只包含6个不同的字符,分别为a、b、c、d、e、f。若用3个二进制位(bit)就可以表示8个不同字符,此时用3000个bits即可。
这时候霍夫曼编码出现了。霍夫曼编码可以更加节省空间。它不仅会考察文本中有多少个字符,还会考察每个字符出现的频率。根据频率的不同,选择不同长度的编码。
这时候,1000个字符只需要2100bits。
处理过程:我们把每个字符看作一个节点,并且辅带着把频率放到优先级队列中。我们从队列中取出频率最小的两个节点 A、B,然后新建一个节点 C,把频率设置为两个节点的频率之和,并把这个新节点 C 作为节点 A、B 的父节点。最后再把 C 节点放入到优先级队列中。重复这个过程,直到队列中没有数据。
我们给每一条边加上画一个权值,指向左子节点的边我们统统标记为 0,指向右子节点的边,我们统统标记为 1,那从根节点到叶节点的路径就是叶节点对应字符的霍夫曼编码。
节点的路径就是叶节点对应字符的霍夫曼编码。
参考资料:王争《数据结构与算法之美》