『算法』『数据结构』|『算法』『数据结构』 浅谈贪心算法,理解程序员必懂必会的计算机常见算法——贪心算法
基本认识
贪心算法(又称贪婪算法)是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,它所做出的是在某种意义上的局部最优解。
基本思想与原理
贪心选择是指所求问题的整体最优解可以通过一系列局部最优的选择,即贪心选择来达到。这是贪心算法可行的第一个基本要素,也是贪心算法与动态规划算法的主要区别。贪心选择是采用从顶向下、以迭代的方法做出相继选择,每做一次贪心选择就将所求问题简化为一个规模更小的子问题。对于一个具体问题,要确定它是否具有贪心选择的性质,我们必须证明每一步所作的贪心选择最终能得到问题的最优解。通常可以首先证明问题的一个整体最优解,是从贪心选择开始的,而且作了贪心选择后,原问题简化为一个规模更小的类似子问题。然后,用数学归纳法证明,通过每一步贪心选择,最终可得到问题的一个整体最优解。
贪心算法的基本思路是从问题的某一个初始解出发一步一步地进行,每一步都要确保能获得局部最优解。每一步只考虑一个数据,他的选取应该满足局部优化的条件。若下一个数据和部分最优解连在一起不再是可行解时,就不把该数据添加到部分解中,直到把所有数据枚举完,或者不能再添加算法停止。
适用的问题
贪心策略适用的前提是:局部最优策略能导致产生全局最优解。
实际上,贪心算法适用的情况很少。一般对一个问题分析是否适用于贪心算法,可以先选择该问题下的几个实际数据进行分析,就可以做出判断。
大多数可以使用贪心算法的问题都有以下特点:
(1)原问题复杂度过高;
(2)求全局最优解的数学模型难以建立;
(3)求全局最优解的计算量过大;
(4)没有太大必要一定要求出全局最优解,“比较优”就可以。
严格来说,贪婪算法可解决的问题通常大部分都有如下的特性:
(1)随着算法的进行,将积累起其它两个集合:一个包含已经被考虑过并被选出的候选对象,另一个包含已经被考虑过但被丢弃的候选对象。
(2)有一个函数来检查一个候选对象的集合是否提供了问题的解答。该函数不考虑此时的解决方法是否最优。
(3)还有一个函数检查是否一个候选对象的集合是可行的,也即是否可能往该集合上添加更多的候选对象以获得一个解。和上一个函数一样,此时不考虑解决方法的最优性。
(4)选择函数可以指出哪一个剩余的候选对象最有希望构成问题的解。
(5)最后,目标函数给出解的值。
(6)为了解决问题,需要寻找一个构成解的候选对象集合,它可以优化目标函数,贪婪算法一步一步的进行。起初,算法选出的候选对象的集合为空。接下来的每一步中,根据选择函数,算法从剩余候选对象中选出最有希望构成解的对象。如果集合中加上该对象后不可行,那么该对象就被丢弃并不再考虑;否则就加到集合里。每一次都扩充集合,并检查该集合是否构成解。如果贪婪算法正确工作,那么找到的第一个解通常是最优的。
求解的步骤与模板
贪婪算法并没有固定的算法解决框架,算法的关键是贪婪策略的选择,根据不同的问题选择不同的策略。
必须注意的是策略的选择必须具备无后效性,即某个状态的选择不会影响到之前的状态,只与当前状态有关,所以对采用的贪婪的策略一定要仔细分析其是否满足无后效性。
引例部分
活动安排问题:
设有n个活动的集合E={1,2,…,n},其中每个活动都要求使用同一资源,如演讲会场等,而在同一时间内只有一个活动能使用这一资源。每个活动i都有一个要求使用该资源的起始时间si和一个结束时间fi,且si
文章图片
在这里插入图片描述
解题思路:
把会场要安排的所有活动作为一个集合W,初始开始时间标准为preStart,初始结束时间标准为preEnd.每次放入W的活动要在满足,开始时间s[i]>preStart的前提下f[i]最小。然后把f[i]赋值给preStart. 依次加入,直到加不进去为止,从而把问题解决。
虽然贪心算法并不是针对任何一个问题都存在最优解,但是针对会场安排问题可以得到最优解。我们可以从贪心算法得到的结果集仅进行倒推。首先去掉结果集中的第一个活动A,那么在剩下的活动中,结束时间最早的活动B的结束时间一定不早于A,那么A活动在最优解中一定合理。再用同样的方式判断活动B,依次类推,则结果集就是最优解。
实战部分
整数转罗马数字问题:
文章图片
在这里插入图片描述
文章图片
在这里插入图片描述
解题思路:
这道题要将4,9,40,90,400,900的符号单列出来。然后我们使用贪心算法,每次运算都将总数减去最大的可能数值,直到减到0。(就像假如你要付67块钱,肯定先找出一张50块,再找出一张10块,再找出一张5块,最后找出两张1块)。
下面附上Python3的题解代码
class Solution:
def intToRoman(self, num: int) -> str:
dic={'I':1,'IV':4,'V':5,'IX':9,'X':10,'XL':40,'L':50,'XC':90,'C':100,'CD':400,'D':500,'CM':900,'M':1000}
res=""
for val in sorted(dic.values())[::-1]:
if num==0:
break
tmp=num//val
if tmp==0:
continue
res +=(list (dic.keys()) [list (dic.values()).index (val)])*tmp
num -=val*tmp
return res
趁热打铁 刷题练习部分(持续更新)
以下是LeetCode题库中一些用到贪心算法的经典例题的题目及解析,有题干,有题解代码、有解题思路(持续更新):
No.12.整数转罗马字母:
https://blog.csdn.net/LanXiu_/article/details/104085783
No.13.罗马数字转整数:
https://blog.csdn.net/LanXiu_/article/details/104085783
No.45.跳跃游戏Ⅱ:
https://blog.csdn.net/LanXiu_/article/details/104177349
【『算法』『数据结构』|『算法』『数据结构』 浅谈贪心算法,理解程序员必懂必会的计算机常见算法——贪心算法】No.55.跳跃游戏:
https://blog.csdn.net/LanXiu_/article/details/104216342
推荐阅读
- 画解算法(1.|画解算法:1. 两数之和)
- Guava|Guava RateLimiter与限流算法
- 一个选择排序算法
- SG平滑轨迹算法的原理和实现
- 《算法》-图[有向图]
- “送出”|“送出” "能用" 『又不好用的东西』
- LeetCode算法题-11.|LeetCode算法题-11. 盛最多水的容器(Swift)
- 虚拟DOM-Diff算法详解
- 『青春』(9)有点不甘心
- 《数据结构与算法之美》——队列