贪心算法

14 阅读4分钟

贪心算法(Greedy Algorithm)  是一种在每一步选择中都采取当前状态下局部最优解的策略,希望通过一系列局部最优选择最终得到全局最优解的算法设计方法。它的核心思想是“短视但高效”——每一步只关注眼前的最佳选择,不回溯、不全局规划。


贪心算法的核心特点

  1. 局部最优选择
    每一步决策都基于当前信息选择最优选项,不考虑未来的潜在影响。
  2. 不可回溯
    一旦做出选择,不会撤销或重新评估之前的决策。
  3. 高效性
    通常时间复杂度较低(如 O(nlog⁡n)O(nlogn)),适合处理大规模数据。
  4. 不保证全局最优
    只有在问题满足特定性质时,贪心算法才能得到全局最优解。

贪心算法的适用条件

贪心算法有效的关键在于问题满足以下两个性质:

  1. 贪心选择性质(Greedy Choice Property)
    通过局部最优选择可以推导出全局最优解。即:每一步的贪心决策不会被后续步骤推翻。
  2. 最优子结构(Optimal Substructure)
    问题的最优解包含其子问题的最优解。例如,若从 AA 到 CC 的最短路径是 A→B→CA→B→C,则 A→BA→B 和 B→CB→C 也必须是各自段的最短路径。

贪心算法 vs. 动态规划

特性贪心算法动态规划
决策依据仅当前局部最优所有子问题的解组合
回溯性无回溯可能需要比较多种子问题的组合
时间复杂度通常更低(如 O(nlog⁡n)O(nlogn))通常较高(如 O(n2)O(n2))
适用问题满足贪心选择性质的问题具有重叠子问题和最优子结构的问题
示例霍夫曼编码、最小生成树(Kruskal)背包问题、最长公共子序列

经典贪心算法示例

1. 活动选择问题

  • 问题:从多个时间重叠的活动中选出最多不冲突的活动。

  • 贪心策略:每次选择结束时间最早的活动,为后续留出更多时间。

  • 代码片段(按结束时间排序后选择):

    function selectActivities(activities) {
        activities.sort((a, b) => a.end - b.end); // 按结束时间排序
        const selected = [activities[0]];
        let lastEnd = activities[0].end;
    
        for (const act of activities.slice(1)) {
            if (act.start >= lastEnd) {
                selected.push(act);
                lastEnd = act.end;
            }
        }
        return selected;
    }
    

2. 霍夫曼编码

  • 问题:用最短二进制编码表示字符,使总编码长度最小。
  • 贪心策略:优先合并频率最低的节点,构建二叉树。
  • 时间复杂度:O(nlog⁡n)O(nlogn)(使用优先队列)。

3. 最小生成树(Kruskal算法)

  • 问题:在连通图中选择边,使所有节点连通且总权重最小。

  • 贪心策略:按权重从小到大选择边,避免形成环。

  • 代码关键步骤

    // 使用并查集(Union-Find)检测环
    edges.sort((a, b) => a.weight - b.weight);
    for (const edge of edges) {
        if (find(parent, edge.u) !== find(parent, edge.v)) {
            union(parent, edge.u, edge.v);
            mst.push(edge);
        }
    }
    

贪心算法的局限性

  1. 不适用于所有问题
    例如,0-1背包问题(物品不可分割)无法用贪心算法得到最优解,但分数背包问题(物品可分割)可以。
  2. 需严格证明正确性
    贪心策略的直观性可能掩盖其潜在错误。例如,若活动选择问题改为“选择时间最短的活动”,则可能无法得到最优解。

如何证明贪心算法的正确性?

  1. 数学归纳法
    证明每一步的贪心选择都能导向全局最优解。
  2. 交换论证
    假设存在一个最优解,通过交换步骤使其逐步与贪心解一致,证明贪心解不劣于最优解。

总结

贪心算法通过局部最优的短视决策,在特定问题中高效达到全局最优。它的威力与风险并存:

  • 优势:简单、高效,适合实时系统或大规模数据。
  • 挑战:必须严格验证问题是否满足贪心选择性质,否则可能得到次优解。

在应用时,始终先问: “当前问题是否能用贪心策略?如何证明?”