news 2026/1/21 7:27:36

贪心与动态规划算法:原理、对比与 C++ 实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
贪心与动态规划算法:原理、对比与 C++ 实现

算法设计中,贪心算法和动态规划是解决最优化问题的两大核心思想。二者均聚焦于 “最优解”,但解题策略截然不同:贪心追求 “局部最优” 以逼近全局最优,动态规划则通过 “记录子问题解” 避免重复计算,确保全局最优。本文将从原理、适用场景、经典案例出发,结合 C++ 代码实现,深入剖析两种算法的核心逻辑与应用差异。

一、核心概念梳理

1.1 贪心算法(Greedy Algorithm)

核心思想:每一步都做出当前状态下的最优选择(局部最优),且一旦选择便不可回溯,最终期望通过一系列局部最优决策得到全局最优解。关键条件:问题需满足「贪心选择性质」(局部最优能推导出全局最优)和「最优子结构」(问题的最优解包含子问题的最优解)。优缺点

  • 优点:时间复杂度低,实现简单;
  • 缺点:并非所有问题都适用,若局部最优无法导向全局最优,则贪心失效。

1.2 动态规划(Dynamic Programming, DP)

核心思想:将复杂问题拆解为若干重叠子问题,通过记录子问题的最优解(DP 表),避免重复计算,最终合并子问题解得到全局最优。关键条件:问题需满足「最优子结构」和「子问题重叠性」(子问题会被多次重复计算)。核心步骤

  1. 定义状态(DP 数组 / 表的含义);
  2. 推导状态转移方程;
  3. 确定初始条件;
  4. 计算最优解(自底向上 / 自顶向下)。优缺点
  • 优点:能保证全局最优,适用范围广;
  • 缺点:空间复杂度较高(需存储子问题解),状态转移方程设计难度大。

二、经典案例对比实现

2.1 案例 1:找零钱问题(贪心 vs 动态规划)

问题描述

给定不同面额的硬币coins和一个总金额amount,求凑成该金额所需的最少硬币数。若无法凑出,返回 - 1。

场景 1:硬币面额满足贪心性质(如 [1,5,10,25])

当硬币面额为规范的整数倍(如美元、人民币)时,贪心算法可直接取最大面额硬币,逐步减少金额,得到最优解。

贪心实现(C++)

cpp

运行

#include <iostream> #include <vector> #include <algorithm> using namespace std; // 贪心找零:仅适用于硬币面额满足贪心选择性质的场景 int greedyChange(vector<int>& coins, int amount) { // 先将硬币按面额降序排序 sort(coins.rbegin(), coins.rend()); int count = 0; for (int coin : coins) { if (amount <= 0) break; // 尽可能多取当前最大面额的硬币 int num = amount / coin; count += num; amount -= num * coin; } // 若剩余金额不为0,说明无法凑出 return amount == 0 ? count : -1; } int main() { vector<int> coins = {1,5,10,25}; // 满足贪心性质的面额 int amount = 41; int res = greedyChange(coins, amount); if (res == -1) { cout << "无法凑出金额" << endl; } else { cout << "最少硬币数(贪心):" << res << endl; // 输出:4(25+10+5+1) } return 0; }
场景 2:硬币面额不满足贪心性质(如 [1,3,4])

若硬币面额为[1,3,4],目标金额为 6,贪心会选择4+1+1(3 枚),但最优解是3+3(2 枚)。此时需用动态规划。

动态规划实现(C++)

cpp

运行

#include <iostream> #include <vector> #include <climits> using namespace std; // 动态规划找零:通用解法 int dpChange(vector<int>& coins, int amount) { // 定义dp数组:dp[i]表示凑成金额i所需的最少硬币数 vector<int> dp(amount + 1, INT_MAX); dp[0] = 0; // 初始条件:凑0元需要0枚硬币 // 遍历所有金额,计算每个金额的最少硬币数 for (int i = 1; i <= amount; ++i) { // 遍历所有硬币面额,尝试更新dp[i] for (int coin : coins) { if (i >= coin && dp[i - coin] != INT_MAX) { dp[i] = min(dp[i], dp[i - coin] + 1); } } } return dp[amount] == INT_MAX ? -1 : dp[amount]; } int main() { vector<int> coins = {1,3,4}; // 不满足贪心性质的面额 int amount = 6; int res = dpChange(coins, amount); if (res == -1) { cout << "无法凑出金额" << endl; } else { cout << "最少硬币数(DP):" << res << endl; // 输出:2 } return 0; }

2.2 案例 2:最长递增子序列(LIS)—— 动态规划经典应用

贪心虽可优化 LIS 的时间复杂度,但基础解法为动态规划。

问题描述

给定一个整数数组nums,找到其中最长严格递增子序列的长度。

动态规划实现(C++)

cpp

运行

#include <iostream> #include <vector> #include <algorithm> using namespace std; // 动态规划解LIS:时间复杂度O(n²) int lengthOfLIS(vector<int>& nums) { int n = nums.size(); if (n == 0) return 0; // 定义dp数组:dp[i]表示以nums[i]结尾的最长递增子序列长度 vector<int> dp(n, 1); // 初始条件:每个元素自身是长度为1的子序列 int maxLen = 1; for (int i = 1; i < n; ++i) { // 遍历i之前的所有元素,更新dp[i] for (int j = 0; j < i; ++j) { if (nums[i] > nums[j]) { dp[i] = max(dp[i], dp[j] + 1); } } maxLen = max(maxLen, dp[i]); } return maxLen; } // 贪心+二分优化LIS:时间复杂度O(n log n) int lengthOfLIS_Greedy(vector<int>& nums) { vector<int> tails; // tails[i]表示长度为i+1的递增子序列的最小末尾元素 for (int num : nums) { // 二分查找第一个大于等于num的位置 auto it = lower_bound(tails.begin(), tails.end(), num); if (it == tails.end()) { tails.push_back(num); // 可以延长子序列 } else { *it = num; // 替换,优化末尾元素,为后续更长序列做准备 } } return tails.size(); } int main() { vector<int> nums = {10,9,2,5,3,7,101,18}; cout << "LIS长度(DP):" << lengthOfLIS(nums) << endl; // 输出:4 cout << "LIS长度(贪心+二分):" << lengthOfLIS_Greedy(nums) << endl; // 输出:4 return 0; }

2.3 案例 3:活动选择问题 —— 贪心经典应用

问题描述

给定多个活动的开始时间和结束时间,选择最多的互不重叠活动。

贪心实现(C++)

cpp

运行

#include <iostream> #include <vector> #include <algorithm> using namespace std; struct Activity { int start; int end; }; // 贪心策略:选择结束时间最早的活动 int activitySelection(vector<Activity>& acts) { // 按结束时间升序排序 sort(acts.begin(), acts.end(), [](const Activity& a, const Activity& b) { return a.end < b.end; }); int count = 1; // 至少选第一个活动 int lastEnd = acts[0].end; // 遍历剩余活动,选择不重叠的 for (int i = 1; i < acts.size(); ++i) { if (acts[i].start >= lastEnd) { count++; lastEnd = acts[i].end; } } return count; } int main() { vector<Activity> acts = {{1,2}, {3,4}, {0,6}, {5,7}, {8,9}, {5,9}}; cout << "最多可选择的活动数:" << activitySelection(acts) << endl; // 输出:4 return 0; }

三、贪心与动态规划的核心差异

维度贪心算法动态规划
决策方式局部最优,一步到位,不可回溯全局最优,记录子问题解,逐步推导
子问题关系子问题无重叠,独立决策子问题重叠,需复用解
适用场景满足贪心选择性质的问题最优子结构 + 子问题重叠的问题
时间复杂度通常更低(如 O (n)、O (n log n))较高(如 O (n²)、O (nm))
空间复杂度通常 O (1) 或 O (n)需存储 DP 表,O (n) 或 O (nm)
最优性不一定全局最优保证全局最优

四、算法选择策略

  1. 优先尝试贪心:若问题满足 “每一步局部最优能推导出全局最优”(如活动选择、哈夫曼编码),优先用贪心,实现简单且效率高;
  2. 贪心失效用 DP:若局部最优无法得到全局最优(如非规范面额找零、LIS),或问题存在重叠子问题,选择动态规划;
  3. 特殊场景优化:部分问题(如 LIS)可通过 “贪心 + 二分” 优化动态规划的时间复杂度,兼顾效率与最优性。

五、总结

贪心算法是 “短视的最优”,依赖问题本身的性质;动态规划是 “全局的最优”,通过记录子问题解避免重复计算。二者均基于 “最优子结构”,但贪心更强调 “贪心选择性质”,动态规划更依赖 “子问题重叠性”。

在实际开发中,需先分析问题特性:若能证明贪心选择的正确性,优先使用贪心;若无法证明,或问题存在重叠子问题,则选择动态规划。掌握两种算法的核心思想与适用场景,是解决算法优化问题的关键。

以上所有代码均可直接编译运行,建议结合具体场景调试,深入理解算法的执行过程与核心逻辑。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/1/18 23:55:55

2025云原生DevOps专家养成指南:从技能构建到职业跃迁

2025云原生DevOps专家养成指南&#xff1a;从技能构建到职业跃迁 【免费下载链接】DevOps-Roadmap DevOps-Roadmap: 是一个关于 DevOps 工程师职业发展和技能提升的路线图。适合 DevOps 工程师和初学者了解 DevOps 行业趋势&#xff0c;学习相关知识和技能。 项目地址: https…

作者头像 李华
网站建设 2026/1/19 23:33:15

windows下串口类封装(亲测好用)

使用示例: // 创建串口对象 CSerial serial;// 打开串口 if (serial.Open(3, 115200)) {qDebug() << "串口打开成功";// 发送数据unsigned char data[] = {0x01, 0x02, 0x03};int sent = serial.SendData(dat

作者头像 李华
网站建设 2026/1/18 22:27:14

技术深度:Infoseek 媒体发布系统的微服务架构与二次开发实战

2025 年 “清朗” 专项行动对媒体发布提出 “AI 内容标注、资质核验、来源追溯” 三大硬性要求&#xff0c;传统发布工具因架构陈旧&#xff0c;面临 “合规功能缺失、多平台适配差、响应延迟高” 的技术瓶颈。字节探索 Infoseek 基于 “微服务 AI 大模型” 构建全链路发布系统…

作者头像 李华
网站建设 2026/1/21 2:35:00

SpringBoot3实战:SSE实时推送

前言 在当今互联网软件开发的浪潮中&#xff0c;实时数据交互已成为众多应用不可或缺的特性。对于互联网软件开发人员而言&#xff0c;如何高效地实现数据从后端实时推送到前端&#xff0c;是一个值得深入探讨的问题。今天&#xff0c;我们就来聚焦于在 Spring Boot3 框架下&a…

作者头像 李华
网站建设 2026/1/21 22:11:13

快速上手:5分钟部署Nginx LDAP认证系统

快速上手&#xff1a;5分钟部署Nginx LDAP认证系统 【免费下载链接】nginx-ldap-auth Example of LDAP authentication using ngx_http_auth_request_module 项目地址: https://gitcode.com/gh_mirrors/ng/nginx-ldap-auth 在现代企业Web应用中&#xff0c;安全身份验证…

作者头像 李华