关于贪心算法的一些总结 Apare_xzc
关于贪心算法的一些总结
关于贪心算法:
来自百度百科的定义:贪心算法是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,他所做出的是在某种意义上的局部最优解。
贪心算法不是对所有问题都能得到整体最优解,关键是贪心策略的选择,选择的贪心策略必须具备无后效性,即某个状态以前的过程不会影响以后的状态,只与当前状态有关。
贪心算法的2个基本要素:
贪心选择是指所求问题的整体最优解可以通过一系列局部最优的选择,即贪心选择来达到。这是贪心算法可行的第一个基本要素,也是贪心算法与动态规划算法的主要区别。贪心选择是采用从顶向下、以迭代的方法做出相继选择,每做一次贪心选择就将所求问题简化为一个规模更小的子问题。对于一个具体问题,要确定它是否具有贪心选择的性质,我们必须证明每一步所作的贪心选择最终能得到问题的最优解。通常可以首先证明问题的一个整体最优解,是从贪心选择开始的,而且作了贪心选择后,原问题简化为一个规模更小的类似子问题。然后,用数学归纳法证明,通过每一步贪心选择,最终可得到问题的一个整体最优解。
当一个问题的最优解包含其子问题的最优解时,称此问题具有最优子结构性质。运用贪心策略在每一次转化时都取得了最优解。问题的最优子结构性质是该问题可用贪心算法或动态规划算法求解的关键特征。贪心算法的每一次操作都对结果产生直接影响,而动态规划则不是。贪心算法对每个子问题的解决方案都做出选择,不能回退;动态规划则会根据以前的选择结果对当前进行选择,有回退功能。动态规划主要运用于二维或三维问题,而贪心一般是一维问题
研究的问题用贪心算法可以得到最优解需要满足的两个性质:
- 最优子结构性质
- 无后效性
几个可以用贪心算法求最优解的经典问题
问题描述:有n个物品,分别有各自的重量和价值,有一个最大容量(最大承受重量)为W的背包,问选择n个物品中其中若干物品,物品可以拆分,且密度不变。求背包可以得到的最大价值
sample input:
6个物品
背包最大容量为20kg
物品的重量(kg)和价值(¥)分别为
10 3
1 6
8 100
8 32
1 30
6 3
思路:因为物品可以拆分,我们可以贪心地选择单位重量价值比较大的物品先放入背包
序号 | 重量(kg) | 价值(¥) | 单位重量的价值(¥/kg) | 累计重量 |
---|---|---|---|---|
1 | 1 | 30 | 30 | <mark>1</mark> |
2 | 8 | 100 | 12.5 | <mark>9</mark> |
3 | 1 | 6 | 6 | <mark>10</mark> |
4 | 8 | 32 | 4 | <mark>18</mark> |
5 | 6 | 3 | 0.5 | 24 |
6 | 10 | 3 | 0.3 | 34 |
我们按单位重量价值从大到小进行排序,得到了新的序号,选择1,2,3,4号和5号的2kg:
ans = 30 + 100 + 6 + 32 + 0.5 * 2 = 169 (¥)
可以证明这样选择是最优解
学校n个活动,每个活动要单独占用学校展示厅一段连续的时间,均为开区间。(s1,e1), (s2,e2),(s3,e3),…,(s1,sn)
问最多可以安排几个活动?
我们用HDU-2037 今年暑假不AC为例来分析:
Sample Input
12
1 3
3 4
0 7
3 8
15 19
15 20
10 15
8 18
6 12
5 10
4 14
2 9
0
Sample Output
5
序号 | 00 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
1 | __ | __ | __ | |||||||||||||||||||
2 | __ | __ | ||||||||||||||||||||
3 | __ | __ | __ | __ | __ | __ | __ | __ | ||||||||||||||
4 | __ | __ | __ | __ | __ | __ | ||||||||||||||||
5 | __ | __ | __ | __ | __ | |||||||||||||||||
6 | __ | __ | __ | __ | __ | __ | ||||||||||||||||
7 | __ | __ | __ | __ | __ | __ | ||||||||||||||||
8 | __ | __ | __ | __ | __ | __ | __ | __ | __ | __ | __ | |||||||||||
9 | __ | __ | __ | __ | __ | __ | __ | |||||||||||||||
10 | __ | __ | __ | __ | __ | __ | ||||||||||||||||
11 | __ | __ | __ | __ | __ | __ | __ | __ | __ | __ | __ | |||||||||||
12 | __ | __ | __ | __ | __ | __ | __ | __ |
分析:
我们可以有几种贪心策略:
- 按照开始时间排序,从前往后选择
试一下下面这个例子:
**(1,30) (2,5) (5,10) (10,13) **
显然,先选(1,30)就凉了,答案显然是3 ,选择后三个即可 - 是不是因为(1,30)占据的时间太长了?我们不如按活动时间长度排序
试一下下面这个例子:
(1,4) (4,14)(13,15) (14,30)
显然,如果先选择活动时间最短的(13,15),那么其他的活动都不能安排了,得到的答案为1,但显然正确答案是3。 - 按照结束的时间排序,从小往大选择 <–这样的贪心策略是正确的。
据说可以用数学归纳法来证明。
保证了无后效性
我们现在按照结束时间排序:
序号 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
---|---|---|---|---|---|---|---|---|---|---|---|---|
开始时间 | <mark>1</mark> | <mark>3</mark> | 0 | 2 | 2 | <mark>5</mark> | 6 | 4 | <mark>10</mark> | 8 | <mark>15</mark> | 15 |
结束时间 | <mark>3</mark> | <mark>4</mark> | 7 | 8 | 9 | <mark>10</mark> | 12 | 14 | <mark>15</mark> | 18 | <mark>19</mark> | 20 |
我们根据排好序的表格可以得到答案为5
我们可以选择(1,3) (3,4) (5,10) (10,15) (15,19)这5个活动
纸币找零问题
现在有1元,5元,10元,20元,50元的纸币若干张(可以认为很多很多),要找零钱,要求找的零钱张数最少
分析: 很简单的问题,只需要面值从大到小找钱即可
买卖物品(记得是18年还是19年HDU多校的题)(洛谷5662纪念品)
题目大意:
一个商人,做生意n(n为偶数,n<=200)天,要保证开始没有货物,结束后也没有货物。每天只能买一件物品或者卖一件物品。已知每天的商品价格,第一天一定买,最后一天一定卖。求商人可以赚到的钱的最大值。
分析:
这是个挺好的题。我们来分析一下。显然,要想赚到钱,那么就要贪心地在价格较低的天数买入,在价格较高的天数卖出
我们分析,显然,某一天必须买或卖,不能什么都不做。那么我们肯定是有n/2天买入,有n/2天卖出。那我们要如何决策在哪几天买入,哪几天卖出呢?还有一点,没有货物当然不能卖,我们要保证买货减去卖货的前缀和永远是非负数。
因为第1天一定会买,所以到了2天就可以选择买或卖。这个时候我们就要进行决策。如果2天价格比较高,可以选择直接卖掉,但是可能第3天价格更高,只能高价买入。
好在我们提前知道了每天的价格。我们就可以进行“撤销”’操作。
因为第1天一定得买,第n天一定得卖。那么[2,n-1]这个区间里面买的天数等于卖的天数。我们可以先贪心地考虑相邻的两天。第i天和第i+1天哪一天价格高就卖出,哪一天价格低就买入。伪代码如下:
int solve(int n,int * price)
ans = a[n]-a[1]
for i in range(2,n):
buy = min(price[i],price[i+1])
sell= max(price[i],price[i-1])
ans = ans + sell - buy
return ans
但是这样简单地for循环一遍贪心有两个问题:
- 可能这样达不到最优解
- 可能这样会出现先卖5天再买5天的情况,不符合实际。
n = 6,每天的价格如下:
10 1 2 100 200 5
我们可以知道,第1天一定买入,第6天一定卖出。
ans = 5-10 = -5;
我们考虑第2天和第3天,价格为1和2,我们从这两天的低价买入,高价卖出。
ans = -5 - 1 + 2 = -4
我们到了第4天,发现,第三天是卖出,此时手里面还剩下货物1(10元买入)
我们再看第4天和第5天, 100元和200元,我们在100元的时候买入,200元的时候卖出。
ans = -4 - 100 + 200 = 96
这个问题有一个特点:
我们手里没有货就不能卖,但是可以买很多东西屯着,以后价格高的时候再买
我们可以把卖了的物品记录下来,如果这样不合算的话,以后可以再撤销。
如果日后发现,之前某天的卖出价格太低了,后面的卖价更高,我们就可以调整方案:在之前卖出价格最低的那天买入,然后在后面价格较高的时候卖出,为了快速查找之前卖出的最小值和并更新,我们可以用小顶堆来存储所有卖货的价格。
如果发现某天买入的价格比之前卖出的价格更高,就推到之前低价卖出那天,选择在那天买入,然后在这天价格高的时候卖出。
代码略~
其它利用贪心思想的算法:
- 哈夫曼编码:Huffman
- 最小生成树算法:Kruskal,prim
- 最短路算法:Dijkstra
2019.12.7 xzc