贪心算法(greedy algorithm,又称贪婪算法)详解(附例题)

news2025/1/2 3:39:18
目录
    • 基本思想
    • 一)概念
    • 二)找出全局最优解的要求
    • 三)求解时应考虑的问题
    • 四)基本步骤
    • 五)贪心策略选择
    • 六)实际应用
      • 1.零钱找回问题
      • 2.背包问题
      • 3.哈夫曼编码
      • 4.单源路径中的Djikstra算法
      • 5.最小生成树Prim算法

基本思想

贪心算法(Greedy Algorithm)是一种在求解问题时,每一步都选择当前最优解,以期望最终得到全局最优解的算法思想。贪心算法的基本思想可以总结为“每一步都做出一个局部最优的选择,最终就能得到全局最优解”。

贪心算法通常包含以下关键步骤:

找到可选的子问题: 首先,将原问题拆分成一系列可选的子问题或决策。

找到局部最优解: 对每个子问题,找到一个局部最优解。这个局部最优解应该是一个贪心选择,即在当前状态下选择最优的方式。

合并子问题的解: 将各个子问题的局部最优解合并起来,得到原问题的解。

检查解的有效性: 最后,检查得到的解是否满足问题的约束和要求。如果满足,就认为得到了问题的解。

贪心算法适用于一些特定类型的问题,通常要求问题具有贪心选择性质(即每一步的选择都是最优的),以及最优子结构性质(即问题的最优解可以通过子问题的最优解推导得出)。然而,贪心算法不一定能够求解所有问题,有些问题可能需要更复杂的算法来解决。

经典的贪心算法问题有找零钱问题、活动选择问题、背包问题中的部分背包等。贪心算法在求解这些问题时,通常能够得到接近最优解的结果,但并不保证一定能够得到全局最优解。

总之,贪心算法是一种基于每一步的局部最优选择来求解问题的思想,适用于一些满足贪心选择性质和最优子结构性质的问题。

一)概念

贪心算法(Greedy Alogorithm)又叫登山算法,它的根本思想是逐步到达山顶,即逐步获得最优解,是解决最优化问题时的一种简单但是适用范围有限的策略。

贪心算法没有固定的框架,算法设计的关键是贪婪策略的选择。贪心策略要无后向性,也就是说某状态以后的过程不会影响以前的状态,只与当前状态有关。

贪心算法是对某些求解最优解问题的最简单、最迅速的技术。某些问题的最优解可以通过一系列的最优的选择即贪心选择来达到。但局部最优并不总能获得整体最优解,但通常能获得近似最优解

在每一步贪心选择中,只考虑当前对自己最有利的选择,而不去考虑在后面看来这种选择是否合理。

二)找出全局最优解的要求

在遇见问题时如何确定是否可以使用贪心算法解决问题,那么决定一个贪心算法是否能找到全局最优解的条件是什么呢?其实就是以下两点:

  • 最优子结构(optimal subproblem structure,和动态规划中的是一个概念)
  • 最优贪心选择属性(optimal greedy choice property)

三)求解时应考虑的问题

1.候选集合S
为了构造问题的解决方案,有一个候选集合C作为问题的可能解,问题的最终解均取自于候选集合C。
2.解集合S
随着贪心选择的进行,解集合不断扩展,直到构成一个满足问题的完整解。
3.解决函数solution
检查解集合是否构成问题的完整解。
4.选择函数select
即贪心策略,这是贪心算法的关键,它指出哪个候选对象有希望构成成问题的解。
5.可行函数feasible
检查解集合中加入一个候选对象是否可行,即解集合扩展后是否满足约束条件。

四)基本步骤

贪心算法使用基本步骤:
1.从问题的某个初始解出发
2.采用循环语句,当可以向求解目标前进一步时,就根据局部最优策略,得到一个部分解,缩小问题的范围或规模。
3.将所有的部分解综合起来,得到问题的最终解。

五)贪心策略选择

贪心算法的原理是通过局部最优来达到全局最优,采用的是逐步构造最优解的方法。在每个阶段,都做出一个看上去最优的,决策一旦做出,就不再更改。

