算法设计与分析(超详解!) 第三节 贪婪算法

1.贪心算法基础

1.贪心算法的基本思想

贪心算法是从问题的某一个初始解出发,向给定的目标推进。但它与普通递推求解过程不同的是,其推动的每一步不是依据某一固定的递推式,而是做一个当时看似最佳的贪心选择,不断地将问题实例归纳为更小的相似的子问题,并期望通过所做的局部最优选择产生出一个全局最优解。

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

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

贪心算法是对某些求解最优解问题的最简单、最迅速的技术。某些问题的最优解可以通过一系列的最优的选择即贪心选择来达到。

但局部最优并不总能获得整体最优解,但通常能获得近似最优解。 在每一步贪心选择中,只考虑当前对自己最有利的选择,而不去考虑在后面看来这种选择是否合理。

2.贪心算法的基本要素

一个贪心算法求解的问题必须具备以下两要素:

1. 贪心选择性质        

所谓贪心选择性质是指应用同一规则,将原问题变为一个相似的、但规模更小的子问题、而后的每一步都是当前看似最佳的选择。这种选择依赖于已做出的选择,但不依赖于未做出的选择。

(1)贪心算法选择每一步最佳的

(2)不依赖于未做出的选择

(3)自顶向下的迭代

(4)程序在运行过程中无回溯过程

首先证明问题存在一个整理最优解必定包含了第一个贪心选择。然后证明在做了贪心选择之后,原问题简化为规模较小的类似的子问题,即可继续使用贪心选择。

2、最优子结构性质      

当一个问题的最优解包含其子问题的最优解时,称此问题具有最优子结构性质。 由于运用贪心策略解题在每一次都取得了最优解,问题的最优子结构性质是该问题可用贪心算法或动态规划算法求解的关键特征。

1.贪心算法(又称贪婪算法)是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,他所做出的是在某种意义上的局部最优解。 ​

2.贪心选择是指所求问题的整体最优解可以通过一系列局部最优的选择,即贪心选择来达到。这是贪心算法可行的第一个基本要素。 ​

3.当一个问题的最优解包含其子问题的最优解时,称此问题具有最优子结构性质。运用贪心策略在每一次转化时都取得了最优解。问题的最优子结构性质是该问题可用贪心算法求解的关键特征。贪心算法的每一次操作都对结果产生直接影响。贪心算法对每个子问题的解决方案都做出选择,不能回退。 ​

4.贪心算法的基本思路是从问题的某一个初始解出发一步一步地进行,根据某个优化测度,每一步都要确保能获得局部最优解。每一步只考虑一个数据,他的选取应该满足局部优化的条件。若下一个数据和部分最优解连在一起不再是可行解时,就不把该数据添加到部分解中,直到把所有数据枚举完,或者不能再添加算法停止。 ​

5.实际上,贪心算法适用的情贪心算法(贪婪算法)况很少。一般对一个问题分析是否适用于贪心算法,可以先选择该问题下的几个实际数据进行分析,就可以做出判断。

该算法存在的问题 :

1.不能保证求得的最后解是最佳的

2.不能用来求最大值或最小值的问题

3.只能求满足某些约束条件的可行解的范围

3.贪心算法适合的问题

贪心算法通常用来解决具有最大值或最小值的优化问题。它是从某一个初始状态出发,根据当前局部而非全局的最优决策,以满足约束方程为条件,以使得目标函数的值增加最快或最慢为准则,选择一个最快地达到要求的输入元素,以便尽快地构成问题的可行解。

4.贪心算法的基本步骤

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

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

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

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

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

步骤:

(1) 选定合适的贪心选择的标准;        

(2) 证明在此标准下该问题具有贪心选择性质;        

(3) 证明该问题具有最优子结构性质;        

(4) 根据贪心选择的标准,写出贪心选择的算法,求得最优解。

说明:(1)当一个问题具有多个最优解时,贪婪算法并不能求出所有的最优解。

(2)整体的最优解时通过一系列的局部最优选择,即贪心选择来达到的。通常采用的方法是假设问题的一个整体最优解,并证明可以修改这个最优解,使其以贪婪算法开始。

贪心算法使用基本步骤:

1.从问题的某个初始解出发

2.采用循环语句,当可以向求解目标前进一步时,就根据局部最优策略,得到一个不分解,缩小问题的范围或规模。

3.将所有的部分解综合起来,得到问题的最终解。

货郎担(旅行商)问题:

有n个城市,用1,2,…,n表示,城i,j之间的距离为dij,有一个货郎从城1出发到其他城市一次且仅一次,最后回到城市1,怎样选择行走路线使总路程最短?

假定有5个城市,费用矩阵如表1-1所示。如果货郎从第一个城市出发,采用贪婪法求解,解法如下图:

5.贪心算法实例——背包问题

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

2.汽车加油问题

问题描述: 一辆汽车加满油后可以行驶N千米。旅途中有若干个加油站,如图4-1所示。指出若要使沿途的加油次数最少,设计一个有效的算法,指出应在那些加油站停靠加油(前提:行驶前车里加满油)。

由于汽车是由始向终点方向开的,我们最大的麻烦就是不知道在哪个加油站加油可以使我们既可以到达终点又可以使我们加油次数最少。我们可以假设不到万不得已我们不加油,即除非我们油箱里的油不足以开到下一个加油站,我们才加一次油。在局部找到一个最优的解。每加一次油我们可以看作是一个新的起点,用相同的递归方法进行下去。最终将各个阶段的最优解合并为原问题的解得到我们原问题的求解。

贪心策略:汽车行驶过程中,应走到自己能走到并且离自己最远的那个加油站,在那个加油站加油后再按照同样的方法贪心选择下一个加油站。

例:在汽车加油问题中,设各个加油站之间的距离为(假设没有环路):1,2,3,4,5,1,6,6.汽车加满油以后行驶的最大距离为7,则根据贪心算法求得最少加油次数为4,需要在3,4,6,7加油站加油,如下图所示:

1)贪心选择性质        

设在加满油后可行驶的N千米这段路程上任取两个加油站A、B,且A距离始点比B距离始点近,则若在B加油不能到达终点那么在A加油一定不能到达终点,如下图:

由图可知:因为m+N<n+N,即在B点加油可行驶的路程比在A点加油可行驶的路程要长n-m千米,所以只要终点不在A、B之间且在B的右边的话,根据贪心选择,为使加油次数最少就会选择距离加满油得点远一些的加油站去加油,因此,加油次数最少满足贪心选择性质。

2)最优子结构性质

当一个大问题的最优解包含着它的子问题的最优解时,称该问题具有最优子结构性质。      

(b[1],b[2],……b[n])  整体最优解      

b[1]=1,(b[2],b[3],……b[n])  局部最优解      

每一次加油后与起点具有相同的条件,每个过程都是相同且独立。

算法步骤:先监测各加油站之间的距离,若发现其中有一个距离大于汽车加满油能行驶的距离,则输出no solution;否则,对加油站间的距离进行逐个扫描,尽量选择往远处行驶,不能行驶就让num+1.最终统计出来的num便是最少的加油站数

int Greedy(int a[],int n,int k) { int *b=new int[k+1]; //加油站加油最优解b1~bkint num = 0;  int s=0;    //加满油后行驶的公里数for(int i = 0;i < =k;i++) {    if(a[i] > n) {    cout<<"no solution\n";    return;    }    }    for(int i = 0,s = 0;i < =k;i++) {    s += a[i]; if(s > n) {    num++; b[i]=1;   s = a[i];  }    }    return num;    
}

3.最优服务次序问题

最优服务次序问题: 设有n个顾客同时等待同一项服务,顾客i 需要的服务时间为ti,1 ≤ i ≤ n,应如何安排这n 个顾客的服务次序才能使平均等待时间达到最小。平均等待时间是n 个顾客等待服务时间的总和除以n。

假设原问题为T,而我们已经知道了某个最优服务系列,即最优解为A={t(1),t(2),⋯ ,t(n)}(其中t(i)为第i个用户需要的服务时间),则每个用户等待时间为:          

T(1)=t(1); T(2)=t(1)+t(2);          

 ⋯⋯          

T(n)=t(1)+t(2)+t(3)+…+t(n);          

那么总等待时间,即最优值为:          

TA=n*t(1)+(n-1)*t(2)+…+(n+1-i)*t(i)+…+2*t(n-1)+t(n);        

由于平均等待时间是n个顾客等待时间的总和除以n,故本题实际上就是求使顾客等待时间的总和最小的服务次序。

贪心策略:对服务时间最短的顾客先服务的贪心选择策略。首先对需要服务时间最短的顾客进行服务,即做完第一次选择后,原问题T 变成了对n-1 个顾客服务的新问题T’。新问题和原问题相同,只是问题规模由n减小为n-1。基于此种选择策略,对新问题T’,选择n-1顾客中选择服务时间最短的先进行服务,如此进行下去,直至所有服务都完成为止。

1)贪心选择性质    

先来证明该问题具有贪心选择性质,即最优服务A 中t(1)满足条件: t(1)<=t(i)(2 ≤ i ≤ n)。证明:用反证法:    

假设t(1)不是最小的,不妨设t(1)>t(i)(i>1)。设另一服务序列B={t(i),t(2),⋯, t(1),...t(n)}     那么TA-TB=n*[t(1)-t(i)]+(n+1-i)*[t(i)- t(1)]=(1-i)*[t(i)-t(1)]>0                    

即TA>TB,这与A 是最优服务相矛盾,即问题得证。故最优服务次序问题满足贪心选择性质。

2) 问题的最优子结构性质  

在进行了贪心选择后,原问题T就变成了如何安排剩余的n-1 个顾客的服务次序的问题T’,是原问题的子问题。若A 是原问题T 的最优解,则A’={t(2),⋯ t(i)...t(n)}是服务次序问题子问题T’的最优解。

证明: 假设A’不是子问题T’的最优解,其子问题的最优解为B’,则有TB’<TA’,而根据TA 的定义知,TA’+t(1)=TA。因此,TB’+t(1)<TA’+t(1)=TA,即存在一个比最优值TA更短的总等待时间,而这与TA为问题T的最优值相矛盾。因此,A’是子问题T’的最优值。从以上贪心选择及最优子结构性质的证明,可知对最优服务次序问题用贪心算法可求得最优解。

根据以上证明,最优服务次序问题可以用最短服务时间优先的贪心选择可以达到最优解。故只需对所有服务先按服务时间从小到大进行排序,然后按照排序结果依次进行服务即可。平均等待时间即为TA/n.

/*功能:计算平均等待时间输入:各顾客等待时间a[n],n是顾客人数   输出:平均等待时间average
*/
double GreedyWait(int a[],int n)
{double average=0.0;Sort(a);//按服务时间从小到大排序for(int i=0;i<n;i++){average += (n-i)*a[i];}average /=n;return average;}

例:有一批集装箱要装上一艘载重量为c的轮船。其中集装箱i的重量为Wi。最优装载问题要求确定在装载体积不受限制的情况下,将尽可能多的集装箱装上轮船。

1、算法描述         

最优装载问题可用贪心算法求解。采用重量最轻者先装的贪心选择策略,可产生最优装载问题的最优解。具体算法描述如下页。

template<class Type>
void Loading(int x[],  Type w[], Type c, int n)
{int *t = new int [n+1];Sort(w, t, n);for (int i = 1; i <= n; i++) x[i] = 0;for (int i = 1; i <= n && w[t[i]] <= c; i++) {x[t[i]] = 1; c -= w[t[i]];}
}

多机调度问题要求给出一种作业调度方案,使所给的n个作业在尽可能短的时间内由m台机器加工处理完成。         

约定,每个作业均可在任何一台机器上加工处理,但未完工前不允许中断处理。作业不能拆分成更小的子作业。

这个问题是NP完全问题,到目前为止还没有有效的解法。对于这一类问题,用贪心选择策略有时可以设计出较好的近似算法。

4.区间相交问题

给定x 轴上n 个闭区间。去掉尽可能少的闭区间,使剩下的闭区间都不相交。输出计算出的去掉的最少闭区间数。

最小删去区间数目=区间总数目-最大相容区间数目。        

区间选择问题即若干个区间要求互斥使用某一公共区间段,目标是选择最大的相容区间集合。 假定集合S={x1, x2, …, xn}中含有n个希望使用某一区间段,每个区间xi有开始时间li和完成时间ri,其中,0≤lefti<righti<∞。如果某个区间xi被选中使用区间段,则该区间在半开区间(lefti,righti]这段时间占据区间段。如果区间xi和xj在区间(lefti,righti ]和(leftj,rightj]上不重叠,则称它们是相容的,即如果lefti≥ rightj或者leftj≥ righti ,区间xi和xj是相容的。 区间选择问题是选择最大的相容区间集合。

最大相容区间问题可以用贪心算法解决,可以将集合S的n个区间段以右端点的非减序排列,每次总是选择具有最小右端点相容区间加入集合A中。直观上,按这种方法选择相容区间为未安排区间留下尽可能多的区间段。也就是说,该算法贪心选择的意义是使剩余的可安排区间段极大化,以便安排尽可能多的相容区间。

1)具有贪心选择性质        

