贪心算法

1 - 引言

贪心法是一种解决问题的策略。如果策略正确,那么贪心法往往是易于描述、易于实现的。

2 - 背包相关问题

最优装载问题。给出n个物体,第i个物体重量为 w i w_i wi。选择尽量多的物体,使得总重量不
超过C。

【分析】
由于只关心物体的数量,所以装重的没有装轻的划算。只需把所有物体按重量从小到大
排序,依次选择每个物体,直到装不下为止。这是一种典型的贪心算法,它只顾眼前,但却
能得到最优解。

部分背包问题。有n个物体,第i个物体的重量为 w i w_i wi,价值为 v i v_i vi。在总重量不超过C的情
况下让总价值尽量高。每一个物体都可以只取走一部分,价值和重量按比例计算。

【分析】
本题在上一题的基础上增加了价值,所以不能简单地像上题那样先拿轻的(轻的可能价
值也小),也不能先拿价值大的(可能它特别重),而应该综合考虑两个因素。一种直观的
贪心策略是:优先拿“价值除以重量的值”最大的,直到重量和正好为C。
注意:由于每个物体可以只拿一部分,因此一定可以让总重量恰好为C(或者全部拿走
重量也不足C),而且除了最后一个以外,所有的物体要么不拿,要么拿走全部。
乘船问题。有n个人,第i个人重量为 w i w_i wi。每艘船的最大载重量均为C,且最多只能乘两
个人。用最少的船装载所有人。

【分析】
考虑最轻的人i,他应该和谁一起坐呢?如果每个人都无法和他一起坐船,则唯一的方
案就是每人坐一艘船(想一想,为什么)。否则,他应该选择能和他一起坐船的人中最重的
一个j。这样的方法是贪心的,因此它只是让“眼前”的浪费最少。幸运的是,这个贪心策略
也是对的,可以用反证法说明。

假设这样做不是最好的,那么最好方案中i是什么样的呢?

  • 情况1:i不和任何一个人坐同一艘船,那么可以把j拉过来和他一起坐,总船数不会增
    加(而且可能会减少)。
  • 情况2:i和另外一人k同船。由贪心策略,j是“可以和i一起坐船的人”中最重的,因
    此k比j轻。把j和k交换后k所在的船仍然不会超重(因为k比j轻),而i和j所在的船也不会超
    重(由贪心法过程),因此所得到的新解不会更差。

由此可见,贪心法不会丢失最优解。最后说一下程序实现。在刚才的分析中,比j更重
的人只能每人坐一艘船。这样,只需用两个下标i和j分别表示当前考虑的最轻的人和最重的
人,每次先将j往左移动,直到i和j可以共坐一艘船,然后将i加1,j减1,并重复上述操作。
不难看出,程序的时间复杂度仅为O(n),是最优算法(别忘了,读入数据也需要O(n)时间,
因此无法比这个更好了)。

3 - 区间相关问题

选择不相交区间。数轴上有n个开区间 ( a i , b i ) (a_i, b_i) (ai,bi)。选择尽量多个区间,使得这些区间两两
没有公共点。
【分析】
首先明确一个问题:假设有两个区间x,y,区间x完全包含y。那么,选x是不划算的,因
为x和y最多只能选一个,选x还不如选y,这样不仅区间数目不会减少,而且给其他区间留出
了更多的位置。接下来,按照 b i b_i bi从小到大的顺序给区间排序。贪心策略是:一定要选第一个
区间。为什么?

现在区间已经排序成 b 1 ≤ b 2 ≤ b 3 … b1≤b2≤b3… b1b2b3了,考虑a1和a2的大小关系。

  • 情况1:a1>a2,如图(a)所示,区间2包含区间1。前面已经讨论过,这种情况下一
    定不会选择区间2。不仅区间2如此,以后所有区间中只要有一个i满足 a 1 > a i a1>a_i a1>ai,i都不要选。在
    今后的讨论中,将不考虑这些区间。
  • 情况2:排除了情况1,一定有 a 1 ≤ a 2 ≤ a 3 ≤ … a1≤a2≤a3≤… a1a2a3,如图(b)所示。如果区间2和区间1完全
    不相交,那么没有影响(因此一定要选区间1),否则区间1和区间2最多只能选一个。如果
    不选区间2,黑色部分其实是没有任何影响的(它不会挡住任何一个区间),区间1的有效部
    分其实变成了灰色部分,它被区间2所包含!由刚才的结论,区间2是不能选的。依此类推,
    不能因为选任何区间而放弃区间1,因此选择区间1是明智的。
    (a)a1>a2 (b)a1<a2<a3
    在这里插入图片描述
    选择了区间1以后,需要把所有和区间1相交的区间排除在外,需要记录上一个被选择的
    区间编号。这样,在排序后只需要扫描一次即可完成贪心过程,得到正确结果。
    区间选点问题。数轴上有n个闭区间[ai, bi]。取尽量少的点,使得每个区间内都至少有
    一个点(不同区间内含的点可以是同一个)。

【分析】
如果区间i内已经有一个点被取到,则称此区间已经被满足。受上一题的启发,下面先
讨论区间包含的情况。由于小区间被满足时大区间一定也被满足,所以在区间包含的情况
下,大区间不需要考虑。
把所有区间按b从小到大排序(b相同时a从大到小排序),则如果出现区间包含的情
况,小区间一定排在前面。第一个区间应该取哪一个点呢?此处的贪心策略是:取最后一个
点,如图所示。

