贪心算法

贪心算法(又称贪婪算法)是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,他所做出的仅是在某种意义上的局部最优解。贪心算法不是对所有问题都能得到整体最优解,但对范围相当广泛的许多问题他能产生整体最优解或者是整体最优解的近似解。

目录

1概念

2特性

3基本思路

4例题分析

5备注

6经典例题

7应用例子

概念

贪婪算法(Greedy algorithm)是一种对某些求最优解问题的更简单、更迅速的设计技术。用贪婪法设计算法的特点是一步一步地进行,常以当前情况为基础根据某个优化测度作最优选择,而不考虑各种可能的整体情况,它省去了为找最优解要穷尽所有可能而必须耗费的大量时间,它采用自顶向下,以迭代的方法做出相继的贪心选择,每做一次贪心选择就将所求问题简化为一个规模更小的子问题,通过每一步贪心选择,可得到问题的一个最优解,虽然每一步上都要保证能获得局部最优解,但由此产生的全局解有时不一定是最优的,所以贪婪法不要回溯。

贪婪算法是一种改进了的分级处理方法。其核心是根据题意选取一种量度标准。然后将这多个输入排成这种量度标准所要求的顺序,按这种顺序一次输入一个量。如果这个输入和当前已构成在这种量度意义下的部分最佳解加在一起不能产生一个可行解,则不把此输入加到这部分解中。这种能够得到某种量度意义下最优解的分级处理方法称为贪婪算法。

对于一个给定的问题,往往可能有好几种量度标准。初看起来,这些量度标准似乎都是可取的,但实际上,用其中的大多数量度标准作贪婪处理所得到该量度意义下的最优解并不是问题的最优解,而是次优解。因此,选择能产生问题最优解的最优量度标准是使用贪婪算法的核心。

一般情况下,要选出最优量度标准并不是一件容易的事,但对某问题能选择出最优量度标准后,用贪婪算法求解则特别有效。最优解可以通过一系列局部最优的选择即贪婪选择来达到,根据当前状态做出在当前看来是最好的选择,即局部最优解选择,然后再去解做出这个选择后产生的相应的子问题。每做一次贪婪选择就将所求问题简化为一个规模更小的子问题,最终可得到问题的一个整体最优解。

特性

贪婪算法可解决的问题通常大部分都有如下的特性:

⑴ 有一个以最优方式来解决的问题。为了构造问题的解决方案,有一个候选的对象的集合:比如不同面值的硬币。

⑵ 随着算法的进行,将积累起其它两个集合:一个包含已经被考虑过并被选出的候选对象,另一个包含已经被考虑过但被丢弃的候选对象。

⑶ 有一个函数来检查一个候选对象的集合是否提供了问题的解答。该函数不考虑此时的解决方法是否最优。

⑷ 还有一个函数检查是否一个候选对象的集合是可行的,也即是否可能往该集合上添加更多的候选对象以获得一个解。和上一个函数一样,此时不考虑解决方法的最优性。

⑸ 选择函数可以指出哪一个剩余的候选对象最有希望构成问题的解。

⑹ 最后,目标函数给出解的值。

为了解决问题,需要寻找一个构成解的候选对象集合,它可以优化目标函数,贪婪算法一步一步的进行。起初,算法选出的候选对象的集合为空。接下来的每一步中,根据选择函数,算法从剩余候选对象中选出最有希望构成解的对象。如果集合中加上该对象后不可行,那么该对象就被丢弃并不再考虑;否则就加到集合里。每一次都扩充集合,并检查该集合是否构成解。如果贪婪算法正确工作,那么找到的第一个解通常是最优的。

基本思路

⒈建立数学模型来描述问题。

⒉把求解的问题分成若干个子问题。

⒊对每一子问题求解,得到子问题的局部最优解。

⒋把子问题的解局部最优解合成原来解问题的一个解。

实现该算法的过程:

从问题的某一初始解出发;

while 能朝给定总目标前进一步 do

求出可行解的一个解元素;

由所有解元素组合成问题的一个可行解。

下面是一个可以试用贪心算法解的题目,贪心解的确不错,可惜不是最优解。

例题分析

[0-1背包问题]有一个背包,背包容量是M=150。有7个物品,物品不可以分割成任意大小。

