当前位置: 首页 > news >正文

贪心算法教程(个人总结版)

背景

贪心算法(Greedy Algorithm)是一种在每一步选择中都采取在当前状态下最好或最优的选择,期望通过局部最优选择达到全局最优解决方案的算法。贪心算法的应用广泛,包括图算法、动态规划、贪心选择、装载问题等。它通常用于解决优化问题,例如最短路径、最小生成树、背包问题等。

贪心算法的基本思想

贪心算法的核心思想是,在每一步都选择当前最优解,以期最终达到全局最优。贪心算法通常包括以下几个要素:

  1. 贪心选择性质:可以通过局部最优选择构造出全局最优解。
  2. 最优子结构性质:一个问题的最优解包含其子问题的最优解。

贪心算法的应用

贪心算法在许多经典问题中有着广泛的应用,如:

  1. 活动选择问题:选择不重叠的最大活动集合。
  2. 背包问题:选择最大价值的物品装入背包。
  3. 哈夫曼编码:构造最优前缀码。
  4. 最小生成树问题:如Prim算法和Kruskal算法。
  5. 最短路径问题:如Dijkstra算法。

贪心算法的实现

1. 活动选择问题

问题描述

给定一组活动,每个活动有一个开始时间和结束时间。要求选择尽可能多的互不重叠的活动。

贪心策略

每次选择结束时间最早且不与已选活动重叠的活动。

算法实现
def activity_selection(activities):# 按照结束时间排序activities.sort(key=lambda x: x[1])# 选择活动selected_activities = []last_end_time = 0for activity in activities:if activity[0] >= last_end_time:selected_activities.append(activity)last_end_time = activity[1]return selected_activities# 示例
activities = [(1, 4), (3, 5), (0, 6), (5, 7), (3, 8), (5, 9), (6, 10), (8, 11), (8, 12), (2, 13), (12, 14)]
selected_activities = activity_selection(activities)
print("选择的活动:", selected_activities)
详细解释
  1. 排序:首先按照活动的结束时间对活动进行排序。
  2. 选择活动:遍历排序后的活动列表,每次选择第一个不与已选择活动重叠的活动。
  3. 更新结束时间:每次选择一个活动后,更新最后选择的活动的结束时间。

2. 背包问题

背包问题是经典的优化问题之一,其中包括0-1背包问题和分数背包问题。贪心算法主要适用于分数背包问题。

分数背包问题

分数背包问题允许将物品分割,目的是在总重量不超过背包容量的情况下,选择最大价值的物品集合。

贪心策略

每次选择单位重量价值最高的物品。

算法实现
def fractional_knapsack(items, capacity):# 计算单位重量价值items.sort(key=lambda x: x[1] / x[0], reverse=True)total_value = 0for weight, value in items:if capacity >= weight:total_value += valuecapacity -= weightelse:total_value += value * (capacity / weight)breakreturn total_value# 示例
items = [(2, 10), (3, 5), (5, 15), (7, 7), (1, 6), (4, 18), (1, 3)]
capacity = 15
max_value = fractional_knapsack(items, capacity)
print("最大价值:", max_value)
详细解释
  1. 排序:按照物品的单位重量价值排序。
  2. 选择物品:遍历排序后的物品列表,每次选择单位重量价值最高的物品,直到背包装满。
  3. 处理剩余空间:如果剩余容量小于当前物品的重量,则只取一部分物品。

3. 哈夫曼编码

哈夫曼编码是一种用于数据压缩的贪心算法。

问题描述

给定一组字符及其频率,构造一棵哈夫曼树,使得字符的平均编码长度最短。

贪心策略

每次选择频率最小的两个节点合并。

