C++贪心全解

以下为C++贪心算法详解,包含核心思想、经典问题分析及代码实现,内容结构清晰:

贪心算法基本概念

贪心算法(Greedy Algorithm)通过局部最优选择达到全局最优解,适用于具有最优子结构的问题。其核心特点是不可回溯,一旦做出选择就不再改变。

与动态规划相比,贪心算法不保存子问题解,通常时间复杂度更低,但需要严格证明贪心策略的正确性。典型应用场景包括最短路径、任务调度等问题。

算法设计要素

  1. 贪心选择性质:每一步的局部最优选择能导致全局最优解
  2. 最优子结构:问题的最优解包含子问题的最优解
  3. 无后效性:当前选择不影响后续决策的可行性

经典问题解析

活动选择问题

选择最多数量的互不重叠活动:

#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;
}

算法证明方法

  1. 交换论证法:通过比较任意解与贪心解的差异进行证明
  2. 数学归纳法:验证贪心选择在各阶段保持最优性
  3. 决策包容性:证明最优解包含贪心选择

典型应用场景

  • 最小生成树(Prim/Kruskal算法)
  • 单源最短路径(Dijkstra算法)
  • 分数背包问题
  • 任务调度问题
  • 硬币找零问题(特定面值情况)

复杂度分析

贪心算法的时间复杂度通常由排序步骤决定:

  • 活动选择:O(n log n)
  • 霍夫曼编码:O(n log n)
  • 最小生成树:O(E log V)

空间复杂度一般为O(n)或O(1),取决于是否需要额外存储结构。

局限性

  1. 不适用于需要全局考虑的问题
  2. 无法保证所有问题的最优解
  3. 需要严格的数学证明支持
  4. 对输入数据有特定要求(如背包问题的分数形式)

实战技巧

  1. 优先考虑结束时间/最小频率等可排序指标
  2. 使用优先队列处理动态选择过程
  3. 通过反证法验证贪心策略的正确性
  4. 注意处理边界条件(如空输入、相同权值等)

以上内容涵盖贪心算法的核心要点,通过合理选择贪心策略和正确实现,可以高效解决许多优化问题。建议结合具体问题练习代码实现,并深入理解各经典问题的证明过程。

贪心算法题目与解析

题目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;
}

总结

贪心算法的核心是每一步选择当前最优解,最终希望得到全局最优解。关键在于证明贪心策略的正确性。以上题目涵盖了贪心算法的典型应用场景,包括分配问题、区间问题和动态规划中的贪心选择。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

墨染千千秋

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值