贪心算法(Greedy Algorithm):通过局部最优达成全局最优的决策策略

贪心算法是一种通过每次选择局部最优解来期望全局最优解的算法思想。它不考虑未来的影响,仅根据当前信息做出最优选择,适用于具有贪心选择性质最优子结构的问题。与动态规划相比,贪心算法更简单高效,但适用范围更窄。

一、贪心算法的核心要素
  1. 贪心选择性质:全局最优解可通过一系列局部最优选择(贪心选择)达成。即每次选择的局部最优解,最终能累积成全局最优解。

  2. 最优子结构:问题的最优解包含子问题的最优解(与动态规划相同)。

关键区别

  • 贪心算法:自顶向下,每次做贪心选择后,仅留下一个子问题需要解决。
  • 动态规划:自底向上或自顶向下,需考虑所有子问题并存储其解。
二、经典应用:活动选择问题

问题:有n个活动,每个活动有开始时间start[i]和结束时间end[i],求最多能参加的不重叠活动数量。

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

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;// 活动结构:开始时间和结束时间
struct Activity {int start;int end;
};// 按结束时间排序
bool compare(const Activity& a, const Activity& b) {return a.end < b.end;
}// 选择最多不重叠活动
int maxActivities(vector<Activity>& activities) {if (activities.empty()) return 0;// 1. 按结束时间排序(贪心选择的前提)sort(activities.begin(), activities.end(), compare);int count = 1; // 至少选择第一个活动int lastEnd = activities[0].end;// 2. 依次选择不重叠的活动for (int i = 1; i < activities.size(); i++) {// 若当前活动开始时间 >= 上一个活动结束时间,可选择if (activities[i].start >= lastEnd) {count++;lastEnd = activities[i].end;}}return count;
}int main() {vector<Activity> activities = {{1, 4}, {3, 5}, {0, 6}, {5, 7}, {3, 9}, {5, 9}, {6, 10}, {8, 11}, {8, 12}, {2, 14}, {12, 16}};cout << "最多可参加的活动数:" << maxActivities(activities) << endl; // 输出4(如{1,4}, {5,7}, {8,11}, {12,16})return 0;
}

算法分析

  • 排序时间O(n log n),选择过程O(n),总时间O(n log n)。
  • 正确性:通过数学归纳法可证明,选择最早结束的活动能获得最优解。
三、经典应用:哈夫曼编码(Huffman Coding)

问题:为字符设计变长编码,出现频率高的字符用短编码,频率低的用长编码,实现数据压缩(无歧义解码)。

贪心策略:每次选择频率最低的两个节点合并为新节点,重复至只剩一个节点(哈夫曼树),路径左0右1即为编码。

#include <iostream>
#include <vector>
#include <queue>
#include <unordered_map>
using namespace std;// 哈夫曼树节点
struct HuffmanNode {char data;          // 字符(叶子节点)int freq;           // 频率HuffmanNode *left, *right;HuffmanNode(char d, int f) : data(d), freq(f), left(nullptr), right(nullptr) {}
};// 优先队列比较器(最小堆,频率低的节点优先)
struct Compare {bool operator()(HuffmanNode* a, HuffmanNode* b) {return a->freq > b->freq; // 小顶堆(默认是大顶堆,需反向)}
};// 递归生成哈夫曼编码
void generateCodes(HuffmanNode* root, string code, unordered_map<char, string>& codes) {if (!root) return;// 叶子节点:记录编码if (!root->left && !root->right) {codes[root->data] = code.empty() ? "0" : code; // 处理只有一个字符的情况return;}// 左子树加"0",右子树加"1"generateCodes(root->left, code + "0", codes);generateCodes(root->right, code + "1", codes);
}// 构建哈夫曼树并生成编码
unordered_map<char, string> huffmanCoding(unordered_map<char, int>& freq) {// 1. 创建叶子节点,加入最小堆priority_queue<HuffmanNode*, vector<HuffmanNode*>, Compare> minHeap;for (auto& p : freq) {minHeap.push(new HuffmanNode(p.first, p.second));}// 2. 合并节点直至只剩一个根节点while (minHeap.size() > 1) {// 取出两个频率最低的节点HuffmanNode* left = minHeap.top(); minHeap.pop();HuffmanNode* right = minHeap.top(); minHeap.pop();// 合并为新节点(数据用特殊符号,频率为两者之和)HuffmanNode* merged = new HuffmanNode('$', left->freq + right->freq);merged->left = left;merged->right = right;minHeap.push(merged);}// 3. 生成编码unordered_map<char, string> codes;generateCodes(minHeap.top(), "", codes);return codes;
}int main() {unordered_map<char, int> freq = {{'a', 5}, {'b', 9}, {'c', 12}, {'d', 13}, {'e', 16}, {'f', 45}};auto codes = huffmanCoding(freq);cout << "哈夫曼编码:" << endl;for (auto& p : codes) {cout << p.first << ": " << p.second << endl;}// 输出示例(可能因实现细节略有不同):// f: 0// c: 100// d: 101// a: 1100// b: 1101// e: 111return 0;
}

算法分析