算法实现
import heapqclass Node:def __init__(self, freq, symbol, left=None, right=None):self.freq = freqself.symbol = symbolself.left = leftself.right = rightself.huff = ''def __lt__(self, nxt):return self.freq < nxt.freqdef huffman_coding(symbols):heap = [Node(freq, symbol) for symbol, freq in symbols]heapq.heapify(heap)while len(heap) > 1:left = heapq.heappop(heap)right = heapq.heappop(heap)left.huff = '0'right.huff = '1'new_node = Node(left.freq + right.freq, left.symbol + right.symbol, left, right)heapq.heappush(heap, new_node)return heap[0]def print_huffman_tree(node, val=''):new_val = val + node.huffif node.left:print_huffman_tree(node.left, new_val)if node.right:print_huffman_tree(node.right, new_val)if not node.left and not node.right:print(f"{node.symbol}: {new_val}")# 示例
symbols = [('A', 5), ('B', 9), ('C', 12), ('D', 13), ('E', 16), ('F', 45)]
huffman_tree = huffman_coding(symbols)
print_huffman_tree(huffman_tree)
详细解释
  1. 初始化:将每个字符及其频率创建为一个节点,并加入优先队列(最小堆)。
  2. 合并节点:每次从堆中取出频率最小的两个节点,合并为一个新的节点,将新节点加入堆中。
  3. 构建哈夫曼树:重复上述过程,直到堆中只剩一个节点,这个节点即为哈夫曼树的根节点。
  4. 生成编码:从根节点开始,左子树路径为'0',右子树路径为'1',遍历树生成每个字符的哈夫曼编码。

4. 最小生成树问题

最小生成树问题是图论中的经典问题之一,常用的贪心算法有Prim算法和Kruskal算法。

Prim算法

Prim算法用于找到一个连通图的最小生成树,选择从某个顶点开始,每次选择与当前树相连的权重最小的边。

算法实现
import heapqdef prim(graph, start):mst = []visited = set()min_heap = [(0, start)]while min_heap:weight, node = heapq.heappop(min_heap)if node not in visited:visited.add(node)mst.append((weight, node))for next_node, next_weight in graph[node]:if next_node not in visited:heapq.heappush(min_heap, (next_weight, next_node))return mst# 示例
graph = {'A': [('B', 1), ('C', 3), ('D', 4)],'B': [('A', 1), ('C', 2), ('D', 5)],'C': [('A', 3), ('B', 2), ('D', 6)],'D': [('A', 4), ('B', 5), ('C', 6)]
}
mst = prim(graph, 'A')
print("最小生成树:", mst)
详细解释
  1. 初始化:从起始顶点开始,将所有相邻边加入优先队列(最小堆)。
  2. 选择边:每次选择权重最小的边,若边的终点未被访问过,则将其加入生成树,并将该顶点的所有相邻边加入堆中。
  3. 重复步骤:直到所有顶点都被访问过,生成树构建完成。

5. 最短路径问题

最短路径问题是图论中的另一个经典问题,Dijkstra算法是常用的贪心算法之一。

Dijkstra算法

Dijkstra算法用于找到从单个源点到所有其他顶点的最短路径,每次选择当前已知最短路径的顶点,并更新其邻接顶点的距离。

算法实现
import heapqdef dijkstra(graph, start):distances = {vertex: float('infinity') for vertex in graph}distances[start] = 0priority_queue = [(0, start)]while priority_queue:current_distance, current_vertex = heapq.heappop(priority_queue)if current_distance > distances[current_vertex]:continuefor neighbor, weight in graph[current_vertex]:distance = current_distance + weightif distance < distances[neighbor]:distances[neighbor] = distanceheapq.heappush(priority_queue, (distance, neighbor))return distances# 示例
graph = {'A': [('B', 1), ('C', 4)],'B': [('A', 1), ('C', 2), ('D', 5)],'C': [('A', 4), ('B', 2), ('D', 1)],'D': [('B', 5), ('C', 1)]
}
distances = dijkstra(graph, 'A')
print("最短路径:", distances)
详细解释
  1. 初始化:设置所有顶点到源点的初始距离为无穷大,源点到自身距离为0,将源点加入优先队列。
  2. 选择顶点:每次选择距离最小的顶点,若当前顶点的距离已被更新,则跳过。
  3. 更新邻接顶点的距离:对于当前顶点的每个邻接顶点,计算从源点到该邻接顶点的距离,若新距离小于当前已知距离,则更新并将其加入优先队列。
  4. 重复步骤:直到优先队列为空,所有顶点的最短路径计算完成。

贪心算法的优缺点

优点

  1. 简单易懂:贪心算法的思想简单明了,容易理解和实现。
  2. 高效:贪心算法通常具有较低的时间复杂度,适合处理大规模数据。
  3. 适用于某些特定问题:在一些特定问题中,贪心算法可以快速找到最优解,如最小生成树、最短路径等。

