梦想不会自己发光,真正闪耀的是那个为梦狂奔的你。献给知行的孩子们!(Eric.He著)
贪心算法(Greedy Algorithm)是一种以 “局部最优” 推导 “全局最优” 的算法思想,其核心原理是:在问题求解的每一步决策中,都选择当前状态下看起来最优(即收益最大、成本最低或最符合某种局部准则)的选项,不考虑该决策对未来步骤的影响,通过逐步累积局部最优解,最终期望得到问题的全局最优解。
贪心算法的执行逻辑可概括为 “步步最优,累积成全局”,它不像动态规划那样考虑所有可能的子问题,也不像分治算法那样需要拆分与合并,而是沿着单一的决策路径,每一步都做出当下的最优选择,最终形成完整解。
简单来说,贪心算法就像 “走一步看一步” 的决策方式:比如旅行时每次都选择当前最近的下一个目的地,期望最终形成最短的旅行路线;再比如找零钱时,每次都优先选择面值最大的纸币,期望用最少的纸币数量凑出目标金额。
贪心算法能够得到全局最优解的两个必要前提,缺一不可:
只有同时满足以上两个性质的问题,才能用贪心算法求得全局最优解;若不满足,贪心算法得到的可能只是近似最优解。
要实现一个贪心算法,通常遵循以下 4 个标准步骤:
首先清晰界定问题的求解目标,以及问题的约束条件,这是设计贪心策略的基础。例如:
根据问题目标,制定每一步的决策准则,即 “如何选择当前的局部最优解”。贪心策略的设计是贪心算法的灵魂,不同的贪心策略可能导致不同的结果(最优或近似最优)。例如:
按照设计好的贪心策略,依次进行每一步决策,逐步累积局部最优解,同时更新问题的当前状态(如剩余金额、剩余活动列表等),直到满足终止条件。
验证最终累积的解是否满足问题的目标与约束条件,若满足则输出结果;若问题不满足贪心选择性质,需评估该解是否为可接受的近似最优解。
贪心算法的优化核心围绕 “贪心策略的合理性” 和 “执行效率” 展开,常见优化方向有:
贪心算法适用于满足 “贪心选择性质” 和 “最优子结构性质” 的问题,常见包括:
下面以 “找零钱问题” 为例,展示贪心算法的实现。
问题描述:给定一组固定面值的纸币(如 [1,5,10,20,50,100]),需要给用户找零 n 元,要求使用最少数量的纸币完成找零。
算法解析
#include <iostream>
#include <vector>
using namespace std;
// 1. 定义人民币面额(降序排列,贪心核心:优先选最大面额)
int coins[] = {100, 50, 20, 10, 5, 1};
// 面额数组的长度
int coinKind = sizeof(coins) / sizeof(coins[0]);
/**
* 迭代实现贪心找零
* @param remain 剩余待找零金额
* @param changeList 存储找零组合的容器
*/
void change_iter(int remain, vector<int>& changeList)
{
// 2. 贪心算法核心逻辑
for (int i = 0; i < coinKind; i++) {
int currentCoin = coins[i];
// 只要当前面额 ≤ 剩余金额,就持续选用该面额
while (remain >= currentCoin) {
changeList.push_back(currentCoin);
remain -= currentCoin;
}
// 提前终止循环:金额找零完成,无需遍历后续面额
if (remain == 0) break;
}
}
/**
* 递归实现贪心找零
* @param remain 剩余待找零金额
* @param index 当前遍历的面额索引(从0开始,对应最大面额)
* @param changeList 存储找零组合的容器
*/
void change_recu(int remain, int index, vector<int>& changeList) {
// 递归终止条件:剩余金额为0,无需继续
if (remain == 0) {
return;
}
// 2. 贪心算法核心逻辑
// 取出当前面额
int currentCoin = coins[index];
// 如果当前面额 ≤ 剩余金额,选用该面额并递归处理剩余金额
if (remain >= currentCoin) {
changeList.push_back(currentCoin);
// 递归:剩余金额减少,面额索引不变(继续尝试用当前面额)
change_recu(remain - currentCoin, index, changeList);
} else {
// 当前面额太大,切换到下一个更小的面额(索引+1)
// 注意:index < coinKind-1 是防越界(最后一个面额是1,必能找零)
if (index < coinKind - 1) {
change_recu(remain, index + 1, changeList);
}
}
}
int main() {
// 需要找零的目标金额
int target = 268;
// 存储最终的找零组合明细
vector<int> changeList;
//change_iter(target,changeList);
change_recu(target,0,changeList);
// 3. 输出结果
cout << "找零 " << target << " 元,贪心算法最优解:" << endl;
cout << "最少需要钱币数量:" << changeList.size() << " 张" << endl;
cout << "具体找零组合:";
for (int num : changeList) {
cout << num << " ";
}
cout << endl;
return 0;
}
找零 268 元,贪心算法最优解:
最少需要钱币数量:8 张
具体找零组合:100 100 50 10 5 1 1 1
优点
缺点
| 对比维度 | 贪心算法 | 分治算法 | 枚举算法 |
|---|---|---|---|
| 核心思想 | 局部最优→全局最优 | 分而治之(分 + 治 + 合) | 穷举解空间(遍历 + 验证) |
| 决策方式 | 一步一决策,无后效性 | 先拆分后合并,全局规划 | 逐一验证,无决策筛选 |
| 最优性保证 | 仅满足特定条件时最优 | 满足子问题独立时最优 | 解空间完整时必然最优 |
| 时间复杂度 | 低(O (n)/O (n log n)) | 较低(O (n log n)) | 高(O (n²) 及以上) |
| 空间复杂度 | 低(无需额外空间) | 可能需要辅助空间 | 低(无需额外空间) |
| 适用场景 | 资源分配、区间、图论 | 大规模可拆分问题 | 小规模解空间问题 |