lotus

贵有恒何必三更眠五更起 最无益只怕一日曝十日寒

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

 

贪心算法的介绍和例子

什么是贪心算法?

贪心算法是一种寻找最优解的算法,它的基本思想是:在每一步选择中,都采取当前状态下最好或最优的选择,从而希望导致结果是最好或最优的算法。

贪心算法有以下特点:

  • 只考虑当前状态,不考虑全局最优
  • 不能回退,即一旦做出选择就不能改变
  • 易于实现,运行效率高
  • 不一定能得到全局最优解,需要证明其正确性

贪心算法的例子

贪心算法有很多应用场景,

例如

  • 分糖果、
  • 找零钱、
  • 区间覆盖、
  • 最小生成树、
  • 背包问题

等。下面我们分别介绍这些例子,并给出相应的代码实现。

分糖果

问题描述:我们有m个糖果要分给n个孩子,n大于m,注定有的孩子不能分到糖果。每个孩子对糖果的需求量不同,我们用一个数组g表示,g[i]表示第i个孩子需要的糖果数量。每个糖果的大小也不同,我们用一个数组s表示,s[j]表示第j个糖果的大小。我们的目标是尽可能满足更多数量的孩子,求最多能满足多少孩子。

贪心策略:我们可以先对g和s进行排序,然后从小到大遍历糖果,对于每个糖果,找到能够满足的需求量最小的孩子,如果没有找到,则说明当前糖果太小,无法满足任何孩子,跳过该糖果。如果找到了,则将该孩子标记为已经满足,并将满足孩子数加一。最后返回满足孩子数即可。

代码实现(C++):

#include <iostream>
#include <algorithm>
using namespace std;

int candy(vector<int>& g, vector<int>& s) {
    // g是孩子需求量数组,s是糖果大小数组
    int n = g.size(); // n是孩子数量
    int m = s.size(); // m是糖果数量
    sort(g.begin(), g.end()); // 对g进行升序排序
    sort(s.begin(), s.end()); // 对s进行升序排序
    int i = 0; // i是当前遍历到的孩子索引
    int j = 0; // j是当前遍历到的糖果索引
    int count = 0; // count是满足孩子数
    while (i < n && j < m) { // 当两个数组都没有遍历完时
        if (g[i] <= s[j]) { // 如果当前糖果能够满足当前孩子
            count++; // 满足孩子数加一
            i++; // 移动到下一个孩子
            j++; // 移动到下一个糖果
        } else { // 如果当前糖果不能满足当前孩子
            j++; // 移动到下一个糖果
        }
    }
    return count; // 返回满足孩子数
}

 

找零钱

问题描述:我们有1元、5元、10元、20元、50元、100元纸币各C1、C5、C10、C20、C50、C100张,现在要购买一个价值K元的东西,请问怎么才能使用最少的纸币?

贪心策略:我们可以先从面值最大的纸币开始选择,尽可能多地使用该面值的纸币,直到不能再使用为止,然后换到次大面值的纸币,重复上述过程,直到凑齐K元或者没有更小面值的纸币为止。

代码实现(C++):

#include <iostream>
using namespace std;

int change(int K, int C1, int C5, int C10, int C20, int C50, int C100) {
    // K是要购买的东西的价值,C1-C100是各种面值纸币的数量
    int count = 0; // count是使用的纸币总数
    int money[6] = {100, 50, 20, 10, 5, 1}; // money是各种面值纸币的数组
    int num[6] = {C100, C50, C20, C10, C5, C1}; // num是各种面值纸币的数量数组
    for (int i = 0; i < 6; i++) { // 从大到小遍历各种面值
        int use = min(K / money[i], num[i]); // use是当前面值可以使用的最大数量
        count += use; // 纸币总数增加use
        K -= use * money[i]; // 要凑齐的金额减少use * money[i]
        if (K == 0) break; // 如果已经凑齐了,就退出循环
    }
    if (K > 0) return -1; // 如果还有剩余金额,说明无法凑齐,返回-1
    else return count; // 否则返回纸币总数
}

区间覆盖

问题描述:我们有n个区间,每个区间由左右端点[a,b]表示。现在要从这些区间中选出尽可能多的区间,使得这些区间两两不相交。求最多能选出多少个区间。

贪心策略:我们可以先对所有区间按照右端点从小到大进行排序,然后从左到右遍历区间,对于每个区间,如果它和前一个选中的区间不相交,则将它选中,并更新最后一个选中区间的右端点。否则就跳过该区间。最后返回选中区间的个数即可。

代码实现(C++):

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

struct Interval {
    int start; // 区间左端点
    int end; // 区间右端点
    Interval(int s, int e) : start(s), end(e) {} // 构造函数
};

// 比较函数,按照右端点从小到大排序
bool cmp(Interval a, Interval b) {
    return a.end < b.end;
}

int cover(vector<Interval>& intervals) {
    // intervals是区间数组
    int n = intervals.size(); // n是区间数量
    if (n == 0) return 0; // 如果没有区间,返回0
    sort(intervals.begin(), intervals.end(), cmp); // 对区间按照右端点排序
    int count = 1; // count是选中区间的个数,初始为1
    int last = intervals[0].end


贪心算法的优缺点和适用条件

贪心算法的优点

  • 贪心算法是一种简单,高效,省时省空间的算法,它可以在很多问题中得到较好的近似解或者次优解。
  • 贪心算法通常作为其他算法的辅助算法来使用,比如在动态规划,分支限界,回溯等算法中,可以用贪心算法来进行剪枝,预处理,启发式搜索等操作。
  • 贪心算法的思想也可以运用到实际生活中的很多场景,比如排队,装箱,调度等问题。

贪心算法的缺点

  • 贪心算法不能保证最后求得的解是最优的,有时候甚至会得到错误的解或者无解。
  • 贪心算法对问题的适用性有很强的限制,需要问题具有无后效性和最优子结构性质,即当前的决策不会影响到后续的决策,而且问题的最优解可以由子问题的最优解构成。
  • 贪心算法的设计和证明比较困难,需要找到合适的贪心策略,并且证明其正确性和最优性。

贪心算法的适用条件

  • 问题具有无后效性和最优子结构性质。
  • 问题可以分解成若干个子问题,并且每个子问题都有一个明确的最优选择。
  • 问题可以按照某种顺序或者规则进行处理,并且每一步都做出当前状态下的最优选择。
  • 问题不需要求出全局最优解,只需要求出一个可行解或者近似解。
posted on 2023-04-29 23:18  白露~  阅读(44)  评论(0编辑  收藏  举报