  • 时间复杂度O(n log n)(n个节点,每次堆操作O(log n))。
  • 正确性:哈夫曼编码是最优前缀码,总编码长度最短。
四、经典应用:零钱兑换(贪心适用场景)

问题:用最少的硬币凑出指定金额,硬币面额为{25, 10, 5, 1}。

贪心策略:每次选择最大面额的硬币,直至金额为0。

int coinChangeGreedy(int amount, vector<int>& coins) {sort(coins.rbegin(), coins.rend()); // 面额从大到小排序int count = 0;for (int coin : coins) {while (amount >= coin) {amount -= coin;count++;}if (amount == 0) break;}return amount == 0 ? count : -1; // 无法凑出返回-1
}

局限性:仅适用于“大面额是小面额倍数”的情况(如美元硬币)。对于{1, 3, 4}面额凑6,贪心会选4+1+1(3枚),但最优解是3+3(2枚),此时需用动态规划。

五、贪心算法 vs 动态规划
特性贪心算法动态规划
决策方式局部最优(仅看当前)全局最优(考虑所有子问题)
子问题关系贪心选择后仅一个子问题需解决多个重叠子问题
时间复杂度通常更低(如O(n log n))较高(如O(n²)或O(nW))
适用场景具有贪心选择性质的问题所有具有最优子结构的问题
典型例子活动选择、哈夫曼编码、最小生成树背包问题、LCS、最长递增子序列
六、贪心算法的优缺点

优点

  • 实现简单,时间效率高(通常为线性或线性对数级)。
  • 空间开销小(无需存储子问题解)。

缺点

  • 适用范围有限(仅能解决具有贪心选择性质的问题)。
  • 无法回溯,一旦做出选择就无法更改,可能错过全局最优解。
七、如何判断问题是否适合贪心算法
  1. 尝试构造反例:假设贪心策略不成立,能否找到反例?
    例如:零钱兑换问题中,若存在非倍数面额,贪心可能失效。

  2. 证明贪心选择性质
    假设全局最优解中不包含贪心选择的元素,能否通过替换证明存在更优解?若不能,则贪心策略有效。

  3. 验证最优子结构:问题的最优解包含子问题的最优解。

总结

贪心算法是一种直观高效的算法思想,通过每次选择局部最优解来逼近全局最优解。它特别适合解决资源分配、调度、编码等具有贪心选择性质的问题,如活动选择、哈夫曼编码、最小生成树(Kruskal和Prim算法)等。

尽管贪心算法的适用范围不如动态规划广泛,但其简单性和高效性使其在许多场景中成为首选。掌握贪心算法的关键在于:

  1. 识别问题是否具有贪心选择性质和最优子结构。
  2. 设计合理的贪心策略(如按结束时间排序、选最大面额等)。
  3. 通过数学证明或反例验证策略的正确性。

在实际开发中,贪心算法常与其他算法结合使用(如贪心+动态规划),以平衡效率和通用性。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/news/921665.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/921665.shtml
英文地址,请注明出处:http://en.pswp.cn/news/921665.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

LangChain实战(二十一):构建自动化AI客服系统

本文是《LangChain实战课》系列的第二十一篇,将带领您构建一个完整的自动化AI客服系统。通过结合对话记忆、工具调用和业务知识库,我们将创建一个能够处理复杂客户查询的智能客服解决方案。 前言 在现代商业环境中,客户服务是企业成功的关键因素之一。传统客服系统往往面临…

一人公司智能管理系统概述

系统概述 项目结构 Al_Compny系统采用前后端分离的全栈架构&#xff0c;项目根目录下包含两个主要子目录&#xff1a;Al_Compny_backend&#xff08;后端服务&#xff09;和Al_Compny_frontend&#xff08;前端应用&#xff09;。核心功能模块 Al_Compny系统是一个面向"一…

OpenWrt | 在 PPP 拨号模式下启用 IPv6 功能

文章目录一、WAN 口配置二、LAN 口配置三、IPv6 测试本文将详细介绍 将光猫的网络模式改成桥接之后使用路由器拨号的上网方式的情况下&#xff0c;在 OpenWrt 上使用 PPP 拨号模式上网时&#xff0c;启用 IPv6 功能的方法。 一、WAN 口配置 首先&#xff0c;我们需要在 网络 …

Java如何实现一个安全的登录功能?

安全登录系统完整教程 &#x1f4cb; 目录 项目概述技术栈安全特性项目结构核心组件详解安全实现原理部署和运行安全最佳实践常见问题解答进阶扩展 &#x1f3af; 项目概述 这是一个基于Spring Boot和Spring Security的完整安全登录系统&#xff0c;专为初学者设计&#xff…

星辰诞愿——生日快乐

前言 今天这篇博客并非技术文章&#xff0c;而是庆祝我可爱的妹妹18岁生日以及介绍我半年以来的学习经历 祝生网站&#xff1a;星辰诞愿(用户列表里第一位就是我妹妹&#xff0c;希望大家能献上自己的祝福&#xff0c;能分享转发更好&#xff0c;我在此感谢大家。如果使用手机&…

基于STM32单片机的智能粮仓温湿度检测蓝牙手机APP设计

基于STM32单片机的智能粮仓温湿度检测蓝牙手机APP设计 1 系统功能介绍 本系统是一款基于STM32单片机的智能粮仓环境监测与控制装置&#xff0c;核心目标是通过传感器实时采集粮仓内的温度和湿度信息&#xff0c;并结合蓝牙通信模块将数据传输至手机端&#xff0c;实现对粮仓环境…

简单视频转换器 avi转mp4

直接上代码package com.example.videoconverter;import ws.schild.jave.Encoder; import ws.schild.jave.EncoderException; import ws.schild.jave.MultimediaObject; import ws.schild.jave.encode.AudioAttributes; import ws.schild.jave.encode.EncodingAttributes; impor…

Kafka 与 RocketMQ 核心概念与架构对比

Kafka 与 RocketMQ 核心概念与架构对比DeepSeek生成&#xff0c;便于记忆大概逻辑核心概念对比图 #mermaid-svg-dEbo1XpAjfzOjvUW {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-dEbo1XpAjfzOjvUW .error-icon{fill…

30分钟深度压测cuBLAS:从FP64到INT8全精度性能剖析

在深度学习和高性能计算领域&#xff0c;GPU的矩阵运算性能是衡量系统算力的核心指标之一。NVIDIA的cuBLAS库作为CUDA平台上最基础的线性代数计算库&#xff0c;其性能表现直接影响着上层应用的运行效率。本文将详细介绍如何使用cublasmatmulbench工具对多GPU进行全面的性能基准…

超越模仿:探寻智能的本源

引言&#xff1a;超越模仿&#xff0c;探寻智能的本源近年来&#xff0c;以大语言模型&#xff08;LLM&#xff09;为代表的自然语言处理&#xff08;NLP&#xff09;技术&#xff0c;在模仿人类语言生成方面取得了令人瞩目的成就。从流畅的对话到精炼的文本摘要&#xff0c;机…

ROS/ROS2课程笔记00-大纲-25-26-1

大纲 AI版 以下是基于第四代高校课程核心理念设计的《ROS2机器人程序设计&#xff08;ROS2 Jazzy版&#xff09;》课程大纲&#xff0c;突出智能互联、跨学科融合、终身学习等特征&#xff0c;并融入技术赋能、生态重塑、素养导向等要求&#xff1a; 课程名称&#xff1a;ROS…

Linux内核进程管理子系统有什么第四十六回 —— 进程主结构详解(42)

接前一篇文章&#xff1a;Linux内核进程管理子系统有什么第四十五回 —— 进程主结构详解&#xff08;41&#xff09; 本文内容参考&#xff1a; Linux内核进程管理专题报告_linux rseq-CSDN博客 《趣谈Linux操作系统 核心原理篇&#xff1a;第三部分 进程管理》—— 刘超 《…

Linux网络连接不上?NetworkManager提示“device not managed“!

#操作系统 #Linux #NetworkManager适用环境kylin v10Centos 8Redhat 8一、故障现象在CentOS/RHEL(同样适用于kylin v10&#xff09;系统中&#xff0c;管理员执行 nmcli connection up ens160 命令尝试激活名为 ens160 的网络连接时&#xff0c;遇到以下错误&#xff1a;[roo…

【系统分析师】第2章-基础知识:数学与工程基础(核心总结)

更多内容请见: 备考系统分析师-专栏介绍和目录 文章目录 一、数学统计基础 1.1 概率论基础 1.2 数理统计基础 1.3 常用统计分析方法 二、图论应用 2.1 基本概念 2.2 核心算法与应用 三、预测与决策 3.1 预测方法 3.2 决策方法 四、数学建模 4.1 建模过程 4.2 常用模型类型 五、…

StrUtil.isBlank()

这段代码是一个条件判断&#xff0c;用于检查变量 shopJson 是否为空或空白&#xff0c;如果是&#xff0c;就直接返回 null。我们来逐句讲解&#xff1a;原始代码&#xff1a; if(StrUtil.isBlank(shopJson)) {// 3.存在&#xff0c;直接返回return null; }逐句解释&#xff1…

mysql 回表查询(二次查询,如何检查,如何规避)

h5打开以查看 “回表查询”通常发生在使用二级索引&#xff08;Secondary Index&#xff09;的查询中。当查询所需的数据列并不全部包含在二级索引中时&#xff0c;即使使用了索引&#xff0c;MySQL 也需要根据索引记录中的主键值&#xff0c;回到聚簇索引&#xff08;Cluster…

深度学习(二):神经元与神经网络

在人工智能的浪潮中&#xff0c;神经网络&#xff08;Neural Networks&#xff09;无疑是驱动核心技术的引擎&#xff0c;它赋予了计算机前所未有的学习和识别能力。而这一切的起点&#xff0c;是受到生物大脑中基本单元——神经元&#xff08;Neurons&#xff09;的深刻启发。…

JavaScript 行为型设计模式详解

1. 观察者模式1.1. 使用场景观察者模式用于对象间的一对多依赖关系&#xff0c;当一个对象的状态发生变化时&#xff0c;所有依赖于它的对象都能收到通知并自动更新。常用于事件处理、通知系统。在前端中&#xff0c;观察者模式用于实现事件监听、数据绑定等功能。1.2. 代码实现…

指令查找表LUT

本文整理自22. FlexSPI—读写外部SPI NorFlash — [野火]i.MX RT库开发实战指南——基于i.MXRT1052 文档 用作个人学习和分享 指令查找表LUT 访问FLASH存储器通常包含一些读写功能的的控制指令&#xff0c;主控设备可通过这些指令访问FLASH存储器。 为了适应这种需求&#…

uv使用指南

&#x1f680; Python 打包工具 UV 使用指南 UV 是一个用 Rust 编写的极速 Python 包管理器和解析器&#xff0c;旨在成为 pip、pip-tools、virtualenv 等工具的单一替代方案。 &#x1f4cb; 目录 核心概念与设计哲学安装与配置基础使用方法项目管理与工作流高级功能与技巧…