算法训练之贪心


♥♥♥~~~~~~欢迎光临知星小度博客空间~~~~~~♥♥♥

♥♥♥零星地变得优秀~也能拼凑出星河~♥♥♥

♥♥♥我们一起努力成为更好的自己~♥♥♥

♥♥♥如果这一篇博客对你有帮助~别忘了点赞分享哦~♥♥♥

♥♥♥如果有什么问题可以评论区留言或者私信我哦~♥♥♥
✨✨✨✨✨✨ 个人主页✨✨✨✨✨✨


在前面的博客中,我们已经学习了部分算法思路,可以解决一些问题了,这一篇博客我们来展开一个新的算法思想------贪心,准备好了吗~我们发车去探索奥秘啦~🚗🚗🚗🚗🚗🚗

目录

前置知识

什么是贪心算法?

举例

找零钱问题

最小路径和问题

贪心算法的特点

柠檬水找零

将数组和减半的最小操作数

priority_queue

核心特性

常用操作


前置知识

在真正开始贪心算法题目练习之前,我们首先要了解什么是贪心算法?贪心算法有什么特点?

什么是贪心算法?

我们可以用一句话来概括:贪婪+鼠目寸光

贪心策略: 解决问题的策略,由局部最优------>全局最优
具体步骤:

1.把解决问题的过程分为若干步;

2.解决每一步的时候,都选择当前看起来"最优的"解法;(鼠目寸光)

3."希望"得到全局最优解。(不一定就是最优解)

光光看这些步骤会有一点抽象,我们来结合具体例子来看~

举例

找零钱问题

将要解决的问题分为几步,从最大的面值开始计算,找当前花的最少张数,显然答案是正确的,后续我们会进行证明~

最小路径和问题

在前面动态规划我们提到过这个问题,这一次我们试一下使用贪心算法可不可以解决~

从左上角开始,我们每一步都按当前位置的最优选择(局部最优解)进行,但是不难发现结果是错误的~所以我们是"希望"得到全局最优解,但是结果不一定近人意,我们就需要更改我们的贪心策略~

贪心算法的特点

结合前面的例子,我们可以发现贪心算法有下面的特点:

一、贪心策略的提出

1.贪心策略的提出是没有标准以及模板的

2.可能每一道题的贪心策略都是不同的
二、贪心策略的正确性

"贪心策略"可能是一个错误的方法;正确的贪心策略,我们是需要"证明的"。

常用的证明的方法:数学中见过的所有证明方法

接下来,我们就来证明找零钱问题贪心策略的正确性:

我们首先假设最优解得到的面值为20、10、5、1的张数分别为A、B、C、D,我们首先来看看它们的特点/性质:

相同的分析可以得到B、C、D满足B<=1,C<=1,D<=4

我们继续来看,假设贪心算法得到的面值为20、10、5、1的张数分别为a、b、c、d~

既然最优解是最少的张数的话,那么我们显然a>=A的,那么我们来分析一下a>A的情况,a大于A说明还有20需要其他面值凑出来,但是B、C、D满足B<=1,C<=1,D<=4,所以最多就是19,不能凑出来20,那么就说明a==A,同理可以得到b==B,c==C,d==D,所以最优解的答案和贪心得到的答案是一样的,那么也就说明是正确的~

因此,正确的贪心策略,我们是需要"证明的",证明结果正确才代表贪心策略正确,证明贪心策略不正确,只需要举一个反例,那么这个时候就需要我们对贪心策略进行调整了~


有了前面的这些小知识点,接下来,我们就来在题目中进行实际应用贪心算法~

柠檬水找零

柠檬水找零

这个题目告诉了我们一杯柠檬水5美元,顾客可能给5美元/10美元/20美元,最开始我们手上没有任何的美元,这也就说明我们只能利用顾客给我们的钱进行找零~并且顾客是排队购买的,必须成功找了零钱才能继续收下一个顾客的钱~

我们来进行分类讨论:

1、如果顾客给的钱是5美元------直接收下

2、如果顾客给的钱是10美元------需要找5美元

3、如果顾客给的钱是20美元------需要找15美元(这里的十五美元就有两种选择了)

①10 + 5 ②5 + 5 + 5

我们主要来分析一下第三种情况,我们优先选择第一种还是第二种呢?

答案是第一种,我们不难发现5美元是有很大用处的,10美元可以找,20美元也可以找,结合我们的生活经验,我们应该尽可能的保留5美元~这里就体现了我们贪心思想,优先选择当前情况下的最优解,最终得到全局的最优解~

有了前面的分析,这道题目的代码实现就比较简单了~

代码实现:

cpp 复制代码
//柠檬水找零
class Solution
{
public:
    bool lemonadeChange(vector<int>& bills)
    {
        int n = bills.size();
        int five = 0, ten = 0;//统计可以找的钱
        for (auto x : bills)
        {
            if (x == 5)//是5元直接收下
            {
                five++;
            }
            else if (x == 10)//10元需要找5元
            {
                //判断是否有五元
                if (five)
                {
                    five--;
                    ten++;
                }
                else
                    return false;
            }
            else//20元需要找15元
            {
                //贪心思想
                //优先10+5
                if (five && ten)
                {
                    five--;
                    ten--;
                }
                //其次5+5+5
                else if (five >= 3)
                {
                    five -= 3;
                }
                //找不了就return false
                else
                    return false;
            }
        }
        return true;
    }
};