要选出最优解可不是一件容易的事,要证明局部最优为全局最优,要进行数学证明,否则就不能说明为全局最优。

很多问题表面上看来用贪心算法可以找到最优解,实际上却把最优解给漏掉了。这就像现实生活中的“贪小便宜吃大亏”。所以我们在解决问题的时候,一定要谨慎使用贪心算法,一定要注意这个问题适不适合采用贪心算法

贪心算法很多时候并不能达到全局最优,为什么我们还要使用它呢?

因为在很多大规模问题中,寻找最优解是一件相当费时耗力的事情,有时候付出大量人力物力财力后,回报并不与投入成正比。在这个时候选择相对最优的贪心算法就比较经济可行了。有的问题对最优的要求不是很高,在充分衡量付出和回报后,选择贪心算法未尝不是一种不错的选择呢。

六)实际应用

1.零钱找回问题

这个问题在我们的日常生活中就更加普遍了。假设1元、2元、5元、10元、20元、50元、100元的纸币分别有c0, c1, c2, c3, c4, c5, c6张。现在要用这些钱来支付K元,至少要用多少张纸币?用贪心算法的思想,很显然,每一步尽可能用面值大的纸币即可。在日常生活中我们自然而然也是这么做的。在程序中已经事先将Value按照从小到大的顺序排好。
下面展示一些 内联代码片

#include<iostream>
#include<algorithm>
using namespace std;
const int N=7; 
int Count[N]={3,0,2,1,0,3,5};
int Value[N]={1,2,5,10,20,50,100};
  
int solve(int money) 
{
	int num=0;
	for(int i=N-1;i>=0;i--) 
	{
		int c=min(money/Value[i],Count[i]);
		money=money-c*Value[i];
		num+=c;
	}
	if(money>0) num=-1;
	return num;
}
 
int main() 
{
	int money;
	cin>>money;
	int res=solve(money);
	if(res!=-1) cout<<res<<endl;
	else cout<<"NO"<<endl;
}

2.背包问题

在 从零开始学动态规划中我们已经谈过三种最基本的背包问题:零一背包,部分背包,完全背包。很容易证明,背包问题不能使用贪心算法。然而我们考虑这样一种背包问题:在选择物品i装入背包时,可以选择物品的一部分,而不一定要全部装入背包。这时便可以使用贪心算法求解了。计算每种物品的单位重量价值作为贪心选择的依据指标,选择单位重量价值最高的物品,将尽可能多的该物品装入背包,依此策略一直地进行下去,直到背包装满为止。在零一背包问题中贪心选择之所以不能得到最优解原因是贪心选择无法保证最终能将背包装满,部分闲置的背包空间使每公斤背包空间的价值降低了。在程序中已经事先将单位重量价值按照从大到小的顺序排好。

#include<iostream>   
using namespace std;   
const int N=4;  
void knapsack(float M,float v[],float w[],float x[]);  
  
int main()  
{  
    float M=50;
	//背包所能容纳的重量   
    float w[]={0,10,30,20,5};
	//每种物品的重量  
    float v[]={0,200,400,100,10};  
  	//每种物品的价值 
    float x[N+1]={0};  
    //记录结果的数组 
    knapsack(M,v,w,x);  
    cout<<"选择装下的物品比例:"<<endl;  
    for(int i=1;i<=N;i++) cout<<"["<<i<<"]:"<<x[i]<<endl;  
}  
  
void knapsack(float M,float v[],float w[],float x[])  
{  
    int i;  
    //物品整件被装下  
    for(i=1;i<=N;i++)
    {  
        if(w[i]>M) break;   
        x[i]=1;  
        M-=w[i];  
    }   
    //物品部分被装下  
    if(i<=N) x[i]=M/w[i];   
} 

3.哈夫曼编码

假设有一系列的字符,我们希望用一些二进制码来代替这些字符以进行数据压缩,使得压缩后的总比特数最小。哈夫曼编码正是这样一样压缩数据的方式。

在这里插入图片描述

如果我们已知各字符在文本中的出现频率,考虑到为了让压缩后的数据更小,我们直觉是让出现频率高的字符用尽可能短的编码,而出现频率高的则可以用更长的编码。

