【贪心算法经典应用】哈夫曼编码原理与算法详解 python

作者介绍:10年大厂数据\经营分析经验,现任大厂数据部门负责人。
会一些的技术:数据分析、算法、SQL、大数据相关、python
欢迎加入社区:码上找工作
作者专栏每日更新:
LeetCode解锁1000题: 打怪升级之旅
python数据分析可视化:企业实战案例
备注说明:方便大家阅读,统一使用python,带必要注释,公众号 数据分析螺丝钉 一起打怪升级

哈夫曼编码是一种广泛使用的数据压缩方法,特别是在无损数据压缩领域。本文将详细介绍哈夫曼编码的原理、算法过程,以及如何使用贪心算法实现这一过程。通过这种方式,我们能有效地理解贪心算法在实际问题解决中的应用。

背景和理论基础

哈夫曼编码由David A. Huffman于1952年提出,它是一种利用字符频率来构造最优前缀码的算法。其核心思想是创建一个低成本的编码,用较短的代码表示频率高的字符,用较长的代码表示频率低的字符,从而实现数据的有效压缩。

  • 前缀码:任何字符的编码都不是其他字符编码的前缀,这消除了解码时的歧义性。
  • 贪心策略:在构造编码树时,总是选择两个最小频率的字符进行合并,这保证了最终的编码总成本(即编码的长度和频率的乘积)最小。

哈夫曼编码原理

考虑字符串 “aabacabad”,我们如何构建哈夫曼编码呢?

步骤 1: 统计频率

首先,计算字符串中每个字符的出现频率:

a: 5
b: 2
c: 1
d: 1

步骤 2: 初始化优先队列

对每个字符创建一个节点,并根据其频率放入优先队列(最小堆)中。每个节点是一个树的叶子节点。

初始队列(按频率排序):

节点     频率
---------------
[c:1]
[d:1]
[b:2]
[a:5]

步骤 3: 构建哈夫曼树

哈夫曼编码的目的是将常用字符编码为较短的码字,而不常用字符编码为较长的码字。字符的频率直接影响其在哈夫曼树中的位置:

  • 高频字符 被放在树的较高层(更靠近根节点),这样从根节点到这些字符的路径较短,因此产生的编码也较短。
  • 低频字符 被放在树的较低层(更远离根节点),这样路径较长,编码也较长。

这种策略使得编码的总长度(即编码长度乘以频率的总和)最小化,从而实现有效的压缩。
使用以下算法构建哈夫曼树,直到队列中只剩一个节点:

  • 合并两个最小频率的节点:从队列中取出两个最小的节点,合并为一个新节点,其频率是两个节点频率的和,这两个节点成为新节点的子节点。
  • 将新节点重新加入队列
第一次合并(合并 ‘c’ 和 ‘d’)
新节点: [cd],频率: 2
结构:
    [cd:2]
   /     \
[c:1]   [d:1]

队列更新为:

[b:2]
[cd:2]
[a:5]
第二次合并(合并 ‘b’ 和 ‘cd’)
新节点: [bcd],频率: 4
结构:
    [bcd:4]
   /      \
[b:2]    [cd:2]
        /    \
     [c:1]  [d:1]

队列更新为:

[bcd:4]
[a:5]
第三次合并(合并 ‘bcd’ 和 ‘a’)
新节点: [abcda],频率: 9 (这是根节点)
结构:
      [abcda:9]
     /        \
  [a:5]      [bcd:4]
            /      \
         [b:2]    [cd:2]
                 /    \
              [c:1]  [d:1]

队列清空,树构建完成。

步骤 4: 生成编码

在哈夫曼编码过程中,每个字符的编码由其在哈夫曼树中的位置决定,具体来说,是由从根节点到该字符对应叶子节点的路径决定。路径中左转表示“0”,右转表示“1”。

  • ‘a’ 的路径直接左转,因此编码为 “0”。
  • ‘b’ 的路径是向右转,然后左转,因此编码为 “10”。
  • ‘c’ 的路径是向右转,再向右转,然后左转,因此编码为 “110”。
  • ‘d’ 的路径是向右转,再向右转,然后右转,因此编码为 “111”。

最终编码:

  1. a -> 1
  2. b -> 01
  3. c -> 001
  4. d -> 000

效率分析

首先,基于字符 “aabacabad”,我们确定字符频率及其对应的哈夫曼编码和固定长度编码:

字符频率哈夫曼编码哈夫曼位数固定编码固定位数
a511002
b2012012
c10013102
d10003112
计算总位数需求