要求尽可能让装入背包中的物品总价值最大,但不能超过总容量。

物品 A B C D E F G

重量 35 30 60 50 40 10 25

价值 10 40 30 50 35 40 30

分析:

目标函数:∑pi最大

约束条件是装入的物品总重量不超过背包容量:∑wi<=M(M=150)

⑴根据贪心的策略,每次挑选价值最大的物品装入背包,得到的结果是否最优?

⑵每次挑选所占重量最小的物品装入是否能得到最优解?

⑶每次选取单位重量价值最大的物品,成为解本题的策略。

值得注意的是,贪心算法并不是完全不可以使用,贪心策略一旦经过证明成立后,它就是一种高效的算法。

贪心算法还是很常见的算法之一,这是由于它简单易行,构造贪心策略不是很困难。

可惜的是,它需要证明后才能真正运用到题目的算法中。

一般来说,贪心算法的证明围绕着:整个问题的最优解一定由在贪心策略中存在的子问题的最优解得来的。

对于例题中的3种贪心策略,都是无法成立(无法被证明)的,解释如下:

⑴贪心策略:选取价值最大者。

反例:

W=30

物品:A B C

重量:28 12 12

价值:30 20 20

根据策略,首先选取物品A,接下来就无法再选取了,可是,选取B、C则更好。

⑵贪心策略:选取重量最小。它的反例与第一种策略的反例差不多。

⑶贪心策略:选取单位重量价值最大的物品。

反例:

W=30

物品:A B C

重量:28 20 10

价值:28 20 10

根据策略,三种物品单位重量价值一样,程序无法依据现有策略作出判断,如果选择A,则答案错误。

【注意:如果物品可以分割为任意大小,那么策略3可得最优解】

对于选取单位重量价值最大的物品这个策略,可以再加一条优化的规则:对于单位重量价值一样的,则优先选择重量小的!这样,上面的反例就解决了。

但是,如果题目是如下所示,这个策略就也不行了。

W=40

物品:A B C

重量:25 20 15

价值:25 20 15

附:本题是个DP问题,用贪心法并不一定可以求得最优解,以后了解了动态规划算法后本题就有了新的解法。

备注

贪心算法当然也有正确的时候。求最小生成树的Prim算法和Kruskal算法都是漂亮的贪心算法。

贪心法的应用算法有Dijkstra的单源最短路径和Chvatal的贪心集合覆盖启发式

所以需要说明的是,贪心算法可以与随机化算法一起使用,具体的例子就不再多举了。(因为这一类算法普及性不高,而且技术含量是非常高的,需要通过一些反例确定随机的对象是什么,随机程度如何,但也是不能保证完全正确,只能是极大的几率正确)

经典例题

马踏棋盘的贪心算法

【问题描述】

马的遍历问题。在8×8方格的棋盘上,从任意指定方格出发,为马寻找一条走遍棋盘每一格并且只经过一次的一条最短路径。

【初步设计】

首先这是一个搜索问题,运用深度优先搜索进行求解。算法如下:

⒈ 输入初始位置坐标x,y;

⒉ 步骤 c:

如果c> 64输出一个解,返回上一步骤c--

(x,y) ← c

计算(x,y)的八个方位的子结点,选出那此可行的子结点

循环遍历所有可行子结点,步骤c++重复2

显然⑵是一个递归调用的过程,大致如下:

#define N 8

……

……

void dfs(int x,int y,int count)

{

int i,tx,ty;

if(count> N*N)

{

output_solution();//输出一个解

return;

}

for(I=0;i <8;++i)

{

tx=hn.x;//hn[]保存八个方位子结点

ty=hn.y;

s[tx][ty]=count;

dfs(tx,ty,count+1);//递归调用

s[tx][ty]=0;

}

}

Pascal程序:

Program YS;

Const FXx:array[1..8]of -2..2=(1,2,2,1,-1,-2,-2,-1);

FXy:array[1..8]of -2..2=(2,1,-1,-2,-2,-1,1,2);

Var

Road:array[1..10,1..10]of integer;

x,y,x1,y1,total:integer;

Procedure Find(x,y:integer);

var Nx,Ny,i:integer;

Begin

For i:=1 to 8 do begin {8个方向}