在这里插入图片描述
根据刚才的讨论,所有需要考虑的区间的a也是递增的,可以把它画成图8-8的形式。如
果第一个区间不取最后一个,而是取中间的,如灰色点,那么把它移动到最后一个点后,被
满足的区间增加了,而且原先被满足的区间现在一定被满足。不难看出,这样的贪心策略是
正确的。
区间覆盖问题。数轴上有n个闭区间[ai, bi],选择尽量少的区间覆盖一条指定线段[s,
t]。
【分析】
本题的突破口仍然是区间包含和排序扫描,不过先要进行一次预处理。每个区间在[s, t]
外的部分都应该预先被切掉,因为它们的存在是毫无意义的。预处理后,在相互包含的情况
下,小区间显然不应该考虑。
把各区间按照a从小到大排序。如果区间1的起点不是s,无解(因为其他区间的起点更
大,不可能覆盖到s点),否则选择起点在s的最长区间。选择此区间[ai, bi] 后,新的起点应
该设置为bi,并且忽略所有区间在bi之前的部分,就像预处理一样。虽然贪心策略比上题复
杂,但是仍然只需要一次扫描,如图8-9所示。s为当前有效起点(此前部分已被覆盖),则
应该选择区间2。
在这里插入图片描述

4 - Huffman编码

假设某文件中只有6种字符:a, b, c, d, e, f,可以用3个二进制位来表示
在这里插入图片描述
这样,一共需要(45+13+12+16+9+5)3=300千比特(即二进制的位)。第二种方法是采
用变长编码
在这里插入图片描述
总长度为1
45+313+312+316+49+4*5=224千比特,比定长码短。读者可能会说:还
可以更短
在这里插入图片描述

总长度只有1*(45+13)+2*(12+16+9+5)=142千比特,不是更短吗?可惜,这样的编码方案
是有问题的。如果收到了001,那么究竟是aab、cb,还是ad?换句话说,这样的编码有歧
义,因为其中一个字符的编码是另一个码的前缀(prefix)。表8-3所示的码没有这样的情
况,任何一个编码都不是另一个的前缀。这里把满足这样性质的编码称为前缀码(Prefix
Code)。下面正式叙述编码问题。

最优编码问题。给出n个字符的频率ci,给每个字符赋予一个01编码串,使得任意一个
字符的编码不是另一个字符编码的前缀,而且编码后总长度(每个字符的频率与编码长度乘
积的总和)尽量小。

【分析】

在解决这个问题之前,首先来看一个结论:任何一个前缀编码都可以表示成每个非叶结
点恰好有两个子结点的二叉树。如图所示,
每个非叶结点与左子结点的边上写1,与右子结
点的边上写0。
每个叶子对应一个字符,编码为从根到该叶
子的路径上的01序列。在图8-10中,N的编码为
001,而E的编码为11。为了证明在一般情况下,
都可以用这样的二叉树来表示最优前缀码,需要
证明两个结论。

在这里插入图片描述

  • 结论1:n个叶子的二叉树一定对应一个前
    缀码。如果编码a为编码b的前缀,则a所对应的结点一定为b所对应结点的祖先。而两个叶子
    不会有祖先后代的关系。

  • 结论2:最优前缀码一定可以写成二叉树。逐个字符构造即可。每拿到一个编码,都可
    以构造出从根到叶子的一条路径,沿着已有结点走,创建不存在的结点。这样得到的二叉树
    不可能有单子结点,因为如果存在,只要用这个子结点代替父结点,得到的仍然是前缀码,
    且总长度更短。

接下来的问题就变为:如何构造一棵最优的编码树。

Huffman算法:把每个字符看作一个单结点子树放在一个树集合中,每棵子树的权值等
于相应字符的频率。每次取权值最小的两棵子树合并成一棵新树,并重新放到集合中。新树
的权值等于两棵子树权值之和。

下面分两步证明算法的正确性。

  • 结论1:设x和y是频率最小的两个字符,则存在前缀码使得x和y具有相同码长,且仅有
    最后一位编码不同。换句话说,第一步贪心法选择保留最优解。
    证明:假设深度最大的结点为a,则a一定有一个兄弟b。不妨设f(x)≤f(y),f(a)≤f(b),则f(x)≤f(a),f(y)≤f(b)。如果x不是a,则交换x和a;如果y不是b,则交换y和b。这样得到的新
    编码树不会比原来的差。
  • 结论2:设T是加权字符集C的最优编码树,x和y是树T中两个叶子,且互为兄弟结
    点,z是它们的父结点。若把z看成具有频率f(z)=f(x)+f(y)的字符,则树是字符集的一棵最优
    编码树。换句话说,原问题的最优解包含子问题的最优解。

结论1通常称为贪心选择性质,结论2通常称为最优子结构性质。根据这两个结
论,Huffman算法正确。在程序实现上,可以先按照频率把所有字符排序成表P,然后创建一
个新结点队列Q,在每次合并两个结点后把新结点放到队列Q中。由于后合并的频率和一定
比先合并的频率和大,因此Q内的元素是有序的。类似有序表的合并过程,每次只需要检
查P和Q的首元素即可找到频率最小的元素,时间复杂度为O(n)。算上排序,总时间复杂度
为O(nlogn)。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值