下面,我们计算每种编码策略的总位数需求:

哈夫曼编码总位数
  • 对于 ‘a’:5个字符 ✖️ 1位/字符 = 5位
  • 对于 ‘b’:2个字符 ✖️2位/字符 = 4位
  • 对于 ‘c’:1个字符 ✖️3位/字符 = 3位
  • 对于 ‘d’:1个字符 ✖️3位/字符 = 3位

哈夫曼编码总位数 = 5 + 4 + 3 + 3 = 15位

固定长度编码总位数
  • 每个字符使用2位编码(固定)

  • 对于 ‘a’:5个字符 ✖️ 2位/字符 = 10位

  • 对于 ‘b’:2个字符 ✖️2位/字符 = 4位

  • 对于 ‘c’:1个字符 ✖️2位/字符 = 2位

  • 对于 ‘d’:1个字符 ✖️ 2位/字符 = 2位

固定编码总位数 = 10 + 4 + 2 + 2 = 18位

压缩效率比较表

最后,我们整理以上计算结果,形成一个压缩效率比较表:

编码类型总位数压缩效率
哈夫曼编码15位
固定长度编码18位

结论

从表中可见,哈夫曼编码通过对字符使用变长编码,使得频率高的字符使用更短的编码,有效减少了总编码长度。相比之下,固定长度编码不区分字符频率,导致其总位数使用较多,压缩效率较低。哈夫曼编码尤其在处理非均匀分布的大数据集时,能显著优化数据存储和传输效率。

通过这种方式,哈夫曼编码不仅提供了理论上的最优压缩方案,而且在实际应用中广泛用于多种数据压缩场景,包括网络数据传输和文件存储。

哈夫曼编码Python算法

这里使用贪心算法来构建哈夫曼树,它是哈夫曼编码核心过程中的一个主要部分。贪心算法在此过程中的应用体现在选择过程中 —— 每次从所有可用的节点中选择两个频率最低的节点来合并。这种方法是基于局部最优选择,目的是构建全局最优的哈夫曼树。
贪心策略

在构建哈夫曼树的过程中,我们按以下贪心策略操作:

  1. 选择最小元素:每次从节点集合(初始时为优先队列)中选取两个频率最小的节点。这是一种贪心选择,因为合并这两个节点可以保证后续构建的树的总权重增加最小。
  2. 合并操作:将这两个最小节点合并为一个新的节点,其频率是两个子节点频率之和。这个新节点随后会被重新加入到节点集合中参与后续的合并操作。
  3. 重复过程:重复上述过程,直到节点集合中只剩一个节点,这个节点就是哈夫曼树的根节点,代表了构建完成的哈夫曼树。

代码示例

import heapq
from collections import Counter, defaultdict

class HuffmanNode:
    def __init__(self, char, freq):
        self.char = char   # 存储字符
        self.freq = freq   # 存储频率
        self.left = None   # 左子树
        self.right = None  # 右子树

    # 定义比较操作,以支持优先队列中的节点排序
    def __lt__(self, other):
        return self.freq < other.freq

def build_huffman_tree(text):
    """构建哈夫曼树并返回根节点"""
    # 统计字符频率
    frequency = Counter(text)
    # 创建优先队列(最小堆)
    heap = [HuffmanNode(char, freq) for char, freq in frequency.items()]
    heapq.heapify(heap)

    # 当堆中节点数大于1时,执行合并操作
    while len(heap) > 1:
        left = heapq.heappop(heap)  # 取出频率最小的节点
        right = heapq.heappop(heap) # 取出频率第二小的节点

        merged = HuffmanNode(None, left.freq + right.freq)  # 创建新的内部节点
        merged.left = left
        merged.right = right

        heapq.heappush(heap, merged)  # 将新节点添加回堆中

    return heap[0]  # 堆中最后剩下的节点为根节点

def huffman_codes(node, prefix="", code={}):
    """生成哈夫曼编码表"""
    if node is not None:
        if node.char is not None:
            code[node.char] = prefix
        huffman_codes(node.left, prefix + "0", code)
        huffman_codes(node.right, prefix + "1", code)
    return code

def encode(text, code):
    """使用哈夫曼编码表来编码文本"""
    return ''.join(code[char] for char in text)

def main():
    text = "aabacabad"  # 示例文本
    root = build_huffman_tree(text)  # 构建哈夫曼树
    code = huffman_codes(root)  # 生成哈夫曼编码表

    encoded_text = encode(text, code)  # 编码文本
    print("原始文本:", text)
    print("字符频率:", Counter(text))
    print("哈夫曼编码表:", code)
    print("编码后的文本:", encoded_text)