If (x+FXx in [1..8]) and (y+FXy in [1..8]) Then{确定新坐标是否越界}

If Road[x+Fxx,y+Fxy]=0 Then begin{判断是否走过}

Nx:=x+FXx; Ny:=y+FXy; Road[Nx,Ny]:=1;{建立新坐标}

If (Nx=x1) and (Ny=y1) Then inc(total)

else Find(Nx,Ny); {递归}

Road[Nx,Ny]:=0 {回朔}

end

end

End;

BEGIN{Main}

Total:=0;

FillChar(Road,sizeof(road),0);

Readln(x,y); {读入开始坐标}

Readln(x1,y1); {读入结束坐标}

If (x>10) or (y>10) or (x1>10) or (y1>10) Then writeln('Eorror') {判断是否越界}

Else Find(x,y);

Writeln('Total:',total) {打出总数}

END.

这样做是完全可行的,它输入的是全部解,但是马遍历当8×8时解是非常之多的,用天文数字形容也不为过,这样一来求解的过程就非常慢,并且出一个解也非常慢。

怎么才能快速地得到部分解呢?

【贪心算法】

其实马踏棋盘的问题很早就有人提出,且早在1823年,J.C.Warnsdorff就提出了一个有名的算法。在每个结点对其子结点进行选取时,优先选择'出口'最小的进行搜索,'出口'的意思是在这些子结点中它们的可行子结点的个数,也就是'孙子'结点越少的越优先跳,为什么要这样选取,这是一种局部调整最优的做法,如果优先选择出口多的子结点,那出口少的子结点就会越来越多,很可能出现'死'结点(顾名思义就是没有出口又没有跳过的结点),这样对下面的搜索纯粹是徒劳,这样会浪费很多无用的时间,反过来如果每次都优先选择出口少的结点跳,那出口少的结点就会越来越少,这样跳成功的机会就更大一些。这种算法称为为贪心算法,也叫贪婪算法或启发式算法,它对整个求解过程的局部做最优调整,它只适用于求较优解或者部分解,而不能求最优解。这样的调整方法叫贪心策略,至于什么问题需要什么样的贪心策略是不确定的,具体问题具体分析。实验可以证明马遍历问题在运用到了上面的贪心策略之后求解速率有非常明显的提高,如果只要求出一个解甚至不用回溯就可以完成,因为在这个算法提出的时候世界上还没有计算机,这种方法完全可以用手工求出解来,其效率可想而知。

应用例子

如把3/7和13/23分别化为三个单位分数的和

【贪心算法】

设a、b为互质正整数,a<b 分数a/b 可用以下的步骤分解成若干个单位分数之和:

步骤一: 用b 除以a,得商数q1 及余数r1。(r1=b - a*q1)

步骤二:把a/b 记作:a/b=1/(q1+1)+(a-r)/b(q1+1)

步骤三:重复步骤2,直到分解完毕

3/7=1/3+2/21=1/3+1/11+1/231

13/23=1/2+3/46=1/2+1/16+1/368

以上其实是数学家斐波那契提出的一种求解埃及分数的贪心算法,准确的算法表述应该是这样的:

设某个真分数的分子为a,分母为b;

把b除以a的商部分加1后的值作为埃及分数的某一个分母c;

将a乘以c再减去b,作为新的a;

将b乘以c,得到新的b;

如果a大于1且能整除b,则最后一个分母为b/a;算法结束;

或者,如果a等于1,则,最后一个分母为b;算法结束;

否则重复上面的步骤。

备注:事实上,后面判断a是否大于1和a是否等于1的两个判断可以合在一起,及判断b%a是否等于0,最后一个分母为b/a,显然是正确的。

