贪心算法综述

贪心算法综述

  • 引言
  • 贪心算法的基本概念
    • 贪心算法的定义
    • 贪心算法的选择性质
    • 贪心算法的流程
    • 贪心算法的实现过程
    • 贪心算法与动态规划
  • 贪心算法模型
    • 背包问题
    • 单源最短路径问题
  • 贪心算法的问题
  • 贪心算法的优缺点
  • 经典贪心算法
    • 普里姆算法
      • 普里姆算法概览
      • 算法简单描述
    • Kruskal算法
      • Kruskal算法概览
      • 算法简单描述
    • Dijkstra算法
      • Dijkstra算法思想
      • Dijkstra算法步骤

引言

近年来的信息学竞赛中,经常需要求一个问题的可行解和最优解,这就是所谓的最优化 问题。贪心法是求解这类问题的一种常用算法。在众多的算法中,贪心法可以算的上是最接近人们日常思维的一种算法,他在各级各类信息学竞赛、尤其在一些数据规模很大的问题求解中发挥着越来越重要的作用。

贪心算法的基本概念

贪心算法的定义

贪心算法(也称贪婪算法),是一种在进行每一步选择中都采取当前窗台下最好或者最优的选择,即最有利的选择,从而希望得到的结果是最好或者最优的算法。贪心算法在求解最优子结构的问题中尤为高效。最优子结构的意思是局部最优解能决定全局最优解。简单的说,问题能够分成子问题来解决,子问题的最优解能推导到最终问题的最优解。因此,贪心算法不管之前的选择,也不管以后的选择,只与当前状态有关,只对当前的子问题选择最优解。
例如在旅行推销员问题(最短路径问题,TSP问题)中,给定一系列城市和每对城市之间的距离,求解访问每一座城市一次并回到起始城市的最短回路。从贪心算法的角度来看,旅行推销员每一步要选择当前条件下的最优解,每次都要选择最近的城市。
贪心算法与动态规划问题不同,贪心算法对每个子问题的解决方案都做出选择,没有回溯操作。动态规划则会保存以前的运算结果,并根据以前的结果对当前进行选择,可以进行回溯。
贪心法可以解决一些最优化问题,如:求图中的最小生成树、求哈夫曼编码……对于其他问题,贪心法一般不能得到我们所要求的答案。一旦一个问题可以通过贪心法来解决,那么贪心法一般是解决这个问题的最好办法。由于贪心法的高效性以及其所求得的答案比较接近最优结果,贪心法也可以用作辅助算法或者直接解决一些要求结果不特别精确的问题。

贪心算法的选择性质

贪心的选择性质是指所求问题的整体最优解可以通过一系列局部最优的选择,即贪心选择来达到。这是贪心算法可行的第一个基本要素。贪心算法则通常以自顶向下的方式进行,以迭代的方式做出相继的贪心选择,每作一次贪心选择就将所求问题简化为规模更小的子问题。对于一个具体问题,要确定它是否具有贪心选择性质,必须证明每一步所作的贪心选择最终导致问题的整体最优解。

贪心算法的流程

  1. 创建数学模型来描述问题。
  2. 把求解的问题分成若干个子问题。
  3. 对每一子问题求解,得到子问题的局部最优解。
  4. 把子问题的解局部最优解合成原来解问题的一个解。

贪心算法的实现过程

  1. 从问题的某一初始解出发;
  2. while 能朝给定总目标前进一步
  3. do求出可行解的一个解元素;
  4. 最后,由所有解元素组合成问题的一个可行解。

贪心算法与动态规划

最优解问题大部分都可以拆分成一个个的子问题,把解空间的遍历视作对子问题树的遍历,则以某种形式对树整个的遍历一遍就可以求出最优解,大部分情况下这是不可行的。
贪心算法和动态规划本质上是对子问题树的一种修剪,两种算法要求问题都具有的一个性质就是子问题最优性(组成最优解的每一个子问题的解,对于这个子问题本身肯定也是最优的)。
动态规划方法代表了这一类问题的一般解法,我们自底向上构造子问题的解,对每一个子树的根,求出下面每一个叶子的值,并且以其中的最优值作为自身的值,其它的值舍弃。而贪心算法是动态规划方法的一个特例,可以证明每一个子树的根的值不取决于下面叶子的值,而只取决于当前问题的状况。换句话说,不需要知道一个节点所有子树的情况,就可以求出这个节点的值。由于贪心算法的这个特性,它对解空间树的遍历不需要自底向上,而只需要自根开始,选择最优的路,一直走到底就可以了。

