贪心算法的介绍和例子
什么是贪心算法?
贪心算法是一种寻找最优解的算法,它的基本思想是:在每一步选择中,都采取当前状态下最好或最优的选择,从而希望导致结果是最好或最优的算法。
贪心算法有以下特点:
- 只考虑当前状态,不考虑全局最优
- 不能回退,即一旦做出选择就不能改变
- 易于实现,运行效率高
- 不一定能得到全局最优解,需要证明其正确性
贪心算法的例子
贪心算法有很多应用场景,
例如
- 分糖果、
- 找零钱、
- 区间覆盖、
- 最小生成树、
- 背包问题
等。下面我们分别介绍这些例子,并给出相应的代码实现。
分糖果
问题描述:我们有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
贪心算法的优缺点和适用条件
贪心算法的优点
- 贪心算法是一种简单,高效,省时省空间的算法,它可以在很多问题中得到较好的近似解或者次优解。
- 贪心算法通常作为其他算法的辅助算法来使用,比如在动态规划,分支限界,回溯等算法中,可以用贪心算法来进行剪枝,预处理,启发式搜索等操作。
- 贪心算法的思想也可以运用到实际生活中的很多场景,比如排队,装箱,调度等问题。
贪心算法的缺点
- 贪心算法不能保证最后求得的解是最优的,有时候甚至会得到错误的解或者无解。
- 贪心算法对问题的适用性有很强的限制,需要问题具有无后效性和最优子结构性质,即当前的决策不会影响到后续的决策,而且问题的最优解可以由子问题的最优解构成。
- 贪心算法的设计和证明比较困难,需要找到合适的贪心策略,并且证明其正确性和最优性。
贪心算法的适用条件
- 问题具有无后效性和最优子结构性质。
- 问题可以分解成若干个子问题,并且每个子问题都有一个明确的最优选择。
- 问题可以按照某种顺序或者规则进行处理,并且每一步都做出当前状态下的最优选择。
- 问题不需要求出全局最优解,只需要求出一个可行解或者近似解。