七日打卡——数据结构与算法之贪心算法

1,401 阅读4分钟

数据结构与算法之贪心算法

前言

数据结构的专题好久没有更新了,然后一直想更新一篇贪心算法的文章,因此不能再拖更了,贪心算法他来了!

定义

贪心算法,又称为贪婪算法(Greedy Algorithm),以下是贪心算法在维基百科上的定义。

贪心算法(Greedy Algorithm) A greedy algorithm is any algorithm that follows the problem-solving heuristic of making the locally optimal choice at each stage.

从上面的定义可以知晓,贪心算法与回溯算法、动态规划一样都是需要进行分步决策
并且根据贪心算法的定义,其解决问题是在每一步决策时,都选择该步最优的选择(即局部最优),希望可以得到全局最优。但是局部最优抉择或许会达不到全局最优,这是值得思考的,所以这也是它与动态规划的区别,动态规划的每一步前后关联,追寻的是全局最优。
根据贪心算法的定义,同样我们尝试将贪心算法进行程序化:

  1. 问题的数学模型是什么(变量,分步,目标)
  2. 将问题分解为子问题
  3. 子问题的局部最优解
  4. 局部最优解推导至认为的全局最优解(当然可以采用贪心算法的问题,认为的全局最优即为真正的全局最优)

1. 问题的数学模型

抽象数学模型,定义好操作的变量的含义,找出最终想得到的目标。

2. 拆解成子问题

模型建立后,着重考虑如何进行分步操作。

3. 子问题的局部最优解

分步划分之后,每一步需要如何操作变量进而得到局部最优解。

4. 局部推导全局

得到每一步子问题的局部最优解,如何处理成全局的最优解。

5. 简易算法模板

Greedy() {
	建立数学模型
	拆解子问题
	找寻目标
	while(下一步还有子问题待解决){
		获得子问题的局部最优解
	}
	根据目标整合每一步的局部最优解推导至全局最优解
}

根据上面的算法模板,以及处理问题的流程可以看出,贪心算法的实质就是如何得到局部最优解。

下面就举两个例子来进行示范。 在这里插入图片描述

贪心算法例题

买卖股票的最佳时期II

买卖股票的最佳时期II是力扣中选出的比较有特殊的贪心算法的题目。

问题描述

力扣
122. 买卖股票的最佳时机 II
给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。 注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。 示例

问题分析

  1. 建立数学模型
    本题数学模型为:
    变量:已知n天的股票价格prices[0:n-1]
    操作:经过n天之后,通过第l天买入股票(-prices[l])和第r天卖出股票(+prices[r])(l <= r)的多次操作,买入和卖出可以是同一天。
    目标:经过多次买入卖出操作使得最后我的结果利润res最大。
  2. 拆解成子问题
    选择一天买入股票+选择一天卖出股票
  3. 子问题的局部最优解
    第i天买入,若第i+1天卖出盈利就买,否则不买。 这样就可以,知道第i+1天盈利那第i天肯定买入。然后到了第i+1天以此类推。最后一天如果前面还有股票则肯定是卖出,反之则不买。
  4. 局部最优推导全局最优
    可以理解为差分方程,并且每一步差值都是正数,均是局部最优,并且a - b + b - c = a - c并不会因为中间的买入卖出影响到最低买入与最高卖出之间的最大盈利结果,因此可行。

代码

class Solution {
    public int maxProfit(int[] prices) {
        int ans = 0;
        int n = prices.length;
        for (int i = 1; i < n; ++i) {
            ans += Math.max(0, prices[i] - prices[i - 1]);
        }
        return ans;
    }
}

柠檬水找零

柠檬水找零是比较简单且容易思考的贪心算法题目了,可以根据这一题自己尝试分析下贪心问题哦。

问题描述

力扣
顾客排队购买你的产品,(按账单 bills 支付的顺序)一次购买一杯。
每位顾客只买一杯柠檬水,然后向你付 5 美元、10 美元或 20 美元。你必须给每个顾客正确找零,也就是说净交易是每位顾客向你支付 5 美元。
注意,一开始你手头没有任何零钱。 如果你能给每位顾客正确找零,返回 true ,否则返回 false 。 在这里插入图片描述

问题分析

这个问题就很简单啦,典型贪心算法。要找零15元时候,有10元先给10元美钞,再给5元美钞;没有10元则给3张5元。要找零5元时则直接给一张5元。找不开钱的时候则返回false

代码

class Solution {
    public boolean lemonadeChange(int[] bills) {
        int fiveNums = 0;
        int tenNums = 0;
        for (int bill : bills) {
            if (bill == 5) {
                fiveNums++;
            } else if (bill == 10) {
                if (fiveNums == 0) {
                    return false;
                }
                fiveNums--;
                tenNums++;
            } else {
                if (fiveNums > 0 && tenNums > 0) {
                    fiveNums--;
                    tenNums--;
                } else if (fiveNums >= 0) {
                    fiveNums -= 3;
                } else {
                    return false;
                }
            }
        }
        return true;
    }
}

总结

贪心算法初步讲解就到这里结束了,先搞清贪心算法是否可行,然后再想方设法去制定贪心策略。
当然贪心算法不是万能的,当遇到一些前后关联不大不需要考虑前一状态的问题时,可以考虑是否是贪心算法。在运用过程中可以询问自己如下问题:

  1. 局部最优是什么?
  2. 全局最优是什么?
  3. 局部最优是否能达到全局最优? 贪心算法也是一种分步策略型的算法,在学习过程可以将贪心算法、回溯算法以及动态规划放在一起进行学习与理解。