C++中的贪心算法
一、基本概念
贪心算法(又称贪婪算法,Greedy Algorithm)是指,在对问题求解时,总是做出在当前看来是最好的选择,不从整体最优上加以考虑,所做出的仅是在某种意义上的局部最优解。贪心算法不是对所有问题都能得到整体最优解,但对范围相当广泛的许多问题能产生整体最优解或者是整体最优解的近似解。它需要满足贪心选择性质和最优子结构性质:
- 贪心选择性质:原问题的整体最优解可以通过一系列局部最优的选择得到。应用同一规则,将原问题变为一个相似的但规模更小的子问题,而后的每一步都是当前最佳的选择,这种选择依赖于已做出的选择,但不依赖于未做出的选择。
- 最优子结构性质:问题能够分解成子问题来解决,子问题的最优解能递推到最终问题的最优解。
二、证明方法
贪心算法有两种证明方法:
- 反证法:如果交换方案中任意两个元素/相邻的两个元素后,答案不会变得更好,那么可以推定目前的解已经是最优解了。
- 归纳法:先算得出边界情况(例如n = 1)的最优解F1,然后再证明:对于每个n,Fn+1都可以由Fn推导出结果。
三、常见题型及解法
(一)常见题型
在提高组难度以下的题目中,最常见的贪心有两种:
- 我们将XXX按照某某顺序排序,然后按某种顺序(例如从小到大)选择。
- 我们每次都取XXX中最大/小的东西,并更新XXX(有时「XXX中最大/小的东西」可以优化,比如用优先队列维护)。
(二)解法
- 排序解法
- 适用情况:输入一个包含几个(一般一到两个)权值的数组。
- 解法:通过排序然后遍历模拟计算的方法求出最优值。
- 后悔解法
- 思路:无论当前的选项是否最优都接受,然后进行比较,如果选择之后不是最优了,则反悔,舍弃掉这个选项;否则,正式接受。如此往复1。
四、与动态规划的区别
贪心算法与动态规划的不同在于它对每个子问题的解决方案都做出选择,不能回退;动态规划则会保存以前的运算结果,并根据以前的结果对当前进行选择,有回退功能。
五、C++中的实例
- 找零钱问题
- 策略:优先选用面值最大的纸币。例如1元、2元、5元、10元、20元、50元、100元的纸币分别有若干张,要用这些钱来支付K元,每一步尽可能用面值大的纸币即可。在程序中可以事先将纸币面值按照从小到大的顺序排好,然后按照贪心策略进行找零操作。
- 装载古董问题
- 思路:
- 先将古董重量升序排序。
- 按顺序依次往船上装古董,用一个变量记录已装载到船上的古董重量,一个变量记录已装载的古董个数,只要已装载重量小于等于载重量就继续装载,否则停止,最后得到能装入的古董最大数量。
- 思路:
- 宝物运输问题
- 思路:
- 首先根据宝物的单位价值从大到小排序(单位价值 = 价值/重量)。
- 按照排好的顺序贪心选择宝物,若宝物的重量小于毛驴剩下的承载能力就全部装入,更新毛驴的承载能力和已运走宝物价值之和;若宝物的重量大于毛驴剩下的承载能力就部分装入(根据剩余承载能力按比例计算价值),最后得到装入宝物的最大价值。
- 思路: