五大算法思想(二)贪心算法及常见例子 |
您所在的位置:网站首页 › 常见的解决问题的策略有哪四个 › 五大算法思想(二)贪心算法及常见例子 |
文章目录
一、理论基础1.1 适用场景1.2 使用步骤1.3 算法缺陷1.4 经典例子
二、常见例子2.1 活动选择问题2.2 钱币找零问题2.3 背包问题2.4 小船过河问题2.5 区间覆盖问题
一、理论基础
贪心算法,指在对问题求解时,总是做出在当前看来是最好的选择。也就是说不从整体最优上加以考虑,算法得到的结果是在某种意义上的局部最优解。 1.1 适用场景可以用贪心算法解决的问题一般有如下特征: 1>贪心选择性质。一个问题的整体最优解可通过一系列局部的最优解的选择达到,并且每次的选择可以依赖以前作出的选择,但不依赖于后面要作出的选择。这就是贪心选择性质。对于一个具体问题,要确定它是否具有贪心选择性质,必须证明每一步所作的贪心选择最终导致问题的整体最优解。 2>最优子结构性质。当一个问题的最优解包含其子问题的最优解时,称此问题具有最优子结构性质。问题的最优子结构性质是该问题可用贪心法求解的关键所在。 1.2 使用步骤使用贪心算法的基本步骤: 1>建立数学模型来描述问题 。 2>把求解的问题分成若干个子问题 。 3>对每个子问题求解,得到子问题的局部最优解。 4>把子问题的解局部最优解合成原来解问题的一个解。 以上都是官方说法。依个人的理解,在贪心选择性质里,其实隐含着一个条件,就是:在使用贪心算法时,待选择的因素是有序的。在该前提下,才可能做出对当前最优的选择。用公式来表示的话,大致如下: while(约束条件成立){ 选择当前最优解,记录并累积到最后的解中 if(当前最优解使用完毕) 当前最优解 = 当前次优解 } 1.3 算法缺陷贪心算法也存在如下缺陷: 1>不能保证解是最佳的。因为贪心算法总是求的局部最优解,并未从整体考虑整体最优解。 2>贪心算法只能确定某些问题的可行性范围。 缺陷1和缺陷2表示的意思是类似的,就是贪心算法是有局限性的。此处以一个例子来说明:比如钱币找零问题,假如有15元,需要用11、5、1三种面额的钱币来表示,如果用贪心算法来选择的话,会从11开始选择,这样就会选出11+1+1+1+1的方案,用5张钱币来表示,但实际上,用5+5+5的方案,使用的钱币更少,此时贪心方案就选不出全局最优解。 3>贪心算法一般用来解决求最大或最小解。 该缺陷也好理解,贪心算法是以某种策略选择一个值,而不是遍历出所有解,要遍历出某个问题所有解的话,常常用的是回溯法。 1.4 经典例子常见例子如下: 1>活动选择问题 2>钱币找零问题 3>再论背包问题 4>小船过河问题 5>区间覆盖问题 接下来将对这些例子进行实现,来探究贪心算法思想的具体使用方法。 二、常见例子 2.1 活动选择问题该问题是一般可以用贪心算法和动态规划两种方法解决,此篇文章主要介绍贪心算法实现方式。该类问题的形式一般如下:在某个地方需要举办不同的活动,每个活动要花费不同时间(起止时间不同),问怎样安排,可以安排尽量多的活动?此问题之所以可以用贪心算法解决,是因为下个活动的选择可以只取决于上个活动结束时间,而不必从全局考虑。 我们假设有个已经按结束时间排好序的活动列表,开始时间和结束时间如下: 活动1234567891011开始时间130535688212结束时间4567891011121314在解决该问题时,需要先对每个活动按结束时间进行排序。也许有人会问,为什么要对活动的结束时间,而不是开始时间进行排序,这样做的原因是当前活动的结束时间会影响到下一个活动的选择,而当前活动的开始时间不会影响下一个活动的选择。 整个的解题思路,大致如下:先把活动按结束时间排序,然后默认选取第一个活动,用变量来标识当前活动的结束日期,作为选择下一个活动的日期的判断标准,具体标准为:下一个选取的活动的开始时间要>=当前活动的结束时间。然后重复此过程,直到遍历完整个数组。 活动选择问题的迭代解法,示例代码如下: private static List activityChoice(int[] startTime, int[] endTime){ List list = new ArrayList(); /*因为活动已经按结束时间进行过升序排列,所以要默认选中第一个活动*/ list.add(0); /*当前开始活动的结束时间*/ int curTime = endTime[0]; for(int i=1;i=之前选择的活动的结束时间,则选取该活动,更新当前时间*/ if (startTime[i]>=curTime) { list.add(i); curTime = endTime[i]; } } return list; }活动选择问题的递归解法,示例代码如下: static List acivitySelector(int[] startTime, int[] endTime, int i, int n,List list) { /*因为活动已经按结束时间进行过升序排列,所以要默认选中第一个活动,+1是因为数组下标从0开始,此处是数组下标+1*/ if(i==0){ list.add(i+1); } int j=i+1; /*此处的循环是过滤掉不符合要求的活动*/ while (jstartTime[j]){ j++; } //找到一个满足f[i]需要将物品按照单位重量价值进行排序。 2>将尽可能多的单位重量价值最高的物品装入背包,若最大单位重量价值的物品全部装入背包后,背包还有多余容量,则选择单位重量价值次高的并尽可能多地装入背包。 3>如果最后一件物品无法全部装入,则计算可以装入的比例,然后按比例装入。 此处用迭代方式实现,示例代码如下: package Greedy; /* * 给定n种物品,1个背包,背包容量为c,每个物品i的价值为vi,重量为wi,如何选择装入物品能使背包的总价值最大? */ public class Package { public static void main(String[] args) { /*每件物品的重量*/ int[] weights={10,30,20}; /*每件物品的价值*/ int[] values={60,120,100}; /*背包的容量限制*/ int packNum=50; maxPackageValue(weights,values,packNum); } private static void maxPackageValue(int[] weights,int[] values,int packNum){ /*单位重量价值比*/ double[] univalent=new double[values.length]; /*最后背包中存储的总价值*/ double allValue=0.0; for(int i=0;i最快的和最慢的过河,然后最快的将船划回来,最快的和次慢的过河,然后最快的将船划回来。过程如下:![]() 测试结果: 所花时间为:20 2.5 区间覆盖问题此问题的描述是:给定一个长度为m的区间,再给出n条线段的起点和终点(闭区间),求最少使用多少条线段可以将整个区间完全覆盖。要解此题的完整步骤如下: 1>在所有待选择的区间里,剔除起点和终点在所求范围之外的区间。 2>将所有区间按起点进行排序。 3>默认选中第一个点,然后在挑选点的过程中,需要遵循以下原则:1、新区间的起点要小于等于当前区间的终点;2、新区间的终点要大于当前区间的起点。 4>循环重复步骤3,直到当前区间的终点值>=预期的终点值,结束寻找区间的过程。 此处省略步骤1,重点关注后3步,示例代码如下: package Greedy; /* * 数轴上有n个闭区间[ai, bi],选择尽量少的区间覆盖一条指定线段[s, t]。 */ public class CoverSection { public static void main(String[] args) { int[][] ections={{1,4},{2,4},{2,6},{3,5},{3,6},{3,7},{6,8}}; /*此处假设的是求[1,8区间,所需的最少区间数,因为起点是从1开始的, * 所以1不作为参数传入,只将终点值8作为参数传入即可]*/ int count =overSection(ections,8); System.out.println("最少需要"+count+"个区间"); } private static int overSection(int[][] ections,int length){ /*默认选中第一个*/ int count=1; System.out.println(ections[0][0]+","+ections[0][1]); /*初始化终点,默认为第一个区间的终点*/ int end=ections[0][1]; boolean stopFlag=false; for(int i=1; i=length,代表可以结束循环*/ if(end>=length){ stopFlag=true; } } } } /*结束循环*/ if(stopFlag) break; } return count; } }测试结果: 1,4 3,7 6,8 最少需要3个区间 |
今日新闻 |
推荐新闻 |
CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3 |