贪心算法详解(C++)相关推荐

  1. prim算法_贪心算法详解(附例题)

    贪心算法的特征规律 贪心算法,"贪心"二字顾名思义,因此其规律特征就是更加注重当前的状态,贪心法做出的选择是对于当前所处状态的最优选择,它的解决问题的视角是微观的"局部& ...

  2. 455. 分发饼干001(贪心算法+详解)

    一:题目 假设你是一位很棒的家长,想要给你的孩子们一些小饼干.但是,每个孩子最多只能给一块饼干. 对每个孩子 i,都有一个胃口值 g[i],这是能让孩子们满足胃口的饼干的最小尺寸:并且每块饼干 j,都 ...

  3. 【蓝桥杯方法篇】贪心算法详解一

    文章目录 一,贪心算法 二,以分配题为例 2.1,饼干分配 2.2,最少硬币问题 三,区间问题

  4. 贪心算法哈夫曼编码c语言,贪心算法详解:哈夫曼编码

    理解贪心算法 贪心算法是一种算法思想,并不是一个具体的算法,因此我们用两个例子来理解什么样的问题适合用贪心算法解决. 例一 现在有一个能装 100g 物品的背包,和一些可拆分的物品(见表格),怎么装才 ...

  5. 离线强化学习(Offline RL)系列3: (算法篇) IQL(Implicit Q-learning)算法详解与实现

    [更新记录] 论文信息:Ilya Kostrikov, Ashvin Nair, Sergey Levine: "Offline Reinforcement Learning with Im ...

  6. 最小生成树-Prim算法详解(含全部代码)

    目录 适用条件 测试所用图 算法详解 Prim算法代码 全部代码 实验结果 适用条件 加权连通图 测试所用图 所用原图及生成过程 其中,(a) 为原图,圆圈里面是节点的名称,边上的数字是边的权值.由实 ...

  7. ChatGPT模型采样算法详解

    ChatGPT模型采样算法详解 ChatGPT所使用的模型--GPT(Generative Pre-trained Transformer)模型有几个参数,理解它们对文本生成任务至关重要.其中最重要的 ...

  8. Dijkstra算法详解(完美图解、趣学算法)

    Dijkstra算法详解 Dijkstra算法设计 Dijkstra算法简介 Dijkstra算法的基本思想 Dijkstra贪心策略 完美图解 伪代码详解 完整代码 算法解析及优化拓展 使用优先队列 ...

  9. Matlab人脸检测算法详解

    这是一个Matlab人脸检测算法详解 前言 人脸检测结果 算法详解 源代码解析 所调用函数解析 bwlabel(BW,n) regionprops rectangle 总结 前言 目前主流的人脸检测与 ...

  10. 图论-最短路Dijkstra算法详解超详 有图解

    整体来看dij就是从起点开始扩散致整个图的过程,为什么说他稳定呢,是因为他每次迭代,都能得到至少一个结点的最短路.(不像SPFA,玄学复杂度) 但是他的缺点就是不能处理带负权值的边,和代码量稍稍复杂. ...

最新文章

  1. 关于深度学习的一点理解
  2. python多线程实现同步的方式_深入解析Python中的线程同步方法
  3. 飞天大数据平台助力轻松筹数字化运营
  4. spring下连接mysql_使用Spring连接mysql数据库
  5. Netcraft :2019年4月全球Web服务器排名 nginx首度登顶
  6. 做为产品经理如何判断一个创业项目是否靠谱?
  7. python3.7打包exe坑_[求助]入坑学习python 需要装pyinstaller打包成exe
  8. 进度条上的小圆点怎么做_Android自定义带圆点的半圆形进度条
  9. spring 4 异常HttpMediaTypeNotAcceptableException解决方案
  10. 【python基础语法】对象None
  11. 19. Treat class design as type design
  12. iText 7 基础
  13. 卷积编码verilog实现
  14. ASO优化经验:APP关键字优化的技巧
  15. 磁盘分区、格式化及 LVM 管理
  16. 基于python的批量网页爬虫
  17. BGP路由协议(1)
  18. 软件著作权申请 源代码编辑
  19. 【二分,找规律】Day 14 提高组模拟C组 T1 小麦亩产一千八
  20. 宏的录制——制作工资表

热门文章

  1. win下MySQL 8.0.11 修改密码、开启远程访问
  2. 13.4 Shelve模块
  3. 我国启动新型数据保护密码算法研究
  4. C#程序之二 -- 数独小游戏(1. UI设计)
  5. 《编程之美》笔记(一)
  6. apache 网址重定向
  7. spring @Transactional注解的使用和失效场景
  8. hibernate 实现数据库查询
  9. 萌新如何写出一篇优质观点类新媒体文章?
  10. 全明星投资融资5亿美元,曾下注亏损百亿的滴滴,及商汤、陆金所等独角兽...