数据结构与算法——贪心算法简介

Posted TD程序员

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据结构与算法——贪心算法简介相关的知识,希望对你有一定的参考价值。

贪心算法是一种算法范式,它遵循在每个阶段做出局部最优选择的问题解决启发式,希望找到全局最优。换句话说,贪心算法在每一步都选择最好的选项,而不考虑该选择对未来步骤的影响。

当一个问题可以分解成更小的子问题,并且每个子问题的解决方案可以组合起来解决整体问题时,贪心算法很有用。贪心算法可用于解决涉及在许多可能的解决方案中寻找最佳解决方案的优化问题。

可以使用贪心算法解决的问题的一个典型例子是“硬币找零”问题。问题是用尽可能少的硬币找零给定的钱数。例如,如果金额为 25 美分,可用硬币为 1 美分、5 美分和 10 美分,那么贪心算法将在每一步选择最大的硬币。首先选择一个 10 美分的硬币,然后是另一个 10 美分的硬币,最后是一个 5 美分的硬币,总共三个硬币。

然而,贪心算法不一定总能找到最优解。例如,如果可用的硬币是 1 美分、3 美分和 4 美分,金额是 6 美分,贪心算法会选择一个 4 美分的硬币和两个 1 美分的硬币,而最优解是用两个3 美分的硬币。

因此,证明贪心算法的正确性并了解其局限性很重要。贪心算法可以应用于许多上下文,包括调度、图论和动态规划。

贪心算法被定义为一种解决优化问题的方法,无论最终结果如何,它都会做出能带来最明显和最直接利益的决策。它适用于最小化最大化导致所需解决方案的情况。

贪心算法的特点

对于使用贪婪方法解决的问题,它必须遵循几个主要特征:

  • 有一个有序的资源列表(利润、成本、价值等) 
  • 获取所有资源中的最大值(最大利润、最大值等)。 
  • 例如,在分数背包问题中,根据可用容量首先取最大值/重量。 

贪心算法的使用:-

贪心算法是一种用于优化问题的方法,其目标是在每个阶段做出局部最优选择,以期找到全局最优。之所以称为“贪心”,是因为它试图通过在每一步做出最佳选择来找到最佳解决方案,而不考虑未来步骤或当前决策的后果。

贪心算法的一些常见用例包括:

调度和资源分配:贪心算法可用于高效地调度作业或分配资源。

最小生成树:贪心算法可以用来寻找一个图的最小生成树,它是连接所有顶点的总边权最小的子图。

硬币找零问题:贪心算法可用于以最少数量的硬币找零给定数量,方法是始终选择价值最高且小于要找零的剩余数量的硬币。

霍夫曼编码:贪婪算法可用于生成用于数据压缩的无前缀代码,通过以考虑每个字符的频率的方式构建二叉树。

需要注意的是,并非所有优化问题都可以通过贪心算法解决,并且在某些情况下贪心算法可能会导致次优解决方案。然而,在许多情况下,贪心算法提供了对最优解的良好近似,是快速有效地解决优化问题的有用工具。

所有贪心算法都遵循一个基本结构: 

  1. 声明一个空结果 = 0。
  2. 我们做一个贪婪的选择来选择,如果选择可行就把它加到最终结果中。
  3. 返回结果。

为什么选择贪心法?

贪婪的方法有一些权衡,这可能使其适合优化。一个突出的原因是立即获得最可行的解决方案。在活动选择问题(下面解释)中,如果在完成当前活动之前可以完成更多活动,则可以在同一时间内执行这些活动。另一个原因是根据条件递归地划分问题,而不需要组合所有的解决方案。在活动选择问题中,“递归划分”步骤是通过仅扫描一次项目列表并考虑某些活动来实现的。

贪心算法示例:

一些具有最优子结构特性并且可以使用贪心方法解决的著名问题是 –

1)作业排序问题

首先贪婪地选择利润最大的工作,按照利润的递减顺序对工作进行排序。这将有助于最大化总利润,因为为每个时间段选择利润最大的工作最终将使总利润最大化

2) Prim 求最小生成树的算法

它以一个空的生成树开始。这个想法是维护两组顶点。第一组包含已包含在 MST 中的顶点,另一组包含尚未包含的顶点。在每一步,它都会考虑连接这两个集合的所有边,并从这些边中选择权重最小的边。选择边缘后,它将边缘的另一个端点移动到包含 MST 的集合。 