(1)设A是区间选择问题一个最优解,,A中区间按右端点非减序排列;        

(2)设K是A中第一区间,若k=1,则A就是一个以贪心选择开始的最优解;若k>1,再设B=A-{k}U{1}.由于right1            

rightk且A中区间是相容的,故B中区间也是相容的。 又由于B中区间个数与A中区间个数相同,且A是最优的,故B也是最优的。也就是B是以贪心选择区间1开始的最优区间选择。              

结论:总存在一个以贪心选择开始的最优区间选择方案。

5.单源最短路径

给定一个带权有向图G=(V,E),其中每条边的权是非负实数,另外,还给定V中的一个顶点作为源点。现在要计算源点到其他各顶点的最短路径长度。这里路径长度是指路上各边权之和。这个问题通常称为单源最短路径问题。

例如:现有一张县城的城镇地图,图中的顶点为城镇,边代表两个城镇间的连通关系,边上的权为公路造价,县城所在的城镇为v0。由于该县的经济比较落后,因此公路建设只能从县城开始规划。规划的要求是所有可达县城的城镇必须建设一条通往县城的汽车线路,该线路工程的总造价必须最少。

由Dijkstra提出的一种按路径长度递增序产生各顶点最短路径的算法。Dijkstra算法描述如下,其中输入的带权有向图是G = (V , E),V = {v0,v1,v2,…,vn},顶点v0是源;E为图中边的集合;cost[i,j]为顶点i和j之间边的权值,当(i,j)E时,cost[i,j]的值为无穷大;distance [i]表示当前从源点到顶点i的最短路径长度。    

算法步骤:      

(1)初始时,S中仅含有源。设u是V的某一个顶点,把从源到u且中间只经过S中顶点的路称为从源到u的特殊路径,并用数组distance记录当前每个顶点所对应的最短特殊路径长度。

(2)每次从集合V-S中选取到源点v0路径长度最短的顶点w加入集合S,集合S中每加入一个新顶点w,都要修改顶点v0到集合T中剩余顶点的最短路径长度值,集合T中各顶点新的最短路径长度值为原来最短路径长度值与顶点w的最短路径长度加上w到该顶点的路径长度值中的较小值。    

(3)直到S包含了所有V中顶点,此时,distance就记录了从源到所有其他顶点之间的最短路径长度。

贪心策略:设置两个顶点集合V和S,集合S中存放己经找到最短路径的顶点,集合V中存放当前还未找到最短路径的顶点。设置顶点集合S并不断地作贪心选择来扩充这个集合。一个顶点属于集合S当且仅当从源到该顶点的最短路径长度已知。

贪心算法适用于最优化问题。它是通过做一系列的选择给出某一问题的最优解。对算法中的每一个决策点做出当时看起来最佳的选择。        

贪心算法的基本步骤:        

1.选择合适的贪心选择的标准;        

2.证明在此标准下该问题具有贪心选择性质;        

3.证明该问题具有最优子结构性质;        

4.根据贪心选择的标准,写出贪心选择的算法,求得最优解。                      

贪心算法通常包括排序过程,这是因为贪心选择的对象通常是一个数值递增或递减的有序关系,自顶向下计算。

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MySQL 大量数据插入优化

效率最好的方式是&#xff1a;批量插入 开启事务。 1、数据批量插入相比数据逐条插入的运行效率得到极大提升&#xff1b; ## 批量插入 INSERT INTO table (field1, field12,...) VALUES (valuea1, valuea2,...), (valueb1, valueb2,...),...;当数据逐条插入时&#xff0c;每…

OpenAI 或将推出多模态人工智能数字助理;研究发现部分 AI 系统已学会「说谎」丨 RTE 开发者日报 Vol.203

开发者朋友们大家好&#xff1a; 这里是 「RTE 开发者日报」 &#xff0c;每天和大家一起看新闻、聊八卦。我们的社区编辑团队会整理分享 RTE&#xff08;Real Time Engagement&#xff09; 领域内「有话题的 新闻 」、「有态度的 观点 」、「有意思的 数据 」、「有思考的 文…