哈夫曼编码的解决方案是这样的:不断找到当前出现频率最小的两个结点(字符或频率),将它们结合,作为一个新生成的结点的左右子结点,并将新生成的结点继续放入比较,直到没有落单的字符。

过程演示

该贪心算法针对这个问题得到的解是最优的。

4.单源路径中的Djikstra算法

求A到其他节点的最短路径:
在这里插入图片描述
维护三个东西,从A到其他节点的路径长度队列Queue,数组visited用于记录已保存最短路径的节点,数组res用于记录节点A到其他节点的最短路径。
开始时,Queue中只有A节点自己,三组数据如下:
Queue:[(A, 0)] 起始节点为A,A到A的距离为0
visited:[true, false,false,false,false] A节点是已经访问过的节点,是true,其他节点是false
res:[0,∞,∞,∞,∞] A到自己的距离是0,到其他节点的距离目前是∞

将以A为起点的路径加入到Queue中,2和4是节点D和B的路径权重:
Queue:[(D, 2), (B, 4)]
visited:[true, false,false,false,false]
res:[0,∞,∞,∞,∞]
在Queue中,路径最短的是D,取出D,更新三组数据:
Queue:[(B, 3), (C, 3), (E, 9)]

因为A-D-B的路径权重为3小于A-B的路径权重4,所以更新一下B的路径权重。
visited:[true,false,false,true,false]
res: [0,∞, ∞,2,∞]

取出B,更新三组数据:
Queue: [(C,3), (E, 9)]
visited: [true,true,false,true,false]
res: [0,3, ∞,2, ∞]

取出C,更新三组数据:
Queue:[(E, 6)]
visited: [true,true,true,true,false]
res: [0,3, 3,2, ∞]

取出E,更新三组数据:
Queue:[]
visited: [true,true,true,true,true]
res: [0,3, 3,2, 6]

至此,Queue队列空,计算过程结束。

5.最小生成树Prim算法

prim算法(读者可以将其读作“普里姆算法”)用来解决最小生成树问题,其基本思想是对图G(V,E)设置集合S,存放已被访问的顶点,然后每次从集合V-S中选择与集合S的最短距离最小的一个顶点(记为u),访问并加入集合S。之后,令顶点u为中介点,优化所有从u能到达的顶点v与集合S之间的最短距离。这样的操作执行n次(n为顶点个数),直到集合S已包含所有顶点。可以发现,prim算法的思想与最短路径中Dijkstra算法的思想几乎完全相同,只是在涉及最短距离时使用了集合S代替 Dijkstra算法中的起点s。

在这里插入图片描述

①将地图上的所有边都抹去,只有当访问一个顶点后オ把这个顶点顶点连接的边显现(这点和Dijkstra算法中相同)。

②将已访问的顶点置于ー个巨型防护罩中。可以沿着这个防护罩连接的边去访问未到达的顶点

③在地图中的顶点V(0≤i≤5)上记录顶点V与巨型防护罩之间的最短距离(即V与每个访问的顶点之间距离的最小值)。由于在①把所有边都抹去了,因此在初始状态下只在顶点V0上标记0,而其他顶点都标记无穷大(记为INF)。为了方便叙述,在下文中某几处出现的最短距离都是指从顶点V与当前巨型防护罩之间的最短距离。

下面是行动策略:

①由于要访问六个顶点,因此将②③步骤执行六次,每次访问一个顶点(如果是n个顶点,那么就执行n次)。

②每次都从还未访问的顶点中选择与当前巨型防护罩最近的顶点(记为Vk(0≤k≤5)),使用“爆裂模式”的能力恢复这条最近的边(并成为最小生成树中的一条边),前往访问。

③访问顶点Vk后,将Vk加入巨型防护罩中,开放地图上Vk连接的所有边,并査看以Vk作为巨型防护罩连接外界的接口的情况下,能否利用Vk刚开放的边使某些还未访问的顶点与巨型防护罩的最短距离变小。如果能,则将那个最短距离覆盖到地图对应的顶点上。

另外,为了得到最小生成树的边权之和,需要在访问顶点之前设置一个初值为0的变量sum,并在攻打过程中将加入最小生成树中的边的边权累加起来。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1501765.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

