作者:刘楚杰,时间:2022年11月8日
未经本人允许禁止转载
- 一个贪心的人,在做一个决策时,往往会选择对他最有利的方面,这种人更加重视眼前的利益,只希望达成眼前的“最优解”,不会考虑这在长期长是否为最优解,亦不会考虑这是否在未来会给自己带来损害。
- 贪心算法亦是如此,贪心算法仅仅只考虑在当前状态下的最优解,取当前状态下对自己最有利的情况,仅在当前(局部)最有利的情况我们称为局部最优解,在长期(全局)最有利的情况我们称为全局最优解。而局部最优解不一定会满足全局最优解,这个我们会在后面的动态规划中的背包问题举例说明。
- 贪心算法一定是错误的吗,局部最优解是否均不满足全局最优解?当然不是,有的时候,局部最优解满足全局最优解,我们在每一步的时候都只考虑当前状态下的“最有利的情况”,有时候正好就是整体“最有利的情况”。
- 但我们不能把一个题目轻而易举的就定性为贪心算法,一个参加ICPC比赛的朋友原先跟我说过:“直接提交贪心算法的代码就是最好检验贪心算法是否正确的方法,打不了罚时20分钟。”但在我们以后工作时,不会有这样的判题机来对我们的算法进行核验。因此,当我们遇到可能是贪心算法的题目时,如果可能,尽量要对其进行数学上的证明:局部最优解满足全局最优解
- 贪心算法,求局部最优解,最关键的无异于就是这个“最”字:
- 取当前最大的
- 取当前最小的
- 取区间最左边的
- 取区间最右边的
- 正是因为这个“最”,很多时候我们使用贪心算法都需要对数据进行一个排序处理,比如将数据从大到小排序,然后从大到小取;将数据从小到大排序,然后从小到大取
-
对于一组离散的数据,我们可以这么处理:
- 将这些数据从小到大排序,从小到大处理
- 将这些数据从大到小排序,从大到小处理
- 抽取其中两到三个元素,考虑其局部最优解的情况,然后推广到全局最优解(有时候的贪心并不是单纯的从大到小或者从小到大,可能是两三个数据组合处理(比如加减乘除)后的从大到小或者从小到大,因此我们需要抽取元素单独分析,然后推广到全局)
-
对于一系列的区间,我们可以这么处理:
- 对所有区间的左端点从小到大进行排序,或者对所有区间的右端点从小到大排序
- 对所有区间的左端点从大到小进行排序,或者对所有区间的右端点从大到小排序
- 筛除一个区间的子区间,或者是别人子区间的区间,然后再进行分析处理
- 取区间的左端点,或者取区间的右端点
-
如果我们找不到最优的贪心策略,我们可以先使用暴力搜索的方法寻找最优解,然后选用几个数据进行求解,将过程都打印下来,在其内寻找贪心策略
- 完全数学归纳法:通常用于选取两三个元素求局部最优,然后推广到全局最优的情况(以前i个满足为条件,证明前i+1个也满足)
- 按照贪心的情况进行排序,然后证明交换后结果不会更优
- 假设一系列数据已经按先后放好,然后任取两个数据,证明它们按要求从小到大或者从大到小会更优(交换或者不交换)
- 田忌赛马类证明法:选最靠近的与直接选左端或右端的效果相同
- 反证法
- 对于显而易见的经典情况,直接参考例题的方法即可
- 实在无法证明的题目,可以对应写一个贪心的代码,再写一个确保正确的暴力代码,进行互检对拍,也就是用不完全数学归纳法进行“证明”
给个物体,第个物体重量为,选择尽量多的物体,使得总重量不超过
思路:
想要装的越多,当然是一个物体越轻越好,在同样的情况下,装重的肯定没有装轻的划算,因此我们的贪心策略是先装轻的,将这些数据从小到大排序后处理即可
有个物体,第个物体的重量为,价值为,在总重量不超过的情况下让总价值尽量高。每一个物体可以只取走一部分,价值和重量按比例计算。
思路:
这道题问的是性价比,而不是单纯的价值或者重量,因此我们需要处理的量为,因为每一个物体可以仅取走一部分,因此我们保证背包可以装满,我们只需要选取当前性价比最高的装进去即可,选用结构体,对每个物体的性价比从大到小排序后处理即可
有个人,第个人重量为。每艘船的载重量均为,最多可承两个人。求用最少的船装载所有人的方案。
思路:
将数据从小到大排序,最重的与最轻的在一组,如果当前最重的和当前最轻的不能在一组,则让最重的单独一组,不再考虑这个最重的。
证明:
方法一:
取当前最大的一个,如果其与当前最小的一个无法乘坐同一艘船,那么最大的这个只能够自己乘一艘船,如果他能与别人共一艘船,我们很容易想到:让他与尽可能大的那个人共一艘船,使得他们的重量在不超过船的承重下尽可能的大,但其实这样做是没有意义的,并且会更加浪费时间,我们只需要让其跟重量最小的那个人共一艘船即可,我们可以证明:
- 现在从小到大排序好的体重:,如果能与乘坐同一艘船,那么、等等肯定也能够与乘坐同一艘船,因此,我们只需要让与乘坐同一艘船即可,也就是最大配最小(田忌赛马类证明法)
方法二:
假设最轻的人和最重的人配对的贪心策略不是最优的,最优方案中的是什么样的呢?
情况1:不和任何一个人乘同一艘船,那么可以把拉过来和他一起乘,总船数不会增加(且可能会减少),并且符合贪心策略
情况2:和另外一个人同乘一艘船,其中比轻。把和交换后,原来所在的船仍然不会超重(因为比轻),因此和同乘一艘船,所得到的的新解不会更差,符合贪心策略
给定个开区间,选择尽量多个区间,使得这些区间两两没有公共点。
思路:
对这些区间的右端点从小到大排序,然后按照右端点从小到大的顺序从左边往右边看:如果当前这个区间左端点比前一个选定区间(一定是选定好的区间,而不是前一个区间)的右端点大,则选定这个区间,答案加一,否则,不选定这个区间。
给定个闭区间,在数轴上选尽量少的点,使得每个区间内都至少有一个点(不同区间内点可以是同一个)。
思路:
对这些区间的右端点从小到大排序,然后按照右端点从小到大的顺序从左边往右边看:如果当前这个区间左端点比最新选定的点小,则代表这个区间内已经有点了,无需再处理;如果当前这个区间左端点比最新选定的点大,则代表这个区间内还没有选定点,我们选择这个区间的右端点作为新的点。
给个闭区间,选择尽量少的区间覆盖一条指定的线段区间。
思路:
首先将左端点小于的都将其左端点更改为,右端点大于的都将其右端点更改为,然后对这些区间进行一些处理:左端点相同的不同区间仅留下右端点最大的那个,然后对这些区间的左端点从小到大排序,考虑当前区间的条件是当前区间的左端点在上一个选定区间右端点的左边,这样我们可能会考虑很多个区间,在这些区间中选定右端点最大的那个
有个作业要在两台机器和组成的流水线上完成加工。每个作业都必须先花时间在上加工,然后花时间在上加工。
确定个作业的加工顺序,使得从作业1在机器上加工开始到作业在机器上加工完为止所用的总时间最短。
思路:
我们原先说,贪心算法的核心就体现在“最”字上,而这道题很明显,它的“最”体现为:浪费的时间最少,因为所有的作业都可以等上一个作业完成后立马在机器上开始加工,因此在上加工并不会浪费任何时间,时间的浪费就出现在了机器上
当 均 时,假设有两个作业与,满足:
如果先进行作业后进行作业,则总时间为,
如果先进行作业后进行作业,则总时间为
则::
- 若,则,原式=,因为,,因此:
- 若,则,原式=,因为,因此,原式=,即
综上:当均小于等于时,优先更小的在只有两个作业时最优
推广到多个作业:
- 假设多个作业已经按照一定顺序排好,现在我们选取相邻的两个与,
- 若在在上加工结束前已经停止加工(即:在上加工完后,可以立即在上加工),则这种情况会与之前只有两个作业的情况一致
- 若在在上加工结束时仍在加工,很明显,若,浪费的时间会更少
依次类推,可将所有相邻的作业都进行换序,最终的最优解为从小到大,因此:
当均小于等于时,优先更小的结果最优
假设有两个作业和,其中作业,作业:
如果先进行作业后进行作业,则总时间为,
如果先进行作业后进行作业,则总时间为
则::分类讨论可得
综上:优先比小的在两个作业时更优
这个结论很容易推广到多个作业:优先比小的更优
假设有两个作业和,均满足:
如果先进行作业后进行作业,则总时间为,
如果先进行作业后进行作业,则总时间为
则::分类讨论可得
综上:均大于时,优先大的效果更优(很容易由两个作业推广到多个作业)
因此,本题的贪心策略为:优先的,然后为的。在中,优先小的;在中,优先大的
有个任务,每个任务都需要1个时间单位执行,任务的截止时间表示要求任务在时间结束时必须完成,误时惩罚表示若任务未在时间结束之前完成,将导致的罚款。
确定所有任务的执行顺序,使得罚款最少。
思路:
先对从大到小排序,然后按照排好的顺序一次对任务进行安排。安排的规则为:使处理任务的时间既在之内,又尽量靠后;如果之内的时间都已经排满,就放弃处理此项任务。
证明:
假设按照上述算法得到的解不是最优的,那么必然存在某个任务应当安排到处理的过程中但却没有安排。假设我们要将该任务安排进去,由于在时间内都已经排满,就必然需要将一个已安排的任务与之替换,而。这样,替换显然会增加罚款的数额。因此,除上述安排方法以外的安排方法都不会使罚款的数额减少,可知用上述算法得到的结果是最优的。
恰逢H国国庆,国王邀请n位大臣来玩一个有奖游戏。首先,他让每个大臣在左、右手上面分别写下一个整数,国王自己也在左、右手上各写一个整数。然后,让这n位大臣排成一排,国王站在队伍的最前面。排好队后,所有的大臣都会获得国王奖赏的若干金币,每位大臣获得的金币数分别是:排在该大臣前面的所有人的左手 上的数的乘积除以他自己右手上的数,然后向下取整得到的结果。
国王不希望某一个大臣获得特别多的奖赏,所以他想请你帮他重新安排一下队伍的顺序,使得获得奖赏最多的大臣,所获奖赏尽可能的少。注意,国王的位置始终在队伍的最前面。
思路:
仅考虑两个大臣A与B时,假设大臣A的左手的值为A.left
,右手的值为B.left
;大臣B的左手的值为B.left
,右手的值为B.right
。国王左手的值为king.left
,国王右手的值为king.right
对于大臣A与大臣B,有两种可能的情况:
- 大臣A在大臣B的前面,这个时候:
- 大臣A的金币为
- 大臣B的金币为
- 大臣A在大臣B的后面,这个时候:
- 大臣B的金币为
- 大臣A的金币为
因此我们只需要比较四个数的大小:
、、、
全部除去,即我们需要比较:
很容易知道:
因此我们只需要比较和即可,比较和,也就是比较和,若,则我们将A放到B的后面能使得最大的最小。
推广到多个大臣时,假设大臣都已经按位置站好,现在我们任取两个大臣A和B,A在B的前面,如果我们交换A与B:
- 在A之前的大臣获得的金币数量均不会改变
- 在B之后的大臣获得的金币数量均不会改变
- 在A与B之间的大臣获得的金币数量会改变
如果我们不想考虑在A与B之间的大臣,我们可以选择相邻的大臣A与B,这样,交换A与B的顺序,就只会对A与B获得的金币数量造成影响。
对于相邻的A与B,我们根据前面的推断,很容易得知,当时,A放在B前面,能够使得A与B获得金币数量最多的的那个获得的金币数量最少
像这样,我们每次都取相邻的两个,按照将A放在B的前面,最终我们得到的所有大臣的情况应该满足:
因此,局部最优满足全局最优,这道题的贪心策略为:将大臣的左手的值乘上右手的值从小到大排序,大臣按照这个顺序站,能实现获得金币最多的那个大臣获得的金币数量最少。
如果是对二进制上每一位都有决定权,想要使得最终处理结果最大,那么我们的贪心决策是二进制同位上的1越多越好
齐王和田忌每人各有匹马,并且保证齐王的每一匹马和田忌的每一匹马的速度均不相同(没有速度相同的马),已知齐王会从最好的马开始出,将他的马由速度从大到小参与各场比赛(每一匹马只能参加一场比赛),现在田忌已知齐王所有马的速度和自己所有马的速度,请问田忌该如何决策才能使自己赢的次数最多
思路:
我们通常会这么想:如果不能赢齐王当前的这匹马,那么我们就选取最差的那匹马去和齐王比,如果能够赢齐王的这匹马,那么我们尽量去选择能赢齐王的所有马中速度最慢的那匹马去和齐王比,但其实这没有必要,反而会增加程序的时间复杂度。
当我们能够赢齐王当前的这匹马时,我们可以直接选用当前最大的那匹马去和齐王比,证明:
- 如果我们选择最接近的那匹马,那么这匹马一样能赢齐王接下来的所有马,因为齐王的马是从大到小出的,齐王接下来的所有马都会小于等于齐王现在的这匹马。因此,我们没有必要选择最接近的那匹马,直接选取当前最大的那匹马去和齐王比即可。
最终的贪心策略为:
- 如果我们能够赢齐王当前的这匹马,那么我们选择自己当前最大的那匹马去和齐王比
- 如果我们不能赢齐王当前的这匹马,那么我们选择自己当前最小的那匹马去和齐王比