贪心算法模型

背包问题

背包问题就是有若干物品,每个物品有自己的价值υ1, υ2, υ3 … υn和重量ω1,  ω2, ω3 … ωn。背包有总重量∁。问题就是怎样将背包装的最大价值。背包问题也分很多种,贪心算法解决的是物品可以拆分的背包问题(就是物品可以分成几份装入)。这个问题用贪心还是比较好解决的。
贪心选择是指所求问题的整体最优解可以通过一系列局部最优的选择,即贪心选择来达到。这是贪心算法可行的第一个基本要素,也是贪心算法与动态规划算法的主要区别。此问题就是将每次的放入看成每一步,要想解决问题,就是将每一步都放入最优解。也就是说,每一次的放入都要放入最佳的选择。讲到这里,就要说一说最佳的选择,每一次的放入的最佳的选择就是每次放入的物品都是剩余的物品中价值最大且质量最小的,这里就要引入一个物品的属性,物品的权重值。物品的权重值就是指物品的价值除以物品的质量。所以,本问题的每一次的最佳选择就是每次都选出权重值最大的物品。

单源最短路径问题

单元最短路径:给定顶点到其它任一顶点的最短路径。
一个有向图G,它的每条边都有一个非负的权值c[i,j],“路径长度”就是所经过的所有边的权值之和。对于源点需要找出从源点出发到达其他所有结点的最短路径。
E.Dijkstm发明的贪婪算法可以解决最短路径问题。算法的主要思想是:分步求出最短 路径,每一步产生一个到达新目的顶点的最短路径。下一步所能达到的目的顶点通过如下贪 婪准则选取:在未产生最短路径的顶点中,选择路径最短的目的顶点。

设置顶点集合S并不断作贪心选择来扩充这个集合。当且仅当顶点到该顶点的最短路径 已知时该顶点属于集合S。初始时S中只含源。
设u为G中一顶点,我们把从源点到u且中间仅经过集合S中的顶点的路称为从源到11 特殊路径,并把这个特殊路径记录下来(例如程序中的dist[i,j])。
每次从V-S选出具有最短特殊路径长度的顶点u,将u添加到S中,同时对特殊路径长度进行必要的修改。一旦V=S,就得到从源到艽他所有顶点的最短路径,也就得到问题的解。

贪心算法的问题

贪心算法的优缺点

• 优点:一个正确的贪心算法拥有很多优点,比如思维复杂度低、代码量小、运行效率高、 空间复杂度低等,是信息学竞赛中的一个有力武器,受到参赛选手们的青睐。
• 缺点:贪心法的缺点集中表现在其“非完美性”。通常很难找到一个简单可行并 且保证正确的贪心思路,即使我们找到一个看上去很正确的贪心思路,也需要严格的正确性证明。这往往给我们直接使用贪心算法带来了巨大的困难。往往不能保证求得的最后解是最佳的;同时贪心算法也不能用来求最大或最小解的问题,只能求满足某些约束条件的可行解的范围。

经典贪心算法

普里姆算法

普里姆算法概览