LeetCode 700.二叉搜索树中的搜索

LeetCode 700.二叉搜索树中的搜索 1、题目 题目链接&#xff1a;700. 二叉搜索树中的搜索 给定二叉搜索树&#xff08;BST&#xff09;的根节点 root 和一个整数值 val。 你需要在 BST 中找到节点值等于 val 的节点。 返回以该节点为根的子树。 如果节点不存在&#xff0c;则…

【C语言/数据结构】栈:从概念到两种存储结构的实现

目录 一、栈的概念 二、栈的两种实现方式 1.顺序表实现栈 2.链表实现栈 三、栈的顺序存储结构及其实现 1.栈的声明 2.栈的初始化 3.栈的销毁 4.栈的压栈 5.栈的弹栈 6.栈的判空 7.返回栈顶元素 8.返回栈的长度 四、栈的链式存储结构及其实现 1.栈的声明 2.栈的…

设计模式Java实现-迭代器模式

✨这里是第七人格的博客✨小七&#xff0c;欢迎您的到来~✨ &#x1f345;系列专栏&#xff1a;设计模式&#x1f345; ✈️本篇内容: 迭代器模式✈️ &#x1f371; 本篇收录完整代码地址&#xff1a;https://gitee.com/diqirenge/design-pattern &#x1f371; 楔子 很久…

JavaScript数字(Number)个数学(Math)对象

目录 前言&#xff1a; Number&#xff08;数字&#xff09;对象 前言&#xff1a; nfinity(正负无穷大)&#xff1a; NaN&#xff08;非数字&#xff09;&#xff1a; Number的属性 Number的方法 构造函数 静态方法 实例方法 Math&#xff08;数学&#xff09;对象…

C#之partial关键字

在C#中&#xff0c;partial关键字用于声明一个类、结构体、接口或方法的分部定义。这意味着可以将一个类或其他类型的定义分成多个部分&#xff0c;这些部分可以在同一个命名空间或程序集中的多个源文件中进行定义。当编译器编译这些部分时&#xff0c;会将它们合并成一个单独的…

字符串函数(一):strcpy(拷贝),strcat(追加),strcmp(比较),及strncpy,strncat,strncmp

字符串函数 一.strcpy&#xff08;字符串拷贝&#xff09;1.函数使用2.模拟实现 二.strcat&#xff08;字符串追加&#xff09;1.函数使用2.模拟实现 三.strcmp&#xff08;字符串比较&#xff09;1.函数使用2.模拟实现 四.strncpy1.函数使用2.模拟实现 五.strncat1.函数使用2.…

Vulnhub-wp 获取vulnhub靶机wp搜索工具

项目地址:https://github.com/MartinxMax/vulnhub-wp 简介 搜索Vulnhub平台的解题文章,之过滤返回出正确可访问的页面 使用 $ python3 vulnhubwp.py 支持模糊搜索 [] Query: kiop 进入选项4,获取wp地址 [] Choice options: 4

draw.io 网页版二次开发(1):源码下载和环境搭建

目录 一 说明 二 源码地址以及下载 三 开发环境搭建 1. 前端工程地址 2. 配置开发环境 &#xff08;1&#xff09;安装 node.js &#xff08;2&#xff09;安装 serve 服务器 3. 运行 四 最后 一 说明 应公司项目要求&#xff0c;需要对draw.io进行二次开发&…

java spring boot动态数据库获得配置信息连接多数据源(数据库)

数据库 数据库文件和代码文件 https://download.csdn.net/download/qq_34631220/89304173 链接&#xff1a;https://pan.baidu.com/s/1xoh6xiSRx4nW_gKvR1QPjg 提取码&#xff1a;i7b7 –来自百度网盘超级会员V5的分享 文章位置 添加链接描述 说明&#xff1a;事务只能单库…

☀☀☀☀☀☀☀有关栈和队列应用的oj题讲解☼☼☼☼☼☼☼

准备好了么 目录&#xff1a; 一用两个队列实现栈&#xff1a; 1思路&#xff1a; 2画图理解&#xff1a; 3代码解答&#xff1a; 二用两个栈实现队列&#xff1a; 1思路&#xff1a; 2画图理解&#xff1a; 3代码解答&#xff1a; 三设计循环队列&#xff1a; 1思路…