算法之贪心算法

一、概念

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

贪心算法不是对所有问题都能得到整体最优解,关键是贪心策略的选择,选的贪心策略必须具备无后效性,即某个状态以前的过程不会影响以后的状态,只与当前状态有关。

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

虽然叫贪心算法,其实是一种策略,可以用一个简单的例子说明一下这种算法,比如在所有面值钞票里,选择10张,如何让选出来的钞票最后总额最大。从贪心算法去分析,想要结果最优,即总额最大,那就要局部最优,即单张面值最大,那么就是10张每张都拿当前可选的最大的面额,就能达到整体总额最大。

二、实例及其解决方案

1、栗子--钱币找零

1.1 题目

面对一个给定的以及一定数量的纸币和硬币,给出一个总数,问如何使用这些钞票硬币,使得总数恰好等于所需的总数,并且要求使用的钞票硬币数量最少。

1.2 思路

贪心策略:选择一个钞票/硬币时,选择最大的金额,并且保证所选金额小于所需总数。并继续选择下一个金额最大的,直到所需总数为0为止。

比如,当我们需要找零11元,零钱面值只有1元、2元、5元的时,我们可以采取以下策略:

选择最大面额的硬币5元,此时剩余需要找零6元;

再次选择最大的面额的纸币5元,还需找零1元;

选择最小的纸币1元,已经满足需要找零总数,张数3张;

此时,所用硬币数最少,共使用3个钱币。

此时,贪心算法得到了最优解,但是并不是所有情况下的结果就一定是最优解。

1.3 算法实现
/**
* {acount} number 需要找零总数
* {list} number[] 可找零钱币面额可选范围
* */ 
function greedy1(acount, list) {
                let s = 0;
                const saveD = [];
                const arr = list;
                arr.sort((a,b) => a-b);
                let n = list.length - 1;
                while(s < acount && n > -1) {
                    if (s+arr[n] < acount) {
                        s = s+arr[n];
                        saveD.push(arr[n]);
                    } else if (s+arr[n] == acount) {
                        s = s+arr[n];
                        saveD.push(arr[n]);
                        break;
                    } else {
                        n = n - 1;
                    }
                }
                return saveD;
            }
console.log(greedy1(41,[1,5,10,20]));

2、栗子--排队打水问题

2.1 题目

有n个人排队到r个水龙头前去打水,他们装满水桶的时间分别是t1,t2,t3,...,tn,打水时间均为整数且各不相等,应该如何安排他们的打水时间才能使他们花费的总时间最少?

每个人的打水时间=排队时间+装满水桶的时间(假设排队切换等其他过程均不消耗时间)

2.2 思路

使用贪心算法,目标是所有人打水的总时间最少,那每个人打水的时间要当前最少,装满水桶的时间是不变的,那么就需要当前选择的水龙头排队的时间是最少的,那么如何保证当前这个人排队时间是当前最短的?

首先,第一轮打水的人要是装满水桶时间最少的那批人,才能保证后面的人等待的时间最短,所以要对所有人的装满水桶时间进行排序,得到一个数组。然后从这个数组里,直接安排r个人去进行第一轮打水,此时第二批人已经按照下面的站位进行等待,这样能保证每个人的等待时间是对他而言最短的,第r+1个人的总打水时间就是第一个人的装满水桶时间加上第r+1个人装满水桶的时间,以此类推,得到下面这张图。最后总的打水时间为


2.3 算法实现
/**
* {tapNum} number 水龙头个数
* {peopleTime} number[] 每个人打水所需时间组成的数组
* */ 
function greedy(tapNum, peopleTime) {
  let s = 0;
  const arr = peopleTime;
  arr.sort((a,b) => a-b);
    for (let i = 0; i < peopleTime.length; i++) {
      if (i >= tapNum) {
        arr[i] = arr[i] + arr[i-tapNum];
      }
      s = s + arr[i];
    }
  return s;
}
console.log(greedy(3,[5,2,6,1,8,3,6]));

三、贪心算法的特点

1、贪心算法的特点

优点:

1、简单易实现:贪心算法在每一步只考虑当前最优选择,不需要预测未来状态,因此实现起来较为简单。

2、时间复杂度低,因为只需要对当前情况进行一次计算,在不考虑排序的前提下,时间复杂度是O(n)。

缺点:

1、不能保证最后解是最优解,因为每次选择可能会对后续的选择产生影响,而贪心算法无法考虑这些影响。

2、难以判断某一问题是否适用贪心算法

常见应用场景:如最小生成树和最短路径算法。

对于它的一些缺点,有个与之相对应的概念--动态规划通常可以解决。

2、贪心算法与动态规划的区别

2.1 解决问题的类型不同

贪心算法是没有远视的一种算法,一般用于在做决策时,通过选择当前最优的选项来直接确定后续决策的问题,它关注的是在每个阶段做出最优选择,从而构造出整体最优解。与之相对的动态规划,通常用于解决一个复杂问题,该问题可以分解为多个子问题,这些问题通常在某种程度上是相互重叠的子单元模块,动态规划通过存储和重用子问题的最优解来避免重复计算,从而找到整个问题的最优解。

2.2 能否获得全局最优解不同

贪心算法通常能找到全局最优解,但并不一定,动态规划一定能找到全局最优解,但是过程可能复杂度比较高

如下:

贪婪算法以达到最大和为目标,在每一步都会选择看起来是最优的即时选择,所以在第二步会选择12而不是3,不会得到包含99的最优解。

2.3 算法复杂度不同

贪心算法通常比较简单,其时间复杂度可以是线性级别,而空间复杂度一般较低。动态规划的时间复杂度和空间复杂度通常相对较高,因为它需要存储和重用子问题的解。

2.4 实现方式不同

贪心算法从问题的顶层开始,逐步向下进行优化,每次选择都是基于当前状态下最佳的选择。动态规划则是从问题的底层开始,逐步向上构建最优解,它需要自底向上计算,并保存子问题的最优解以避免重复计算。

四、总结

1、贪心算法是一种思想和策略选择

2、与贪心算法相对的概念,动态规划,后面会单独分享

3、贪心算法的常见案例

附:贪心算法一些其他相关的概念

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值