内容简介:概述贪心算法通常用来求解最优问题最优装载问题
本文主要作为自己的学习笔记,并不具备过多的指导意义。
概述
贪心算法通常用来求解最优问题
-
由局部最优解到整体最优解
通过不断对局部最优进行操作,最终达到整体最优
-
无后效性
后序操作,不会出现数据状态的回滚
-
和DP(动态规划)之间的联系
很多贪心问题可以通过DP进行求解
最优装载问题
-
给出N个物体,第一个物体重量为Mi
-
尽量选择最多的物品,总重不超过C
先将物品按照质量排序,然后依次放入每个物品,直到总重量将超过C位置。
这里依次将剩余物品中质量最小的物品放入的过程,就是贪心的过程。
合并果子
一类总过程代价,取决于子过程代价的问题
-
有N堆果子,没堆果子的数量为Ai,每次可以将两堆果子合并,每次合并将消耗两堆果子总数的体力。
-
求最小消耗的体力
-
1
<10000
首先,如果我们什么都不管直接两两合并: 总计消耗48点体力
然后,我们尝试 排序 后两两合并:总计消耗44点体力
最后,我们尝试只将当前所有数据中最小的两个进行合并:总计消耗38点体力
解法
构建一个小根堆,每次从堆顶推出两个元素合并。并且将合并都的元素追加进小根堆中即可。
具体证明的过程有一定难度,可以参考哈夫曼编码证明的过程。
以上的操作过程,也就是贪心的过程。他只保证单次合并所消耗的体力最优,而不在意其他的数据该如何合并。
堆结构往往用来解决贪心的问题。因为贪心问题往往需要一个明确的指标,最大值或者最小值。
项目利润
输入:
cost[]:每个项目的花费
profits[]:每个项目的利润(纯利润)
k:最多能做k个项目
w:表示初始资金
输出:
最后可以获得的最大钱数
说明:一次只能做一个项目,且做完一个之后马上就能获得收益,可以支持做下一个项目
1. 将cost与profits中的元素依次合并成一个新的节点node:
public class Node { public var c :Int //项目花费 public var p :Int //项目利润 public init(cost:Int,profit:Int) { self.c = cost self.p = profit } }
2. 准备一个以项目花费构建的小根堆
将所有node依次放入
3. 准备一个以项目利润构建的大根堆
贪心过程:
-
从小根堆中依次弹出堆顶元素,直到node.c>w(项目所需资金大于当前资金)
具体代码上,将小根堆数组removeFirst,然后将arr[0]与arr[arr.count-1]位置交换。让小根堆对arr[0]位置元素向下调整即可。
-
将小根堆中弹出的元素放入大根堆中(大根堆中即为当前可执行的项目)
具体代码上,将元素追加进大根堆数组末尾,并进行调整即可。
-
从大根堆中弹出堆顶元素,并将w += node.p(执行收益最大的项目,并且更新当前资金)
具体代码上与第一步类似
该贪心过程总计执行k次,每一次执行都只需要关心小根堆中最小值,与大根堆中最大值即可。最后的w即为最大总资产。
会议安排
在优先的时间内安排数量最多的会议
做一张图可以直观表示过程:
我们将蓝色表示为待安排,红色表示为已安排,黑色表示为不可安排
我们可以尝试几种不同的贪心策略
1. 每次选择持续时间最短的安排
显然不可行
2. 每次选择开始时间最早的
显然也不可行
3. 每次选择开始时间最早的并且持续时间最短的来安排
由此可见该方案是可以行的
代码也很简单,只需要关心当前有效数据内开始时间晚于当前会议结束时间的结束时间最早的一个数据即可。
func bestArrange(programs:[Program]) -> Int { program.sort("end")//根据program.end进行排序 var res = 0 var current = 0 for p in programs { if p.current > current { //开始时间晚于当前时间,否则作废 res += 1 current = p.end //开会,当前时间变成会议结束时间 } } return res }
贪心策略的证明
贪心策略的数学证明通常很复杂,有能力可以去翻阅
这里推荐一种很方便的方式,对数器。
通过小样本大样本量的测试,证明贪心策略的正确性。
以 排序算法 的证明举例
var checkOK = true for i in 0..<10000 { var arr1 = generateRandomArray(size: 5, value: 20) //获取一个长度为10,最大值为20的随机数数组 var arr2 = arr1 //数组在swift里属于值类型,赋值动作会自动copy let originalArr = arr1 arr1.sort()//一定正确的算法 radixSort(arr: &arr2, maxDigit: 2) if arr1 != arr2 { checkOK = false print(originalArr) print(arr2) break } } print(checkOK ? "比对成功":"比对失败")
对于贪心问题,可能不一定存在一个一定正确的算法。那么我们完全可以不去比对结果是否一致,只要贪心策略的结果永远优于默认顺序得出的结果即可。
关于对数器的介绍可以参阅 另一篇
作者:kirito_song
链接:https://www.jianshu.com/p/d300efbd92bf
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 常用算法之贪心算法
- 贪心算法和分治算法
- 算法小专栏:贪心算法
- 【LeetCode】贪心算法--分发糖果(135)
- 【LeetCode】贪心算法--划分字母区间(763)
- 【LeetCode】贪心算法--买卖股票的最佳时机 II(122)
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
C语言接口与实现
(美)David R. Hanson / 人民邮电出版社 / 2010-8 / 79.00元
可重用的软件模块是构建大规模可靠应用程序的基石,创建可重用的软件模块是每个程序员和项目经理必须掌握的技能。C语言对创建可重用的API提供的语言和功能支持非常少,虽然C程序员写应用时都会用到API和库,但却很少有人去创建和发布新的能广泛应用的API。本书介绍用一种基于接口的设计方法创建可重用的API,这一方法将接口与实现分离开来,且与语言无关。书中详细描述了24个接口及其实现,便于读者深入了解此方法......一起来看看 《C语言接口与实现》 这本书的介绍吧!