nginx-排查一次大文件无法正常下载问题

目录 问题现象&报错信息 问题现象以及分析 nginx报错信息 问题解决 方法1&#xff1a;配置proxy_max_temp_file_size 方法2&#xff1a;关闭proxy_buffering 参考文档 问题现象&报错信息 问题现象以及分析 文件正常从后端服务器直接下载时&#xff0c;一切正常…

PCL不同格式点云读取速度(Binary和ASCII )

首先说明一点&#xff1a;Binary(二进制)格式点云文件进行读取时要比Ascll码格式点云读取时要快的多&#xff0c;尤其是对于大型的点云文件&#xff0c;如几百万、甚至几千万个点云的情况下。 今天遇到了一种情况&#xff0c;在写项目的时候进行点云读取&#xff0c;读取的时候…

第七届强网杯-PWN-【warmup】

文章目录 warmup libc 2.35检查IDA逆向maindeldelete_noteadd_noteshow_noteinput_numberread_16atoi __errno_location()相关解释prctl相关 思路高版本off by null利用技巧产生chunk extend泄露libc基地址泄露heap基地址修改放入tcachebin中的chunk的fd为stdout最后add两个chu…

单链表详解(如何实现单链表)

文章目录 前言 一、单链表是什么&#xff1f;二、单链表的实现总结 顺序表的缺点 1.中间/头部的插入删除&#xff0c;时间复杂度为O (N) 2.realloc 扩容&#xff08;特别是异地扩&#xff0c;需要申请新空间&#xff0c;拷贝数据&#xff0c;释放旧空间&#xff09;会有不小的…

语音模块学习——LSYT201B模组(实际操作篇)

目录 一、定制词条 二、直接用串口通信 三、使用单片机通信 理论篇在这&#xff0c;依旧是深圳雷龙发展的语音模块。 http://t.csdnimg.cn/2SzJL 一、定制词条 因为我想后面加到我的毕设上加个语音模块&#xff0c;所以定制的词条都是和芯测相关的。 动作词条播报串口输…

【数据结构】单链表的层层实现!! !

关注小庄 顿顿解馋(●’◡’●) 上篇回顾 我们上篇学习了本质为数组的数据结构—顺序表&#xff0c;顺序表支持下标随机访问而且高速缓存命中率高&#xff0c;然而可能造成空间的浪费&#xff0c;同时增加数据时多次移动会造成效率低下&#xff0c;那有什么解决之法呢&#xff…

devops-Maven【部署及配置】

1、准备maven工具包&#xff0c;Maven官网下载Maven的安装包 Maven – Download Apache Maven Index of /maven (apache.org) 选择后缀是.bin.tar.gz的文件下载&#xff0c;此处下载的版本是3.9.6。 2、安装maven的目录下&#xff0c;建一个Maven路径&#xff0c;然后把压缩…

打开stable diffusion webui时,提示缺少clip或clip安装不上怎么办

在当前数字化时代&#xff0c;软件工具的应用已经成为人们日常生活和工作中不可或缺的一部分。而在使用各种软件工具的过程中&#xff0c;遇到一些技术性问题也是常有的事情。比如&#xff0c;在打开 Stable Diffusion WebUI 这样一个功能强大的工具时&#xff0c;有时会遇到缺…

【win11开启telnet】‘telnet‘ 不是内部或外部命令,也不是可运行的程序 或批处理文件。

解决Windows 11上的’telnet’问题 遇到了一个在Windows 11上常见的问题&#xff0c;那就是尝试使用telnet命令时&#xff0c;出现了以下的错误消息: ‘telnet’ 不是内部或外部命令&#xff0c;也不是可运行的程序 或批处理文件。 解决方案 打开“控制面板”。(Win R -&g…

C语言代码:玫瑰花

前文 在古希腊神话中&#xff0c;玫瑰花集爱与美于一身&#xff0c;既是美神的化身&#xff0c;又溶进了爱神的血液&#xff0c;所以它所代表的含义是爱情。 我们应该用玫瑰花来表达我们的爱意&#xff0c;但是好多的恋人都是因为异地而没有办法去买一束新鲜的玫瑰去送给自己的…