贪心算法是如何工作的?

如果在没有进行彻底检查的情况下选择应用贪心法,那么使用它的决定可能会有些困难,有时甚至会导致失败。在某些情况下,采用局部最佳选择可能会导致失去全局最优解。 

例如: 

  • 贪心法失败的一个例子是在给定图中找到节点的最大加权路径。

带加权顶点的图

  • 在上图中,从根节点10开始,如果我们贪婪地选择下一个节点以获得最大权重路径,则下一个选择的节点将为5,这将使总和为15,并且路径将结束,因为没有5的子节点但是路径10 -> 5不是最大权重路径。

贪心方法失败

  • 为了找到权重最大的路径,必须计算所有可能的路径总和,并且必须比较它们的路径总和以获得所需的结果,可以看出上图中权重最大的路径是 10 -> 1 -> 30,这给出了路径总和41 . 

正确的方法

  • 在这种情况下,贪婪方法将不起作用,而是必须考虑 从叶节点的完整路径以获得正确答案,即权重最大的路径,这可以通过递归检查所有路径并计算它们的权重来实现。

因此要使用贪心算法,问题不能包含重叠的子问题。

贪心算法与动态规划

贪心算法和动态规划是解决复杂规划问题的两种最广泛使用的算法范式,而贪婪方法适用于局部最优选择导致全局最优解的问题动态规划适用于具有重叠子问题结构的问题,其中子问题的答案是需要解决其他几个子问题。详细区别见下表: 

特征

贪心算法动态规则

可行性 

在贪心算法中,我们会做出目前看来最好的选择,希望它会导致全局最优解。在动态规划中,我们在考虑当前问题和先前解决的子问题的解决方案的每一步做出决策,以计算最优解。

最优性

在贪心法中,有时无法保证获得最优解。保证动态规划将生成最佳解决方案,因为它通常会考虑所有可能的情况,然后选择最佳解决方案。

递归

贪心法遵循在每个阶段做出局部最优选择的问题解决启发式。动态规划是一种算法技术,通常基于使用一些先前计算的状态的循环公式。

记忆化                                   

它在记忆方面更有效率,因为它从不回顾或修改以前的选择它需要用于记忆的动态编程表,并且增加了它的内存复杂性。

    时间复杂度                    

贪心方法通常更快。例如,Dijkstra 的最短路径算法需要O(ELogV + VLogV)时间。动态规划通常较慢。例如,Bellman Ford 算法需要O(VE)时间。

时尚

贪婪方法通过以串行前向方式做出选择来计算其解决方案,从不回头或修改以前的选择。动态规划通过从较小的最优子​​解中综合它们来自下而上或自上而下地计算其解。

算法讲解|贪心算法的理解与分析




贪心算法




算法讲解|贪心算法的理解与分析
算法讲解|贪心算法的理解与分析

 Part 1 

算法讲解|贪心算法的理解与分析
算法讲解|贪心算法的理解与分析


贪心算法简介

      贪心算法是从某一个初始状态出发,每次通过选取局部最优解向目标前进,并最终期望取得整体最优解的一种算法。由这个定义可知,贪心选择标准就是选择“当前最好”的决策,贪心算法根据这个标准进行决策,将原问题变成一个相似但规模更小的子问题,而后每一步选出来的一定是原问题整体最优解的一部分。

      如果一个问题贪心后只剩下一个子问题且有最优子结构,那么该问题就可以使用贪心算法。当一个问题的整体最优解包含其子问题的最优解时,我们称次问题具有最优子结构性质。







算法讲解|贪心算法的理解与分析
算法讲解|贪心算法的理解与分析



算法讲解|贪心算法的理解与分析
算法讲解|贪心算法的理解与分析

 Part 2 

算法讲解|贪心算法的理解与分析
算法讲解|贪心算法的理解与分析


解题一般步骤

1、      设计数据找规律;

2、      进行贪心猜想;

3、      正确性证明(包括列举反例和严格的             数学证明);

4、      程序实现。





算法讲解|贪心算法的理解与分析



算法讲解|贪心算法的理解与分析
算法讲解|贪心算法的理解与分析

 Part 3 

算法讲解|贪心算法的理解与分析
算法讲解|贪心算法的理解与分析



例题(洛谷P1080国王游戏):