顺利通过~

将数组和减半的最小操作数

将数组和减半的最小操作数

题目要求是比较简单的,我们这里就直接来说一下我们的贪心策略:

贪心策略

每一次找到数组中最大的数(我们可以使用大根堆)进行减半操作,再把减半的数重新放入数组中,重复操作,找到整个数组和减少一半为止~

贪心策略知道了,代码实现也就更加简单了~

代码实现:

cpp 复制代码
class Solution
{
public:
    int halveArray(vector<int>& nums)
    {
        //使用大根堆------每次找到最大值减少一半
        int n = nums.size();
        //减半之后可能是小数,使用double存储
        priority_queue<double> heap;
        //插入数据+统计数组和
        double sum = 0;
        for (auto e : nums)
        {
            heap.push(e);
            sum += e;
        }
        sum /= 2.0;//和先除2
        int count = 0;//计数
        while (sum > 0)//当减半和>0,进入循环
        {
            //找到堆顶元素减少一半重新插入
            double tmp = heap.top() / 2;
            heap.pop();
            sum -= tmp;
            heap.push(tmp);
            count++;
        }
        return count;
    }
};

顺利通过~接下来对我们的贪心策略进行简单的证明~

交换论证法证明:

贪心解选择的数据:A 、B 、C 、D 、E.....x....

最优解选择的数据:a 、 b 、c 、 d、 e......y...

贪心解每一次都是选择最大的数进行减半操作,那么说明贪心解选择的数据 >= 最优解选择的数据,数据相同的情况,我们就不需要处理了,如果说贪心解选择的数据(x)>最优解选择的数据(y)------分情况讨论

1、如果这个数据没有使用过,因为x > y,那么把最优解里面的数据y替换成x,肯定是可以达到数组和减少一半的操作的

2、如果这个数据使用过了,我们也可以把最优解里面的数据y替换成x,只不过更改一下顺序就可以了

最终进行不断地替换完成之后,我们不难发现贪心解和最优解选择的数据个数是相等的,也就是我们想要的结果~

证明结束~

此外,这一篇文章提到的priority_queue也在这里进行简单介绍

priority_queue

priority_queue(优先队列)是一个基于堆(Heap)结构的容器适配器,用于高效管理元素的优先级。以下是关键点介绍:

核心特性

  • 默认最大堆:默认使用大顶堆(最大元素在队首),可通过比较函数改为小顶堆。
  • 默认大顶堆:priority_queue<int> pqmax;
  • 小顶堆:priority_queue<int, vector<int>, greater<int>> pqmin;
  • 高效操作 :插入(push)和删除(pop)的时间复杂度为 O(log n) ,访问队首元素(top)为 O(1)
  • 不支持遍历:内部通过堆实现,直接遍历无法得到有序序列。

常用操作

  • 插入元素pq.push(value);
  • 删除队首pq.pop();
  • 访问队首int top = pq.top();
  • 判空pq.empty()
  • 大小pq.size()

如果想了解更加具体的可以访问cplusplus------priority_queue


♥♥♥本篇博客内容结束,期待与各位优秀程序员交流,有什么问题请私信♥♥♥

♥♥♥如果这一篇博客对你有帮助~别忘了点赞分享哦~♥♥♥

✨✨✨✨✨✨个人主页✨✨✨✨✨✨


相关推荐
明月看潮生31 分钟前
青少年编程与数学 02-016 Python数据结构与算法 15课题、字符串匹配
python·算法·青少年编程·编程与数学
精彩漂亮ing2 小时前
CExercise_13_1排序算法_2归并排序
算法·排序算法
小媛早点睡4 小时前
贪心算法day10(无重叠区间)
算法·贪心算法
DataFunTalk4 小时前
乐信集团副总经理周道钰亲述 :乐信“黎曼”异动归因系统的演进之路
前端·后端·算法
行走的bug...4 小时前
sklearn估计器和变换器共有的一些方法 待更新
人工智能·算法·sklearn
DataFunTalk4 小时前
开源一个MCP+数据库新玩法,网友直呼Text 2 SQL“有救了!”
前端·后端·算法
Y.O.U..5 小时前
力扣HOT100——560.和为k的子数组
数据结构·c++·算法·leetcode
wuqingshun3141595 小时前
经典算法 判断一个图中是否有环
java·开发语言·数据结构·c++·算法·蓝桥杯·深度优先
柃歌5 小时前
【LeetCode Solutions】LeetCode 160 ~ 165 题解
数据结构·算法·leetcode
小森77675 小时前
(五)机器学习---决策树和随机森林
算法·决策树·随机森林·机器学习·分类算法