前端算法 - 对贪心算法的理解

309 阅读6分钟

1. 什么是贪心算法

顾名思义,贪心算法总是做出当前看来是最好的选择,所做的每一个选择都是当前状态下局部的最好选择,即贪心选择。

这种启发式的策略并不总能奏效,但是在许多情况下的确能达到预期目的。所以贪心算法并不是对所有问题都能得到整体最优解,但是对范围相当广的许多问题它能产生整体最优解,其中活动安排问题的贪心算法就是一个例子。

2. 以活动安排问题为例讲解贪心算法

题目:活动安排问题

描述

给定n个活动,每个活动安排的时间为[aiai,bibi]。求最多可以选择多少个活动,满足选择的活动时间两两之间没有重合。

输入描述:

第一行输入一个整数n,表示可选活动个数。 接下来的n行,每行输入两个整数每行输入两个整数ai,bi,表示第i个活动的时间。

输出描述:

输出一行一个整数,表示最多可选择的活动数

示例1

输入:

3
1 4
1 3
3 5

输出:

2

题解

假设E={1,2,…,n}为所给的活动集合。

由于E中活动按结束时间的非减序排列,故活动1具有最早完成时间。

首先证明活动安排问题有一个最优解以贪心选择开始,即该最优解中包含活动1.设 A ⊆ E 是所给的活动安排问题的一个最优解,且中活动也按结束时间非减序排列,A中的第一个活动是活动k。若k=1,则A就是一个以贪心选择开始的最优解。 若k>1,则设B=A-(k)U{1)。由于f1<fk,且A中活动是相容的,故B中的活动是相容的。又由于B中活动个数与A中活动个数相同,且A是最优的,故B也是最优的。也就是说,B是以贪心选择活动1开始的最优活动安排。由此可见,总存在以贪心选择开始的最优活动安排方案。

进一步,在做了贪心选择,即选择了活动1后,原问题就简化为对E中所有与活动1相容的活动进行活动安排的子问题。即若A是原问题的最优解,则A‘=A-(1}是活动安排问题E’ = {i ∈ E:s >= fi}的最优解。

事实上,如果能找到E'的一个解B',它包含比A'更多的活动。则将活动1加人到B中将产生E的一个解B,它包含比A更多的活动。这与A的最优性矛盾。

因此,每一步所做的贪心选择都将问题简化为一个更小的与原问题具有相同形式的子问题。对贪心选择次数用数学归纳法即知,贪心算法 GreedySelector最终产生原问题的优解。

代码

// $先对所有活动根据结束时间进行排序$
// 如果结束时间一致则用开始时间排序
arranges.sort((arrange1, arrange2) => {
  if (arrange2.finish === arrange1.finish) {
    return arrange1.start - arrange2.start;
  }
  return arrange1.finish - arrange2.finish;
});

function greedySelector(n, arranges) {
  let count = 0;
  let curActivity = 0;
  for (let i = 0; i <= n - 1; i++) {
  // 每一步都寻找最优解即可
    if (arranges[i].start >= curActivity) {
      count++;
      curActivity = arranges[i].finish;
    }
  }
  return count;
}

3. 贪心算法的基本要素

1. 贪心选择性质

所谓贪心选择性质是指所求问题的整体最优解可以通过一系列局部最优的选择,即贪心选择来达到。 对于一个具体问题,要确定它是否具有贪心选择性质,必须证明每一步所做的贪心选择最终导致问题的整体最优解。

2. 最优子结构性质

当一个问题的最优解包含其子问题的最优解时,称此问题具有最优子结构性质。 在活动安排问题中,其最优子结构性质表现为,如果A是对于E的活动安排问题包含活动1的一个最优解,其相容剂和A'= A - {1} 是对于E=iE:s>=fiE’ = {i ∈ E:s >= fi}的活动安排问题的一个最优解。

4. 贪心算法和动态规划算法的区别(背包问题和0-1背包问题)

贪心算法和动态规划算法都要求问题具有最优子结构性质,这时两类算法的一个共同点. 对于这两种算法的不同点,可以用以下两个问题来做出比较:

0-1背包问题:

给定n种物品和一个背包。物品i的重量是wi,其价值为v,背包的容 c.问应如何选择装人背包中的物品,使得装人背包中物品的总价值最大?

在选择装人背包的物品时,对每种物品i只有两种选择,即装人背包或不装人背包。不将物品i装入背包多次,也不能只装入部分的物品i。

此问题的形式化描述是,给定c>0,w>0,v>0,1<i<n,要求找出一个n元0-1量(工,x2,…,x),x∈{0,1},1<i≤n,使得∑wx;<c,而且》vx;达到最大。

背包问题:

与0-1背包问题类似,所不同的是在选择物品i装人背包时,可以选择物品i一部分,而不一定要全部装人背包,1<i<n。

此问题的形式化描述是,给定c>0,w>0,v>o,1≤i<n,要求找出一个n元向量(3 Xz,…,x„),0≤x;≤1,1≤i≤n,使得∑w;x;≤c,而且∑v;x; 达到最大。

这两类问题都具有最优子结构性质。

对于0-1背包问题,设A是能够装入容量为c的背的具有最大价值的物品集合, Aj=A-{j}是n-1个物品1,2,…,j-1,j+1,…,n可人容量为c-w的背包的具有最大价值的物品集合。

对于背包问题,类似地,若它的一个最解包含物品j,则从该最优解中拿出所含的物品j的那部分重量w,剩余的将是n-1个原重品12…,j-1,j+1,…,n及重为w;-w的物品j中可装入容量为c-w的背包且具有最价值的物品。

虽然这两个问题极为相似,但背包问题可以用贪心算法求解,而0-1背包问题却不能用心算法求解。

用贪心算法解背包问题的基本步骤是,首先计算每种物品单位重量的价值v/u然后,依贪心选择策略,将尽可能多的单位重量价值最高的物品装入背包。若将这种物品全装入背包后,背包内的物品总重量未超过c则选择单位重量价值次高的物品并尽可能多地人背包。依此策略一直进行下去,直到背包装满为止。

对于0-1背包问题,贪心选择之所以不能得到最优解,是因为在这种情况下,它无法保证最终能将背包装满,部分闲置的背包空间使每千克背包空间的价值降低了。事实上,在考虑0-1背包问题时,应比较选择该物品和不选择该物品所导致的最终方案,然后再做出最好选择。由此可导出许多互相重叠的子问题。这正是该问题可用动态规划算法求解的另一重要特征。动态规划算法的确可以有效地解0-1背包问题。

4. 其他相关的贪心算法入门题

贪心算法相对于其他算法来说是比较简单的。

我们可以在leetcode上刷几道简单题来巩固一下。

分发饼干

数组拆分

柠檬水找零

参考:《计算机算法设计与分析》