普里姆算法(Prim's algorithm),图论中的一种算法,可在加权连通图里搜索最小生成树。意即由此算法搜索到的边子集所构成的树中,不但包括了连通图里的所有顶点,且其所有边的权值之和亦为最小。该算法于1930年由捷克数学家沃伊捷赫·亚尔尼克发现;并在1957年由美国计算机科学家罗伯特·普里姆独立发现;1959年,艾兹格·迪科斯彻再次发现了该算法。因此,在某些场合,普里姆算法又被称为DJP算法、亚尔尼克算法或普里姆-亚尔尼克算法。

算法简单描述

  1. 输入:一个加权连通图,其中顶点集合为V,边集合为E;
  2. 初始化:Vnew = {x},其中x为集合V中的任一节点(起始点),Enew = {},为空;
  3. 重复下列操作,直到Vnew = V:
    a.在集合E中选取权值最小的边,其中u为集合Vnew中的元素,而v不在Vnew集合当中,并且v∈V(如果存在有多条满足前述条件即具有相同权值的边,则可任意选取其中之一);
    b.将v加入集合Vnew中,将边加入集合Enew中;
  4. 输出:使用集合Vnew和Enew来描述所得到的最小生成树。

Kruskal算法

Kruskal算法概览

Kruskal算法是一种用来寻找最小生成树的算法,由Joseph Kruskal在1956年发表。用来解决同样问题的还有Prim算法和Boruvka算法等。三种算法都是贪婪算法的应用。和Boruvka算法不同的地方是,Kruskal算法在图中存在相同权值的边时也有效。

算法简单描述

  1. 记Graph中有v个顶点,e个边。
  2. 新建图Graph new,Graph new中拥有原图中相同的e个顶点,但没有边。
  3. 将原图Graph中所有e个边按权值从小到大排序。
  4. 循环:从权值最小的边开始遍历每条边直至图Graph中所有的节点都在同一个连通分量中。
    if 这条边连接的两个节点于图Graphnew中不在同一个连通分量中
    then添加这条边到图Graphnew中

Dijkstra算法

迪杰斯特拉算法(Dijkstra)是由荷兰计算机科学家狄克斯特拉于1959 年提出的,因此又叫狄克斯特拉算法。是从一个顶点到其余各顶点的最短路径算法,解决的是有权图中最短路径问题。迪杰斯特拉算法主要特点是从起始点开始,采用贪心算法的策略,每次遍历到始点距离最近且未访问过的顶点的邻接节点,直到扩展到终点为止。

Dijkstra算法思想

设G=(V,E)是一个带权有向图,把图中顶点集合V分成两组,第一组为已求出最短路径的顶点集合(用S表示,初始时S中只有一个源点,以后每求得一条最短路径 , 就将加入到集合S中,直到全部顶点都加入到S中,算法就结束了),第二组为其余未确定最短路径的顶点集合(用U表示),按最短路径长度的递增次序依次把第二组的顶点加入S中。在加入的过程中,总保持从源点v到S中各顶点的最短路径长度不大于从源点v到U中任何顶点的最短路径长度。此外,每个顶点对应一个距离,S中的顶点的距离就是从v到此顶点的最短路径长度,U中的顶点的距离,是从v到此顶点只包括S中的顶点为中间顶点的当前最短路径长度。

Dijkstra算法步骤

  1. 初始时,S只包含源点,即S={v},v的距离为0。U包含除v外的其他顶点,即:U={其余顶点},若v与U中顶点u有边,则正常有权值,若u不是v的出边邻接点,则权值为∞。
  2. 从U中选取一个距离v最小的顶点k,把k,加入S中(该选定的距离就是v到k的最短路径长度)
  3. 以k为新考虑的中间点,修改U中各顶点的距离;若从源点v到顶点u的距离(经过顶点k)比原来距离(不经过顶点k)短,则修改顶点u的距离值,修改后的距离值的顶点k的距离加上边上的权。
  4. 重复步骤b和c直到所有顶点都包含在S中。
    总结
    至此,我们已经对贪心算法有了比较深刻了认识。贪心算法不仅容易理解而且应用面极广。它可以和许多算法与数据结构融合形成新的算法,相信它一定在将来还会再构造出其他很巧的有趣的算法。贪心在竞赛中应用广泛。它设计与实现难度不大,但通常对正确性无法保证。因此需要对题目条件细致分析,并小心求证贪心正确性。对于经典模型需要熟练运用与转化,了解经典贪心算法证明有助于更好理解,熟练使用几种证明方法,必要时可以使用暴力程序验证。

你可能感兴趣的:(数据结构,数据结构,贪心算法,算法)