StarRocks实战——欢聚集团极速的数据分析能力

目录 一、大数据平台架构 二、OLAP选型及改进 三、StarRocks 经验沉淀 3.1 资源隔离&#xff0c;助力业务推广 3.1.1 面临的挑战 3.1.2 整体效果 3.2 稳定优先&#xff0c;监控先行&#xff0c;优化运维 3.3降低门槛&#xff0c;不折腾用户 3.3.1 与现有的平台做打通 …

Arm:初识Keil MDK Vision 6及VScode应用Keil 6(Keil Studio for VS Code安装与使用)

系列文章目录 目录 系列文章目录 前言 一、 Keil MDK Vision 6是什么&#xff1f; 二、Keil MDK Vision 6的组合 2.最值得一看的更新就是VScode插件 三、Keil MDK Vision 6与VScode的组合能碰撞出火花吗&#xff1f;&#xff08;Keil Studio for VS Code&#xff09; 前…

HBase安装,配置,启动,检查

目录: 一、HBase安装&#xff0c;配置 1、下载HBase安装包 2、解压&#xff0c;配置环境变量并激活 3、hbase 配置 4、将hadoop和zookeeper的配置文件创建软连接放在hbase配置目录 5、配置 regionserver 二、HBase启动与关闭&#xff0c;安装检验 1、启动关闭hbase的命令 2、 检…

关于手机是否支持h264的问题的解决方案

目录 现象 原理 修改内容 现象 开始以为是手机不支持h264的编码 。机器人chatgpt一通乱扯。 后来检查了下手机&#xff0c;明显是有h264嘛。 终于搞定&#xff0c;不枉凌晨三点起来思考 原理 WebRTC 默认使用的视频编码器是VP8和VP9&#xff0c;WebRTC内置了这两种编码器…

Java 学习和实践笔记(31):封装(encapsulation)

面向对象的三大特点&#xff1a;继承、封装、多态。前面学了继承&#xff0c;现在讲封装。 封装encapsulation一词来自于capsule&#xff0c;胶囊&#xff0c;小密器&#xff0c;密闭的空间。 封装的理念&#xff1a;高内聚&#xff0c;低耦合。 高内聚就是类的内部数据操作…

Python是编译型还是解释型?——跟老吕学Python编程

Python是编译型还是解释型&#xff1f; 编译型语言和解释型语言的概念编译型语言的定义解释型语言的定义 编译型语言和解释型语言的区别主要体现在程序的执行过程&#xff1a;编译型语言解释型语言 Python的编译和执行过程Python的解释器Python的交互式解释器Python与编译型语言…

thingsboard如何自定义udp-transport

0、参考netty实现udp的文章 https://github.com/narkhedesam/Netty-Simple-UDP-TCP-server-client/blob/master/netty-udp/src/com/sam/netty_udp/server/MessageDecoder.java 调试工具使用的是:卓岚TCP&UDP调试工具 1、在common\transport下面创建udp模块,仿照mqtt的创…

千兆网络变压器的特点

不要选错了&#xff0c;同款的小24PIN工业级千兆网络变压器有两种&#xff0c;外壳尺寸、工程参数完全相同。很多客户对这两款产品傻傻分不清&#xff0c;今天我就来详细介绍一下&#xff1a; HX82409S特点有三个&#xff1a; 一&#xff0c;采用单环设计&#xff0c;只有一颗…

部署 LVS(nginx)+keepalived高可用负载均衡集群

目录 一、集群的概述 1、什么是集群 2、普通集群与负载均衡集群 2.1 普通集群&#xff08;Regular Cluster&#xff09; 2.2 负载均衡集群&#xff08;Load Balancing Cluster&#xff09; 2.3 高可用集群&#xff08;High Availability Cluster&#xff09; 2.4 区别 …

SpringMVC03、HelloSpring

3、HelloSpring 3.1、配置版 新建一个Moudle &#xff0c; springmvc-02-hello &#xff0c; 添加web的支持&#xff01; 确定导入了SpringMVC 的依赖&#xff01; 配置web.xml &#xff0c; 注册DispatcherServlet <?xml version"1.0" encoding"UTF-8…