缺点

  1. 局部最优不保证全局最优:贪心算法通过局部最优选择来构建全局解,但在某些情况下,局部最优选择可能导致最终解并非全局最优。
  2. 问题依赖性强:贪心算法适用于特定问题,不能普遍适用于所有问题。

结论

贪心算法是一种强大而高效的算法,广泛应用于各种优化问题中。通过对贪心选择性质和最优子结构性质的理解,可以设计出适合特定问题的贪心算法。在实践中,应根据具体问题的特点选择合适的算法,以充分发挥其优势。

通过本教程的详细介绍和代码示例,希望您对贪心算法有了更深入的理解,并能够在实际项目中应用这些技术。

相关文章:

  • 开源模型应用落地-语音转文本-whisper模型-AIGC应用探索(二)
  • 最佳 Mac 数据恢复:恢复 Mac 上已删除的文件
  • MySQL各种锁
  • 低功耗蓝牙模块在便携式医疗设备上的应用前景
  • uniapp的tooltip功能放到表单laber
  • 2024中国军民两用智能装备与通信技术产业展览会带你走进轻元素量子材料世界
  • 【html知识】html中常用的表单元素+css格式美化
  • 如何利用向量数据库来弥补 LLM 的弱点
  • 基于Linux的文件操作(socket操作)
  • JDBC常见异常(10)—预编译模式下占位符动态排序字段失效
  • Kotlin 类型别名
  • Linux:subshell(子shell)和childprocess(子进程)
  • 工业相机识别电路板元器件:彩色与黑白的区别
  • 束测后台实操文档2-OpenWrt
  • 基于深度学习的模糊认知图方法
  • 【译】React性能工程(下) -- 深入研究React性能调试
  • AWS实战 - 利用IAM对S3做访问控制
  • Java精华积累:初学者都应该搞懂的问题
  • JDK9: 集成 Jshell 和 Maven 项目.
  • JS实现简单的MVC模式开发小游戏
  • SAP云平台运行环境Cloud Foundry和Neo的区别
  • Vue官网教程学习过程中值得记录的一些事情
  • vue学习系列(二)vue-cli
  • Windows Containers 大冒险: 容器网络
  • 数据库写操作弃用“SELECT ... FOR UPDATE”解决方案
  • 算法-插入排序
  • 微信支付JSAPI,实测!终极方案
  • ​configparser --- 配置文件解析器​
  • ​决定德拉瓦州地区版图的关键历史事件
  • ​中南建设2022年半年报“韧”字当头,经营性现金流持续为正​
  • #LLM入门|Prompt#3.3_存储_Memory
  • (14)目标检测_SSD训练代码基于pytorch搭建代码
  • (day6) 319. 灯泡开关
  • (二) Windows 下 Sublime Text 3 安装离线插件 Anaconda
  • (二)学习JVM —— 垃圾回收机制
  • (附源码)ssm失物招领系统 毕业设计 182317
  • (牛客腾讯思维编程题)编码编码分组打印下标题目分析
  • (五)Python 垃圾回收机制
  • (转)自己动手搭建Nginx+memcache+xdebug+php运行环境绿色版 For windows版
  • ./configure,make,make install的作用
  • .NET 6 Mysql Canal (CDC 增量同步,捕获变更数据) 案例版
  • .NET 设计一套高性能的弱事件机制
  • .NET企业级应用架构设计系列之开场白
  • .Net下使用 Geb.Video.FFMPEG 操作视频文件
  • /run/containerd/containerd.sock connect: connection refused
  • @reference注解_Dubbo配置参考手册之dubbo:reference
  • @Validated和@Valid校验参数区别
  • [ web基础篇 ] Burp Suite 爆破 Basic 认证密码
  • [ 常用工具篇 ] POC-bomber 漏洞检测工具安装及使用详解
  • [ 蓝桥杯Web真题 ]-布局切换
  • [20190416]完善shared latch测试脚本2.txt
  • [AX]AX2012 AIF(四):文档服务应用实例
  • [C#]winform使用引导APSF和梯度自适应卷积增强夜间雾图像的可见性算法实现夜间雾霾图像的可见度增强
  • [Docker]十二.Docker consul集群搭建、微服务部署,Consul集群+Swarm集群部署微服务实战
  • [hdu 2896] 病毒侵袭 [ac自动机][病毒特征码匹配]