if __name__ == "__main__":
    main()

代码说明

  1. HuffmanNode 类:定义了哈夫曼树的节点,包括字符、频率及其左右子节点。
  2. build_huffman_tree 函数:接收输入文本,统计字符频率,构建哈夫曼树,并返回根节点。
  3. huffman_codes 函数:从哈夫曼树的根节点开始,递归地为每个字符生成其对应的哈夫曼编码,并存储在字典中返回。
  4. encode 函数:使用生成的哈夫曼编码表将原始文本转换为编码字符串。
  5. main 函数:提供示例文本,调用上述函数

总结

哈夫曼编码通过贪心算法的应用,优化了编码长度,从而达到了数据压缩的目的。这种算法不仅在理论上具有优雅的数学基础,而且在实际应用中也非常有效,尤其是在文件压缩和通信系统中。理解哈夫曼编码的原理和实现不仅可以深化对贪心算法的理解,还可以扩展到其他需要数据压缩的应用场景中。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

如何在阿里云主机上安装FreeBSD14系统

文章目录 在阿里云主机上安装FreeBSD14系统准备阿里云云主机识别目标磁盘下载 FreeBSD14解压缩 FreeBSD14系统镜像创建可启动的磁盘启动 FreeBSD14在阿里云主机上安装FreeBSD14系统 阿里云主机不支持 FreeBSD14 系统的镜像,因此需要手动进行安装。 准备阿里云云主机 在阿里云…

解决Git 不相关的分支合并

可以直接调到解决方案,接下来是原因分析和每步的解决方式 问题原因: 我之前在自己本机创建了一个初始化了Git仓库,后来有在另一个电脑初始化仓库,并没有clone自己在本机Git远程仓库地址,导致Git历史版本不相关 错误信息 From https://gitee.com/to-uphold-justice-for-other…

文字转语音工具:GPT-SoVITS

诸神缄默不语-个人CSDN博文目录 OpenAI官方的TTS模型我在这篇博文中给出了使用教程&#xff1a;ChatGPT 3.5 API的调用不全指南&#xff08;持续更新ing…&#xff09; - 知乎 但是OpenAI的TTS对中文支持不好&#xff0c;有一种老外说中文的美&#xff0c;所以本文介绍另一个…

Amazon SES邮箱API发送邮件的步骤是什么?

Amazon SES邮箱API发送邮件怎么配置&#xff1f;如何用邮箱API发送邮件&#xff1f; 在数字化时代&#xff0c;电子邮件已成为企业与个人之间沟通的重要桥梁。那么&#xff0c;使用Amazon SES邮箱API发送邮件的步骤究竟是怎样的呢&#xff1f;接下来&#xff0c;就让AokSend来…

IDEA远程调试debug

IDEA远程调试debug jar包启动脚本配置IDEA配置 通俗的说&#xff1a;本地有代码&#xff0c;服务器项目出现问题&#xff0c;环境的中间件配置不同&#xff0c;用idea远程调试&#xff0c;能快速定位问题&#xff0c;解决问题。 jar包启动脚本配置 jdk5-8写法 java -Xdebug -…

ChatGPT在遥感领域中的应用

遥感技术主要通过卫星和飞机从远处观察和测量我们的环境&#xff0c;是理解和监测地球物理、化学和生物系统的基石。ChatGPT是由OpenAI开发的最先进的语言模型&#xff0c;在理解和生成人类语言方面表现出了非凡的能力。本课程重点介绍ChatGPT在遥感中的应用&#xff0c;人工智…

MCU的最佳存储方案CS创世 SD NAND

MCU的最佳存储方案CS创世 SD NAND 写在最前面MCU是什么CS创世 SD NAND 6大优势 写在最前面 转载自 雷龙官网 MCU是什么 大家都知道MCU是一种"麻雀"虽小&#xff0c;却"五脏俱全"的主控。它的应用领域非常广泛&#xff0c;小到手机手表&#xff0c;大到航空…

【Kafka】Kafka Tool工具的使用

抖音视频 https://www.douyin.com/user/self?modal_id7123007128150901256&showTablike CSDN文档 https://blog.csdn.net/qq_43961619/article/details/109381849

Blind Image Super-Resolution: A Survey and Beyond

