以下为C++贪心算法详解,包含核心思想、经典问题分析及代码实现,内容结构清晰:
贪心算法基本概念
贪心算法(Greedy Algorithm)通过局部最优选择达到全局最优解,适用于具有最优子结构的问题。其核心特点是不可回溯,一旦做出选择就不再改变。
与动态规划相比,贪心算法不保存子问题解,通常时间复杂度更低,但需要严格证明贪心策略的正确性。典型应用场景包括最短路径、任务调度等问题。
算法设计要素
- 贪心选择性质:每一步的局部最优选择能导致全局最优解
- 最优子结构:问题的最优解包含子问题的最优解
- 无后效性:当前选择不影响后续决策的可行性
经典问题解析
活动选择问题
选择最多数量的互不重叠活动:
#include <algorithm>
#include <vector>
using namespace std;
struct Activity {
int start, finish;
};
bool compare(Activity a1, Activity a2) {
return a1.finish < a2.finish;
}
vector<Activity> selectActivities(vector<Activity> activities) {
sort(activities.begin(), activities.end(), compare);
vector<Activity> result;
int lastFinish = 0;
for (auto act : activities) {
if (act.start >= lastFinish) {
result.push_back(act);
lastFinish = act.finish;
}
}
return result;
}
霍夫曼编码
构建最优前缀编码树:
#include <queue>
#include <unordered_map>
struct Node {
char data;
unsigned freq;
Node *left, *right;
Node(char d, unsigned f) : data(d), freq(f), left(nullptr), right(nullptr) {}
};
struct compare {
bool operator()(Node* l, Node* r) {
return l->freq > r->freq;
}
};
void buildHuffmanCodes(Node* root, string code, unordered_map<char, string>& huffmanCode) {
if (!root) return;
if (!root->left && !root->right) {
huffmanCode[root->data] = code;
}
buildHuffmanCodes(root->left, code + "0", huffmanCode);
buildHuffmanCodes(root->right, code + "1", huffmanCode);
}
unordered_map<char, string> buildHuffmanTree(string text) {
unordered_map<char, unsigned> freqMap;
for (char ch : text) freqMap[ch]++;
priority_queue<Node*, vector<Node*>, compare> minHeap;
for (auto pair : freqMap) {
minHeap.push(new Node(pair.first, pair.second));
}
while (minHeap.size() != 1) {
Node *left = minHeap.top(); minHeap.pop();
Node *right = minHeap.top(); minHeap.pop();
Node *newNode = new Node('$', left->freq + right->freq);
newNode->left = left;
newNode->right = right;
minHeap.push(newNode);
}
unordered_map<char, string> huffmanCode;
buildHuffmanCodes(minHeap.top(), "", huffmanCode);
return huffmanCode;
}
算法证明方法
- 交换论证法:通过比较任意解与贪心解的差异进行证明
- 数学归纳法:验证贪心选择在各阶段保持最优性
- 决策包容性:证明最优解包含贪心选择
典型应用场景
- 最小生成树(Prim/Kruskal算法)
- 单源最短路径(Dijkstra算法)
- 分数背包问题
- 任务调度问题
- 硬币找零问题(特定面值情况)
复杂度分析
贪心算法的时间复杂度通常由排序步骤决定:
- 活动选择:O(n log n)
- 霍夫曼编码:O(n log n)
- 最小生成树:O(E log V)
空间复杂度一般为O(n)或O(1),取决于是否需要额外存储结构。
局限性
- 不适用于需要全局考虑的问题
- 无法保证所有问题的最优解
- 需要严格的数学证明支持
- 对输入数据有特定要求(如背包问题的分数形式)
实战技巧
- 优先考虑结束时间/最小频率等可排序指标
- 使用优先队列处理动态选择过程
- 通过反证法验证贪心策略的正确性
- 注意处理边界条件(如空输入、相同权值等)
以上内容涵盖贪心算法的核心要点,通过合理选择贪心策略和正确实现,可以高效解决许多优化问题。建议结合具体问题练习代码实现,并深入理解各经典问题的证明过程。
贪心算法题目与解析
题目1:分发饼干
问题描述:假设你是一位家长,要给孩子们分发饼干。每个孩子最多只能给一块饼干。孩子 i 的胃口值为 g[i],饼干 j 的尺寸为 s[j]。如果 s[j] >= g[i],可以将饼干 j 分配给孩子 i。求最多能满足多少个孩子。
思考方式:
- 将孩子的胃口值和饼干尺寸分别排序。
- 从小到大尝试用最小的饼干满足最小的胃口。
- 双指针遍历两个数组,统计满足条件的数量。
C++代码:
#include <vector>
#include <algorithm>
using namespace std;
int findContentChildren(vector<int>& g, vector<int>& s) {
sort(g.begin(), g.end());
sort(s.begin(), s.end());
int i = 0, j = 0;
while (i < g.size() && j < s.size()) {
if (s[j] >= g[i]) {
i++;
}
j++;
}
return i;
}
题目2:跳跃游戏
问题描述:给定一个非负整数数组 nums,初始位于数组的第一个下标。数组中的每个元素表示在该位置可以跳跃的最大长度。判断是否能够到达最后一个下标。
思考方式:
- 维护一个变量
max_reach表示当前能到达的最远位置。 - 遍历数组,更新
max_reach。 - 如果
max_reach超过或等于最后一个下标,返回true。
C++代码:
#include <vector>
using namespace std;
bool canJump(vector<int>& nums) {
int max_reach = 0;
for (int i = 0; i < nums.size(); ++i) {
if (i > max_reach) return false;
max_reach = max(max_reach, i + nums[i]);
if (max_reach >= nums.size() - 1) return true;
}
return true;
}
题目3:买卖股票的最佳时机 II
问题描述:给定一个数组 prices,其中 prices[i] 是某支股票第 i 天的价格。可以多次买卖股票,但必须卖出后才能再次买入。计算能获得的最大利润。
思考方式:
- 只要今天的价格比昨天高,就进行买卖操作。
- 累加所有正利润。
C++代码:
#include <vector>
using namespace std;
int maxProfit(vector<int>& prices) {
int profit = 0;
for (int i = 1; i < prices.size(); ++i) {
if (prices[i] > prices[i - 1]) {
profit += prices[i] - prices[i - 1];
}
}
return profit;
}
题目4:无重叠区间
问题描述:给定一个区间的集合 intervals,其中 intervals[i] = [start_i, end_i]。找到需要移除区间的最小数量,使剩余区间互不重叠。
思考方式:
- 按区间结束时间排序。
- 选择结束时间最早的区间,然后跳过所有与之重叠的区间。
- 统计需要移除的区间数量。
C++代码:
#include <vector>
#include <algorithm>
using namespace std;
int eraseOverlapIntervals(vector<vector<int>>& intervals) {
if (intervals.empty()) return 0;
sort(intervals.begin(), intervals.end(), [](const vector<int>& a, const vector<int>& b) {
return a[1] < b[1];
});
int count = 0;
int end = intervals[0][1];
for (int i = 1; i < intervals.size(); ++i) {
if (intervals[i][0] < end) {
count++;
} else {
end = intervals[i][1];
}
}
return count;
}
总结
贪心算法的核心是每一步选择当前最优解,最终希望得到全局最优解。关键在于证明贪心策略的正确性。以上题目涵盖了贪心算法的典型应用场景,包括分配问题、区间问题和动态规划中的贪心选择。

1万+

被折叠的 条评论
为什么被折叠?



