- C++
C++ 贪心算法
- 2025-5-13 20:37:51 @
C++ 贪心算法入门教程(0基础友好 | 通俗易懂 | 样式丰富)
🌟什么是贪心算法?
贪心算法(Greedy Algorithm)是一种在每一步选择中都采取当前状态下最优的选择,希望通过局部最优解达到全局最优解的算法策略。
注意: 贪心算法并不总是能得到全局最优解,但在某些特定问题中非常有效!
🧠贪心算法的核心思想
- 每一步都做出一个看起来最优的选择
- 不考虑整体结构,只关注当前状态下的“眼前利益”
- 一旦做出选择就不能回退
🎯适用场景(常见问题)
贪心算法适用于以下类型的问题:
- 找零钱问题
- 活动选择问题
- 区间调度问题
- 霍夫曼编码(压缩)
- 最小生成树中的 Prim 和 Kruskal 算法(使用贪心思想)
🚀从最简单的例子开始:找零钱问题
💡问题描述:
你有若干种面值的硬币(如 1 元、2 元、5 元),现在要凑出某个金额 n
,要求使用最少数量的硬币。
✅贪心思路:
每次选择最大可用的硬币面值。
#include <iostream>
using namespace std;
int main() {
int coins[] = {5, 2, 1}; // 面值从大到小排列
int n = 17; // 要凑的金额
int count = 0; // 记录使用的硬币数
for (int i = 0; i < 3; i++) {
if (n >= coins[i]) {
int num = n / coins[i]; // 最多能用几个这个面值的硬币
count += num;
n -= num * coins[i]; // 减去已使用的金额
}
}
cout << "最少需要 " << count << " 枚硬币。" << endl;
return 0;
}
📌 输出:
最少需要 4 枚硬币。
即 5×3 + 2×1 = 17,共 4 枚。
📌贪心算法的一般步骤总结:
- 确定问题是否适合用贪心解决
- 设计贪心策略
- 对数据进行排序或预处理
- 循环遍历,每次都做当前最优选择
- 记录结果并输出
🧩实战练习一:活动选择问题(Activity Selection)
💡问题描述:
给定多个活动的时间区间,从中选出最多可以互不重叠进行的活动个数。
✅贪心策略:
按结束时间从小到大排序,依次选择最早结束的活动。
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
struct Activity {
int start, end;
};
// 按照结束时间升序排序
bool compare(Activity a, Activity b) {
return a.end < b.end;
}
int main() {
vector<Activity> activities = {
{1, 4}, {3, 5}, {0, 6},
{5, 7}, {3, 8}, {5, 9},
{6, 10}, {8, 11}
};
sort(activities.begin(), activities.end(), compare);
int count = 1; // 第一个活动一定选
int last_end = activities[0].end;
for (int i = 1; i < activities.size(); i++) {
if (activities[i].start >= last_end) {
count++;
last_end = activities[i].end;
}
}
cout << "最多可以选择 " << count << " 个互不重叠的活动。" << endl;
return 0;
}
📌 输出:
最多可以选择 4 个互不重叠的活动。
🧩实战练习二:背包问题(分数背包)
💡问题描述:
有一个容量为 W
的背包和若干物品,每个物品有价值和重量,可以取一部分装入背包,求最大总价值。
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
struct Item {
int value, weight;
double ratio; // 价值/重量比
};
// 按照价值密度从高到低排序
bool compare(Item a, Item b) {
return a.ratio > b.ratio;
}
int main() {
vector<Item> items = {
{60, 10}, {100, 20}, {120, 30}
};
int W = 50; // 背包容量
double total = 0; // 总价值
// 计算价值密度
for (auto &item : items) {
item.ratio = (double)item.value / item.weight;
}
sort(items.begin(), items.end(), compare);
for (auto &item : items) {
if (W == 0) break;
if (item.weight <= W) {
total += item.value;
W -= item.weight;
} else {
total += item.ratio * W;
W = 0;
}
}
cout.precision(2);
cout << fixed << "最大总价值为:" << total << endl;
return 0;
}
📌 输出:
最大总价值为:240.00
📚贪心与动态规划的区别
特点 | 贪心算法 | 动态规划 |
---|---|---|
是否回溯 | 否 | 是 |
时间复杂度 | 通常较低 | 通常较高 |
是否保证最优解 | 不一定 | 一般可以 |
使用场景 | 局部最优可导出全局最优 | 多阶段决策问题 |
🧠如何判断一个问题能否用贪心?
- 具有贪心选择性质:可以通过一系列局部最优解得到全局最优解
- 具有最优子结构性质:原问题的最优解包含子问题的最优解
🧑💻练习建议(适合新手)
- 完全理解上面两个例题(找零钱、活动选择)
- 尝试自己实现“会议安排”、“任务调度”等类似问题
- 学会使用
sort()
自定义排序规则 - 练习用浮点数计算性价比(如分数背包)
- 多画图模拟贪心过程
📚推荐学习路径
- 入门:掌握基本贪心问题(找零钱、活动选择)
- 提升:尝试解决更复杂的贪心题目(如:加油站问题、跳跃游戏)
- 拓展:结合其他算法(如排序+贪心、优先队列+贪心)
🧾结语
贪心算法是算法学习中的重要基石之一。它简单、高效,但也有局限性。希望你能通过本教程理解它的基本思想,并动手实践,真正掌握这一实用技能!
📖参考资料
- 《算法导论》(Introduction to Algorithms)
- LeetCode 上的经典贪心题目(如 455. 分发饼干、435. 无重叠区间)
1 条评论
-
admin SU @ 2025-5-13 20:39:08
C++ 贪心算法入门教程
贪心算法是一种在每一步选择中都采取当前状态下最优(局部最优)选择,从而希望导致全局结果也最优的算法策略。虽然贪心算法不能解决所有问题,但它可以高效解决许多经典问题。
1. 贪心算法基本思想
贪心算法的核心思想是:每一步都做出当前看起来最好的选择,而不考虑这种选择对未来的影响。这种策略在某些问题中可以直接得到全局最优解,但在其他问题中可能只能得到近似解。
2. 贪心算法适用条件
贪心算法适用于满足以下两个条件的问题:
- 贪心选择性质:问题的全局最优解可以通过一系列局部最优选择得到。
- 最优子结构性质:问题的最优解包含其子问题的最优解。
3. 简单示例:找零问题
问题描述:假设你是一个收银员,需要找给顾客一定金额的零钱,如何使用最少的硬币?
示例代码:
#include <iostream> #include <vector> using namespace std; // 使用标准命名空间,避免重复写std:: // 找零问题的贪心算法实现 vector<int> greedyChange(int amount) { vector<int> coins = {25, 10, 5, 1}; // 硬币面额:25美分(quarter)、10美分(dime)、5美分(nickel)、1美分(penny) vector<int> result(coins.size(), 0); // 每种硬币的数量 for (int i = 0; i < coins.size(); i++) { // 尽可能多地使用当前最大面额的硬币 result[i] = amount / coins[i]; amount = amount % coins[i]; // 剩余需要找零的金额 } return result; } int main() { int amount = 63; // 需要找零63美分 vector<int> change = greedyChange(amount); cout << "找零 " << amount << " 美分需要的硬币数量:" << endl; vector<int> coins = {25, 10, 5, 1}; for (int i = 0; i < coins.size(); i++) { if (change[i] > 0) { cout << coins[i] << "美分: " << change[i] << "个" << endl; } } return 0; }
代码解释:
- 硬币面额数组
coins
包含四种美国硬币面额(25、10、5、1美分)。 - 贪心策略:每次都选择当前可用的最大面额硬币,直到剩余金额为0。
- 结果验证:对于63美分的找零,算法会返回2个25美分、1个10美分和3个1美分,共6个硬币,这是最优解。
4. 进阶示例:活动选择问题
问题描述:有多个活动需要使用同一资源(如会议室),每个活动有开始时间和结束时间,如何选择最多的活动,使得它们互不冲突?
示例代码:
#include <iostream> #include <vector> #include <algorithm> using namespace std; // 使用标准命名空间,避免重复写std:: // 活动结构体,包含活动编号、开始时间和结束时间 struct Activity { int id; int start; int end; }; // 比较函数,按结束时间升序排序 bool compareActivities(const Activity& a, const Activity& b) { return a.end < b.end; } // 活动选择的贪心算法实现 vector<int> activitySelection(vector<Activity>& activities) { // 按结束时间排序 sort(activities.begin(), activities.end(), compareActivities); vector<int> selected; if (activities.empty()) return selected; // 选择第一个结束的活动 selected.push_back(activities[0].id); int lastEndTime = activities[0].end; // 遍历剩余活动,选择开始时间晚于上一个结束时间的活动 for (int i = 1; i < activities.size(); i++) { if (activities[i].start >= lastEndTime) { selected.push_back(activities[i].id); lastEndTime = activities[i].end; } } return selected; } int main() { // 示例活动:(编号, 开始时间, 结束时间) vector<Activity> activities = { {1, 1, 4}, {2, 3, 5}, {3, 0, 6}, {4, 5, 7}, {5, 3, 9}, {6, 5, 9}, {7, 6, 10}, {8, 8, 11}, {9, 8, 12}, {10, 2, 14}, {11, 12, 16} }; vector<int> selected = activitySelection(activities); cout << "最多可以选择的活动数量:" << selected.size() << endl; cout << "选择的活动编号为:"; for (int id : selected) { cout << id << " "; } cout << endl; return 0; }
代码解释:
- 活动结构体
Activity
存储每个活动的编号、开始时间和结束时间。 - 贪心策略:每次选择结束时间最早且与已选活动不冲突的活动。
- 排序操作:首先按结束时间对活动进行排序,确保每次能选择最早结束的活动。
5. 贪心算法的优缺点
优点:
- 算法简单,易于理解和实现。
- 时间复杂度通常较低,效率高。
缺点:
- 不能解决所有问题,只适用于满足贪心选择性质的问题。
- 有时得到的解只是近似最优解,而非全局最优解。
6. 练习题
-
分数背包问题:给定一组物品,每个物品有重量和价值,以及一个容量为C的背包,如何选择物品放入背包,使得背包中物品的总价值最大?注意:物品可以分割。
-
最小等待时间问题:有n个任务需要处理,每个任务需要一定的时间,如何安排处理顺序,使得所有任务的平均等待时间最短?
-
跳跃游戏:给定一个非负整数数组,每个元素代表在该位置可以跳跃的最大长度,判断是否可以跳到数组的最后一个位置。
通过这些示例和练习,你应该对贪心算法有了基本的理解。贪心算法虽然简单,但在合适的问题中可以发挥很大作用。在实际应用中,需要仔细分析问题是否满足贪心选择性质,避免盲目使用贪心策略导致错误结果。
- 1