TPAMI2023 问题定义 未知图像的退化过程&#xff08;和之前假定bicubic等一个固定且已知的退化过程相对比&#xff09;&#xff0c;由LR恢复HR&#xff1b;退化来源&#xff08;不同的图像采集设备&#xff0c;数字信号处理成可见图像的过程中图像处理算法引入的噪声&#xff…

机器学习——模型融合:Stacking算法

机器学习——模型融合&#xff1a;Stacking算法 在机器学习中&#xff0c;模型融合是一种常用的方法&#xff0c;它可以提高模型的泛化能力和预测性能。Stacking算法&#xff08;又称为堆叠泛化&#xff09;是一种强大的模型融合技术&#xff0c;它通过组合多个基本分类器的预…

PyCharm连接数据库代码解析

1.先导入pymysql模块 在PyCharm中用清华镜像快速安装包 依次把ip地址和账号名、密码、数据库名、端口、编码输入 2.创建游标 游标&#xff1a;是数据库中的一个概念&#xff0c;我们执行sql查询语句时&#xff0c;大部分情况都会得到很多条结果&#xff0c;我们取出这些返回结…

python 无处不在的二分搜索

我们知道二分查找算法。二分查找是最容易正确的算法。我提出了一些我在二分搜索中收集的有趣问题。有一些关于二分搜索的请求。我请求您遵守准则&#xff1a;“我真诚地尝试解决问题并确保不存在极端情况”。阅读完每个问题后&#xff0c;最小化浏览器并尝试解决它。 …

数学建模--蒙特卡罗法MATLAB代码保姆式解析

1.简单介绍 2.思想的实际运用 我们利用蒙特卡罗法的思想求解圆周率π的值&#xff0c;这里求得的肯定是近似值&#xff0c;我们是通过大量的模拟实验&#xff0c;利用概率求解的&#xff0c;但是这个值和我们的精确值之间还是有一定的误差的&#xff1b; 我们的思想就是在半径为…

【Lattice FPGA 开发】Diamond的使用

文章目录 Diamond的使用教程界面器件查看与更改管脚分配RTL分析图查看 第三方工具关联Notepad 问题与解决管脚被分类到unconnected&#xff0c;导致无法分配管脚 Diamond的使用教程 【Lattice FPGA 开发】Diamond的工程建立、文件输入、ip核配置、管脚配置、综合及布线以及下载…

python/pygame 挑战魂斗罗 笔记(二)

一、建立地面碰撞体&#xff1a; 现在主角Bill能够站立在游戏地图的地面&#xff0c;是因为我们初始化的时候把Bill的位置固定了self.rect.y 250。而不是真正的站在地图的地面上。 背景地图是一个完整的地图&#xff0c;没有地面、台阶的概念&#xff0c;就无法通过碰撞检测来…

【分治】Leetcode 排序数组

题目讲解 912. 排序数组 算法讲解 我们这里使用三指针&#xff0c;将数组分成三块&#xff1a;<key 和 key 和 >key,如果当前指针指向的数字<key&#xff0c;我们就swap(nums[left]), nums[i] 。如果当前的数字key &#xff0c;就让i。如果当前的数字>key&…

大屏数字字体+渐变色

vue数据大屏使用数字字体_vue数字字体-CSDN博客 用css实现文字字体颜色渐变的三种方法_css 字体颜色渐变-CSDN博客

DNS服务器配置与管理(2)——BIND部署DNS

在Linux上配置DNS的常用软件是BIND&#xff08;Berkeley Internet Name Domain Service&#xff0c;BIND&#xff09;&#xff0c;它是一款实现DNS服务器的开放源码软件。本文详细介绍了在CentOS7上安装并配置Bind软件。 一、Bind软件介绍 BIND包最初是在 1980 年代初在加州大…

35岁再去学程序员靠谱吗?

不亚于49年入国Jun。35岁的程序员都已经在找后路了…… 总结一句话&#xff1a;35岁自学程序员赚点小钱可以&#xff0c;当主业糊口不行&#xff01; 首先&#xff0c;程序员这行吃青春饭是真的。虽说国外有很多程序员可以写代码到70岁&#xff0c;但国内的现状是35岁就会淘汰一…

财商的思考

【200万粉福利特供|| 高考后的“分层之战”和“人生破圈算法”-哔哩哔哩】 https://b23.tv/5ASl8WA 社会三层 &#xff08;1&#xff09;上层 &#xff08;2&#xff09;中层 &#xff08;3&#xff09;基层&#xff1a; 上层 定义&#xff1a;高护城河生产资料和权利的所有…
最新文章