贪心算法(Greedy Algorithm)是一种在每一步选择中都采取当前状态下最优(即最有利)的选择,从而希望导致结果是全局最优的算法策略。
贪心算法的核心是"局部最优导致全局最优"。它不像动态规划那样考虑所有可能的子问题,而是通过一系列局部最优选择来构建问题的解。
贪心算法适用于满足以下两个条件的问题:
// 贪心算法伪代码框架
GreedyAlgorithm(problem) {
solution = empty; // 初始化解
while (!problem.isSolved()) {
// 选择当前最优的局部解
bestChoice = selectBestOption(problem.options);
// 应用这个选择
solution.add(bestChoice);
problem.update(bestChoice);
}
return solution;
}
问题描述:用最少数量的硬币凑出指定金额。
#include
#include
#include
using namespace std;
/**
* 贪心算法解决找零钱问题
* @param amount 需要找零的金额
* @param coins 可用的硬币面值数组
* @return 包含所用硬币的vector,如果无法正好找零则返回空vector
*/
vector<int> coinChange(int amount, vector<int>& coins) {
// 将硬币按面值从大到小排序(贪心选择:优先使用大面值硬币)
sort(coins.rbegin(), coins.rend());
vector<int> result; // 存储结果的vector
// 遍历所有硬币面值
for (int coin : coins) {
// 当当前硬币面值小于等于剩余金额时,尽可能多地使用该硬币
while (amount >= coin) {
amount -= coin; // 扣除已使用的硬币金额
result.push_back(coin); // 记录使用的硬币
}
}
// 检查是否正好找零
if (amount != 0) {
cout << "无法正好找零,剩余金额: " << amount << endl;
return {}; // 返回空vector表示无法正好找零
}
return result;
}
int main() {
vector<int> coins = {1, 5, 10, 25}; // 美国常用硬币面值
int amount = 63; // 需要找零63美分
vector<int> result = coinChange(amount, coins);
// 输出结果
if (!result.empty()) {
cout << "找零" << amount << "美分需要的硬币: ";
for (int coin : result) {
cout << coin << " ";
}
cout << "\n总共需要" << result.size() << "枚硬币" << endl;
}
return 0;
}
问题描述:选择最多的不重叠区间。
#include
#include
#include
using namespace std;
/**
* 贪心算法解决区间调度问题(选择最多不重叠区间)
* @param intervals 区间数组,每个区间表示为[start, end]
* @return 可以选择的最大不重叠区间数量
*/
int intervalScheduling(vector<vector<int>>& intervals) {
// 如果区间为空,直接返回0
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 = 1; // 至少可以选择第一个区间
int end = intervals[0][1]; // 第一个区间的结束时间
// 遍历所有区间
for (int i = 1; i < intervals.size(); ++i) {
// 如果当前区间的开始时间 >= 上一个选择区间的结束时间
if (intervals[i][0] >= end) {
count++; // 选择该区间
end = intervals[i][1]; // 更新结束时间
}
// 否则跳过该区间(因为会与已选区间重叠)
}
return count;
}
int main() {
// 测试数据:每个子数组表示一个活动的开始和结束时间
vector<vector<int>> intervals = {
{1, 3}, {2, 4}, {3, 6},
{5, 7}, {8, 9}
};
int maxActivities = intervalScheduling(intervals);
cout << "最多可以安排" << maxActivities << "个不重叠活动" << endl;
return 0;
}
问题描述:构建最优前缀编码。
#include
#include
#include
#include
using namespace std;
// 定义霍夫曼树的节点结构
struct Node {
char ch; // 字符
int freq; // 频率
Node *left; // 左子节点
Node *right; // 右子节点
// 构造函数
Node(char c, int f) : ch(c), freq(f), left(nullptr), right(nullptr) {}
};
// 优先队列的比较函数(按频率从小到大排序)
struct compare {
bool operator()(Node* l, Node* r) {
return l->freq > r->freq;
}
};
/**
* 递归生成霍夫曼编码
* @param root 当前节点
* @param str 当前路径的编码字符串
* @param huffmanCode 存储编码结果的哈希表
*/
void encode(Node* root, string str, unordered_map<char, string>& huffmanCode) {
if (!root) return; // 空节点直接返回
// 如果是叶子节点(存储字符的节点)
if (!root->left && !root->right) {
huffmanCode[root->ch] = str; // 保存字符对应的编码
}
// 递归处理左子树(路径添加"0")
encode(root->left, str + "0", huffmanCode);
// 递归处理右子树(路径添加"1")
encode(root->right, str + "1", huffmanCode);
}
/**
* 构建霍夫曼树并生成编码
* @param text 输入文本
*/
void buildHuffmanTree(string text) {
// 1. 统计字符频率
unordered_map<char, int> freq;
for (char ch : text) {
freq[ch]++;
}
// 2. 创建优先队列(最小堆)
priority_queue<Node*, vector<Node*>, compare> pq;
// 为每个字符创建节点并加入优先队列
for (auto pair : freq) {
pq.push(new Node(pair.first, pair.second));
}
// 3. 构建霍夫曼树(贪心选择:每次合并频率最小的两个节点)
while (pq.size() != 1) {
// 取出频率最小的两个节点
Node* left = pq.top(); pq.pop();
Node* right = pq.top(); pq.pop();
// 创建新节点,频率为两个子节点频率之和
int sum = left->freq + right->freq;
Node* newNode = new Node('\0', sum); // 内部节点字符设为空
newNode->left = left;
newNode->right = right;
// 将新节点加入优先队列
pq.push(newNode);
}
// 4. 获取霍夫曼树的根节点
Node* root = pq.top();
// 5. 生成霍夫曼编码
unordered_map<char, string> huffmanCode;
encode(root, "", huffmanCode);
// 6. 输出编码表
cout << "霍夫曼编码表:" << endl;
for (auto pair : huffmanCode) {
cout << pair.first << " : " << pair.second << endl;
}
// 7. 编码原始文本
string encodedStr;
for (char ch : text) {
encodedStr += huffmanCode[ch];
}
cout << "\n原始文本: " << text << endl;
cout << "编码后的字符串: " << encodedStr << endl;
// 计算压缩率
double originalSize = text.length() * 8; // 假设原始是ASCII编码,每个字符8位
double compressedSize = encodedStr.length();
double ratio = (compressedSize / originalSize) * 100;
cout << "\n压缩率: " << ratio << "%" << endl;
}
int main() {
string text = "hello world";
cout << "构建霍夫曼编码示例:\n" << endl;
buildHuffmanTree(text);
return 0;
}
特性 | 贪心算法 | 动态规划 |
---|---|---|
决策依据 | 当前最优选择 | 所有可能的子问题 |
时间复杂度 | 通常较低 | 通常较高 |
空间复杂度 | 通常较低 | 通常较高 |
适用范围 | 满足贪心选择性质的问题 | 具有最优子结构的问题 |
解的正确性 | 不一定全局最优 | 保证全局最优 |