题目描述

      恰逢 H国国庆,国王邀请n 位大臣来玩一个有奖游戏。首先,他让每个大臣在左、右手上面分别写下一个整数,国王自己也在左、右手上各写一个整数。然后,让这 n 位大臣排成一排,国王站在队伍的最前面。排好队后,所有的大臣都会获得国王奖赏的若干金币,每位大臣获得的金币数分别是:排在该大臣前面的所有人的左手上的数的乘积除以他自己右手上的数,然后向下取整得到的结果。

      国王不希望某一个大臣获得特别多的奖赏,所以他想请你帮他重新安排一下队伍的顺序,使得获得奖赏最多的大臣,所获奖赏尽可能的少。注意,国王的位置始终在队伍的最前面。


输入格式

第一行包含一个整数n,表示大臣的人数。

第二行包含两个整数 a和 b,之间用一个空格隔开,分别表示国王左手和右手上的整数。

接下来 n行,每行包含两个整数a 和 b,之间用一个空格隔开,分别表示每个大臣左手和右手上的整数。

输出格式

一个整数,表示重新排列后的队伍中获奖赏最多的大臣所获得的金币数。


输入输出样例

算法讲解|贪心算法的理解与分析



说明/提示

【输入输出样例说明】


按1、2、3 这样排列队伍,获得奖赏最多的大臣所获得金币数为 2;


按 1、3、2 这样排列队伍,获得奖赏最多的大臣所获得金币数为2;


按 2、1、3 这样排列队伍,获得奖赏最多的大臣所获得金币数为 2;


按2、3、1这样排列队伍,获得奖赏最多的大臣所获得金币数为9;


按 3、1、2这样排列队伍,获得奖赏最多的大臣所获得金币数为 2;


按3、2、1 这样排列队伍,获得奖赏最多的大臣所获得金币数为9。


因此,奖赏最多的大臣最少获得 2个金币,答案输出 2。


【数据范围】


对于 20%的数据,有 1≤ n≤ 10,0 < a,b < 8;


对于 40%的数据,有1≤ n≤20,0 < a,b < 8;


对于 60%的数据,有 1≤ n≤100;


对于 60%的数据,保证答案不超过 10^9;


对于 100%的数据,有 1 ≤ n ≤1,000,0 < a,b < 10000。





算法讲解|贪心算法的理解与分析





算法讲解|贪心算法的理解与分析




算法讲解|贪心算法的理解与分析
算法讲解|贪心算法的理解与分析

 Part 4 

算法讲解|贪心算法的理解与分析


解题思路

      不妨先讨论相邻的二元组。由题意可知,相邻两个大臣交换位置不会对前面和后面的其他人的金币数造成影响。也就是说相邻两人位置交换只会对这两个人产生影响我们以此为切入点,分析调换相邻的两个人对答案的影响。


      设这两个人位置分别为i和i+1,左手数字为a[i]和a[i+1],右手数字为b[i]和b[i+1],两人的金币数为w[i]和w[i+1]。记  P[i]=a[1]*a[2]*a[3]*...*a[i]。


    未调换顺序时,k1=w[i]=P[i-1]/b[i]; k2=w[i+1]=P[i-1]*a[i]/b[i+1];则

ans1=max(k1,k2)


     调换顺序后k3=P[i-1]/b[i+1]; k4=P[i-1]*a[i+1]/b[i]; 则ans2=max(k3,k4)


     显然有k1<k4,k3<k2;如果ans1<ans2,那么必有k2<k4。化简可得a[i]*b[i]<a[i+1]*b[i+1];


      所以,为了ans取到最小值,我们需要将a[i]*b[i]较小的放在前面那么我们以a[i]*b[i]为关键字排序即可。同时,统计答案时一定不要忘了写高精度。






小结


贪心算法的核心问题是选择能产生问题最优解的最优度量标准,即具体的贪心策略。


特点是快,在运行过程中无回溯过程,每一步都是当前的最佳选择






·END·


文案:张天泽

排版:郭泓业






以上是关于数据结构与算法——贪心算法简介的主要内容,如果未能解决你的问题,请参考以下文章

算法篇贪心算法介绍——基于Python实现的爬山算法

算法进阶——贪心与动态规划

背包问题(贪心算法)

贪心算法,递归算法,动态规划算法比较与总结

分治、贪心五大算法

贪心算法结构与算法分析:C语言描述