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

【数据结构与算法】第24篇:哈夫曼树与哈夫曼编码

一、基本概念1.1 带权路径长度在二叉树中路径长度从一个节点到另一个节点经过的边数带权路径长度(WPL)所有叶子节点的权重 × 路径长度 之和示例text叶子节点A(7), B(5), C(2), D(4) 普通树 15 / \ 7 8 / \ 5 3 / \ 2 4 WPL 7×1 5×2 2×3 4×3 7 10 6 12 35 哈夫曼树 18 / \ 7 11 / \ 5 6 / \ 2 4 WPL 7×1 5×2 2×3 4×3 35相同1.2 哈夫曼树的定义哈夫曼树带权路径长度最小的二叉树。权值越大的叶子节点离根越近。1.3 应用场景数据压缩哈夫曼编码文件压缩ZIP、RAR多媒体编码JPEG、MP3中的熵编码二、哈夫曼树的构造贪心算法2.1 算法步骤将每个权值看作一个只有根节点的二叉树选择两个权值最小的树作为左右子树构造新树权值为二者之和从森林中删除这两棵树加入新树重复步骤2-3直到只剩一棵树示例权值[5, 4, 2, 7]text步骤1森林 {5}, {4}, {2}, {7} 步骤2取2和4 → 新树6森林 {5}, {6}, {7} 步骤3取5和6 → 新树11森林 {7}, {11} 步骤4取7和11 → 新树18森林 {18}2.2 手动构造text18 / \ 7 11 / \ 5 6 / \ 2 42.3 代码实现c#include stdio.h #include stdlib.h #include string.h #define MAX_NODES 100 typedef struct { int weight; // 权值 int parent; // 父节点下标-1表示无 int left, right; // 左右孩子下标-1表示无 } HuffmanNode; typedef struct { HuffmanNode nodes[MAX_NODES * 2]; // 存储所有节点 int leafNum; // 叶子节点数 int nodeNum; // 总节点数 } HuffmanTree; // 初始化哈夫曼树 void initHuffmanTree(HuffmanTree *tree, int *weights, int n) { tree-leafNum n; tree-nodeNum 2 * n - 1; // 哈夫曼树总节点数 2n-1 // 初始化所有节点 for (int i 0; i tree-nodeNum; i) { tree-nodes[i].weight (i n) ? weights[i] : 0; tree-nodes[i].parent -1; tree-nodes[i].left -1; tree-nodes[i].right -1; } } // 在[0, range)范围内找两个权值最小且parent-1的节点 void selectMin(HuffmanTree *tree, int range, int *s1, int *s2) { int min1 -1, min2 -1; for (int i 0; i range; i) { if (tree-nodes[i].parent ! -1) continue; // 已使用 if (min1 -1 || tree-nodes[i].weight tree-nodes[min1].weight) { min2 min1; min1 i; } else if (min2 -1 || tree-nodes[i].weight tree-nodes[min2].weight) { min2 i; } } *s1 min1; *s2 min2; } // 构造哈夫曼树 void createHuffmanTree(HuffmanTree *tree) { int n tree-leafNum; int total tree-nodeNum; for (int i n; i total; i) { int s1, s2; selectMin(tree, i, s1, s2); // 创建新节点 tree-nodes[i].weight tree-nodes[s1].weight tree-nodes[s2].weight; tree-nodes[i].left s1; tree-nodes[i].right s2; tree-nodes[s1].parent i; tree-nodes[s2].parent i; } } // 打印哈夫曼树 void printHuffmanTree(HuffmanTree *tree) { printf(索引\t权值\t父节点\t左孩子\t右孩子\n); for (int i 0; i tree-nodeNum; i) { printf(%d\t%d\t%d\t%d\t%d\n, i, tree-nodes[i].weight, tree-nodes[i].parent, tree-nodes[i].left, tree-nodes[i].right); } }三、哈夫曼编码3.1 编码规则从根到叶子节点的路径向左走 → 编码 0向右走 → 编码 1示例以上面的树为例text叶子节点及其路径 7: 根 → 左 → 0 5: 根 → 右 → 左 → 10 2: 根 → 右 → 右 → 左 → 110 4: 根 → 右 → 右 → 右 → 111 编码结果 7: 0 5: 10 2: 110 4: 1113.2 编码特点前缀编码没有任何编码是另一个编码的前缀变长编码出现频率高的字符用短编码唯一可解码不会产生歧义3.3 代码实现c#define MAX_CODE 100 // 从叶子向上生成编码 void getHuffmanCodes(HuffmanTree *tree, char **codes) { char *temp (char*)malloc(MAX_CODE * sizeof(char)); for (int i 0; i tree-leafNum; i) { int start MAX_CODE - 1; temp[start] \0; int child i; int parent tree-nodes[child].parent; while (parent ! -1) { if (tree-nodes[parent].left child) { temp[--start] 0; } else { temp[--start] 1; } child parent; parent tree-nodes[child].parent; } // 复制编码 codes[i] (char*)malloc((MAX_CODE - start) * sizeof(char)); strcpy(codes[i], temp[start]); } free(temp); }四、完整代码演示c#include stdio.h #include stdlib.h #include string.h #define MAX_NODES 100 #define MAX_CODE 100 typedef struct { int weight; int parent; int left, right; } HuffmanNode; typedef struct { HuffmanNode nodes[MAX_NODES * 2]; int leafNum; int nodeNum; } HuffmanTree; void initHuffmanTree(HuffmanTree *tree, int *weights, int n) { tree-leafNum n; tree-nodeNum 2 * n - 1; for (int i 0; i tree-nodeNum; i) { tree-nodes[i].weight (i n) ? weights[i] : 0; tree-nodes[i].parent -1; tree-nodes[i].left -1; tree-nodes[i].right -1; } } void selectMin(HuffmanTree *tree, int range, int *s1, int *s2) { int min1 -1, min2 -1; for (int i 0; i range; i) { if (tree-nodes[i].parent ! -1) continue; if (min1 -1 || tree-nodes[i].weight tree-nodes[min1].weight) { min2 min1; min1 i; } else if (min2 -1 || tree-nodes[i].weight tree-nodes[min2].weight) { min2 i; } } *s1 min1; *s2 min2; } void createHuffmanTree(HuffmanTree *tree) { int n tree-leafNum; int total tree-nodeNum; for (int i n; i total; i) { int s1, s2; selectMin(tree, i, s1, s2); tree-nodes[i].weight tree-nodes[s1].weight tree-nodes[s2].weight; tree-nodes[i].left s1; tree-nodes[i].right s2; tree-nodes[s1].parent i; tree-nodes[s2].parent i; } } void getHuffmanCodes(HuffmanTree *tree, char **codes) { char *temp (char*)malloc(MAX_CODE * sizeof(char)); for (int i 0; i tree-leafNum; i) {int start MAX_CODE - 1; temp[start] \0; int child i; int parent tree-nodes[child].parent; while (parent ! -1) { if (tree-nodes[parent].left child) { temp[--start] 0; } else { temp[--start] 1; } child parent; parent tree-nodes[child].parent; } codes[i] (char*)malloc((MAX_CODE - start) * sizeof(char)); strcpy(codes[i], temp[start]); } free(temp); } void printHuffmanTree(HuffmanTree *tree) { printf(\n 哈夫曼树 \n); printf(索引\t权值\t父节点\t左孩子\t右孩子\n); for (int i 0; i tree-nodeNum; i) { printf(%d\t%d\t%d\t%d\t%d\n, i, tree-nodes[i].weight, tree-nodes[i].parent, tree-nodes[i].left, tree-nodes[i].right); } } int main() { // 示例字符频率 char chars[] {A, B, C, D}; int weights[] {7, 5, 2, 4}; int n 4; HuffmanTree tree; initHuffmanTree(tree, weights, n); createHuffmanTree(tree); printHuffmanTree(tree); char *codes[MAX_NODES]; getHuffmanCodes(tree, codes); printf(\n 哈夫曼编码 \n); for (int i 0; i n; i) { printf(%c (权值%d): %s\n, chars[i], weights[i], codes[i]); } // 计算压缩率 int originalBits 0; int compressedBits 0; for (int i 0; i n; i) { originalBits weights[i] * 8; // 假设每个字符原用8位 compressedBits weights[i] * strlen(codes[i]); } printf(\n原始总位数: %d\n, originalBits); printf(压缩后总位数: %d\n, compressedBits); printf(压缩率: %.1f%%\n, (1 - (float)compressedBits / originalBits) * 100); // 释放内存 for (int i 0; i n; i) { free(codes[i]); } return 0; }运行结果text 哈夫曼树 索引 权值 父节点 左孩子 右孩子 0 7 5 -1 -1 1 5 4 -1 -1 2 2 3 -1 -1 3 4 4 -1 -1 4 9 5 1 3 5 16 -1 0 4 哈夫曼编码 A (权值7): 0 B (权值5): 10 C (权值2): 110 D (权值4): 111 原始总位数: 144 压缩后总位数: 41 压缩率: 71.5%五、哈夫曼编码的应用5.1 数据压缩流程text原始数据 → 统计频率 → 构造哈夫曼树 → 生成编码 → 压缩数据 ↓ 存储编码表 编码数据5.2 解压流程text压缩文件 → 读取编码表 → 重建哈夫曼树 → 解码数据 → 原始数据5.3 实际应用应用说明ZIP压缩结合LZ77和哈夫曼编码JPEG对DCT系数进行哈夫曼编码MP3对量化后的频谱数据进行哈夫曼编码PNG使用DEFLATE算法LZ77哈夫曼六、复杂度分析操作时间复杂度说明构造哈夫曼树O(n log n)每次找最小值可用堆优化到O(n log n)生成编码O(n × 树高)最坏O(n²)平均O(n log n)编码数据O(m)m为数据长度解码数据O(m)从根到叶子每字符走一次七、小结这一篇我们学习了哈夫曼树和哈夫曼编码要点说明哈夫曼树带权路径长度最小的二叉树构造算法贪心每次取两个最小的合并哈夫曼编码左0右1频率高的用短码特性前缀编码唯一可解码应用数据压缩ZIP、JPEG、MP3核心思想让出现频率高的字符用最短的编码从而实现整体压缩。下一篇我们讲静态查找顺序查找与折半查找。八、思考题哈夫曼树是否唯一权值相同的两个节点交换位置会怎样如果有n个叶子节点哈夫曼树的总节点数是多少为什么哈夫曼编码为什么不会产生歧义即为什么是前缀编码尝试用最小堆优先队列优化构造哈夫曼树的过程。欢迎在评论区讨论你的答案。

相关文章:

【数据结构与算法】第24篇:哈夫曼树与哈夫曼编码

一、基本概念1.1 带权路径长度在二叉树中:路径长度:从一个节点到另一个节点经过的边数带权路径长度(WPL):所有叶子节点的权重 路径长度 之和示例:text叶子节点:A(7), B(5), C(2), D(4)普通树:15/ \7 8/…...

创意随笔:智能转录便携终端

创意随笔|智能转录便携终端 项目构想 核心亮点 以独立麦克风拾音为核心入口,实现全链路闭环实时翻译 从收音、ASR 识别、翻译、TTS 合成到语音播放/耳机输出,全程不依赖手机或电脑算力,自成一套完整翻译系统,真正做到端…...

技术创业中的风险管理:从内核开发到商业稳定

技术创业中的风险管理:从内核开发到商业稳定 技术创业的风险挑战 作为一名从Linux内核开发者转型产品经理再到科技创业者的人,我深刻体会到风险管理在技术创业中的重要性。技术创业过程中充满了各种风险,从技术风险到商业风险,从市…...

嵌入式开发中的策略模式应用与优化

1. 策略模式在嵌入式开发中的核心价值在嵌入式系统开发中,我们经常遇到这样的场景:同一个功能模块需要根据不同的硬件环境、运行状态或外部条件采用不同的处理算法。传统做法是使用大量的if-else或switch-case语句,但这种做法会带来几个显著问…...

技术创业中的产品迭代:从内核开发到用户中心

技术创业中的产品迭代:从内核开发到用户中心 产品迭代的重要性 作为一名从Linux内核开发者转型产品经理再到科技创业者的人,我深刻体会到产品迭代在技术创业中的重要性。一个成功的产品不是一蹴而就的,而是通过不断的迭代和优化逐步发展起来的…...

【图像加密】基于 AES算法的图像位平面加密解密算法附Matlab代码

✅作者简介:热爱科研的Matlab仿真开发者,擅长毕业设计辅导、数学建模、数据处理、建模仿真、程序设计、完整代码获取、论文复现及科研仿真。🍎 往期回顾关注个人主页:Matlab科研工作室👇 关注我领取海量matlab电子书和…...

OpenClaw性能调优实战:Qwen3-32B在RTX4090D上的量化推理加速

OpenClaw性能调优实战:Qwen3-32B在RTX4090D上的量化推理加速 1. 为什么需要性能调优? 去年冬天,当我第一次在RTX4090D上部署Qwen3-32B模型时,本以为24GB显存足以轻松应对各种任务。但现实很快给我上了一课——一个简单的网页内容…...

IBM与Arm合作推进双架构主机系统开发

IBM和Arm宣布合作开发能够运行IBM和Arm双重工作负载的硬件,使Arm软件能够在IBM主机上运行。两家公司计划在三个方面展开合作:构建虚拟化工具,让Arm软件能够在IBM平台上运行;确保Arm应用程序符合受监管行业必须遵循的安全和数据驻留…...

AWS推出新工具简化量子纠错开发流程

谷歌近日将量子计算机实用化时间表提前至2029年,这得益于量子计算机硬件、量子纠错和算法方面的重大改进。2019年,谷歌估计需要2000万个量子比特才能破解RSA加密。到2025年5月,谷歌将这一估计数字下调至100万个。今年2月,澳大利亚…...

DuinoMemory:面向Arduino的轻量级嵌入式智能指针库

1. 项目概述DuinoMemory 是一款专为 Arduino 及资源受限嵌入式系统设计的轻量级智能指针库。它不依赖 STL、不使用异常(exceptions)、不启用 RTTI,完全以头文件形式提供(header-only),所有实现均通过 C 模板…...

作家使用AI写小说:写作者必须接纳人工智能但我们依然珍贵

我最近在游乐场听到一段对话,这比任何分析师对泡沫的预测都更应该让AI公司高管担忧。一个男孩和一个女孩,大概10岁,正在争吵。"那是AI!那是AI!"女孩喊道。她的意思是男孩在沉溺于一种新的特殊胡言乱语&#…...

OpenAI收购科技脱口秀TBPN,力图塑造AI叙事话语权

OpenAI正通过收购备受硅谷内部人士关注的科技脱口秀TBPN进军媒体行业,该节目主持人周三宣布了这一消息。联合主持人约翰库根和乔迪海斯每个工作日从洛杉矶直播TBPN节目三小时,邀请的嘉宾包括创业者、风险投资家和科技界重要人物。此次交易的财务条款未予…...

OpenClaw压力测试:千问3.5-27B持续运行48小时稳定性报告

OpenClaw压力测试:千问3.5-27B持续运行48小时稳定性报告 1. 测试背景与设计思路 上周在星图平台部署了千问3.5-27B镜像后,我决定对OpenClaw框架进行极限压力测试。这个想法源于实际需求——作为独立开发者,经常需要AI助手连续处理夜间数据抓…...

嵌入式开发中PC与嵌入式思维的融合实践

1. 嵌入式开发中的PC思维与嵌入式思维融合作为一名从PC端开发转向嵌入式领域的工程师,我深刻体会到两种思维方式的差异与互补。PC编程注重抽象层次和开发效率,而嵌入式编程则必须关注硬件特性和实时性。真正的高手往往能将二者有机结合。在嵌入式领域&am…...

嵌入式软件架构设计:基础设施层实践指南

1. 嵌入式软件架构设计概述作为一名在嵌入式领域摸爬滚打多年的工程师,我深知软件架构设计的重要性。很多人认为架构设计是资深工程师的专利,其实不然。就像盖房子需要先打地基一样,任何规模的嵌入式项目都需要合理的架构设计作为基础。嵌入式…...

电动关节机械手设计【任务书+说明书+CAD图纸】 电动关节机器人

电动关节机械手作为工业自动化领域的核心装备,通过电机驱动实现多自由度运动控制,在物料搬运、装配加工等场景中承担关键操作任务。其核心作用在于替代人工完成重复性高、精度要求严苛的作业,例如精密电子元件的抓取、重型工件的定位等&#…...

4大技术方案解决WarcraftHelper工具的《魔兽争霸III》兼容性与性能优化问题

4大技术方案解决WarcraftHelper工具的《魔兽争霸III》兼容性与性能优化问题 【免费下载链接】WarcraftHelper Warcraft III Helper , support 1.20e, 1.24e, 1.26a, 1.27a, 1.27b 项目地址: https://gitcode.com/gh_mirrors/wa/WarcraftHelper WarcraftHelper是一款专注…...

折腾光纤模型的手记

comsol仿真-W型光子晶体光纤色散与损耗分析效果展示最近在实验室被导师催着搞光子晶体光纤的仿真,W型结构这种带双包层设计的玩意儿确实有点意思。作为COMSOL萌新,边啃说明书边试错,折腾一周终于把色散曲线和损耗谱给整明白了。先说建模这个重…...

针对双SMC控制的四轮转向轨迹跟踪模型优化与效果评估研究

四轮转向4WS轨迹跟踪控制模型 采用双SMC控制 4WS通过积分滑模控制跟踪期望横摆角速度和质心侧偏角,效果很好~ 轨迹跟踪为双移线输入,采用积分滑模控制 【特别说明】 是针对两篇论文的控制进行复现哦~ 提供参考文献及模型文件 最近在复现四轮转向轨迹跟踪…...

直接可用4轴插补算法库,STM32的DDA插补联动与梯形加减速算法代码

可以直接使用的4轴插补算法库,不是丢给你一堆gr1b或者写字机或者3d打印的开源代码,本运控库上项目级别的,需要添加在自己的项目中,不支持gm码,只有运动控制核心代码,可以添加在自己项目中的,stm…...

光储并网直流微电网仿真模型(matlab/simulink,2018),包含: 1.MPPT模块

光储并网直流微电网仿真模型(matlab/simulink,2018),包含: 1.MPPT模块,实现光伏输入最大功率跟踪; 2.储能电池模块; 3.超级电容模块; 控制策略简介: 糸统使用…...

质子交换膜(PEM)燃料电池氢气供应系统,阳极压力非线性状态控制simulink模型;自适应反...

质子交换膜(PEM)燃料电池氢气供应系统,阳极压力非线性状态控制simulink模型;自适应反步法控制; 燃料电池电堆模型:阴极流道,阳极流道,膜水合传递,输出电压模型、 氢气回路…...

MAX9814麦克风音量LED指示器嵌入式固件库

1. 项目概述MAX9814_Electret_Microphone_LED_Volume_Indicator是一个面向嵌入式音频前端采集与可视化反馈的轻量级固件库,专为 Adafruit MAX9814 电容式驻极体麦克风放大模块设计。该模块基于 Maxim(现为 Analog Devices)推出的低噪声、高增…...

L293D电机驱动库:嵌入式直流电机控制实战指南

1. L293D电机驱动库深度解析:面向嵌入式工程师的工程实践指南L293D是TI(德州仪器)推出的双H桥直流电机驱动芯片,广泛应用于Arduino、ESP32等微控制器平台的中小功率直流电机控制场景。本库并非简单封装GPIO操作,而是针…...

C语言整数字节拆解:联合体与移位操作详解

1. 理解题目:整数字节拆解的核心需求 在嵌入式开发和底层系统编程中,处理多字节数据是家常便饭。就拿这个面试题来说,我们需要从32位无符号整数0x12345678中提取出它的四个独立字节。这看似简单的需求背后,其实涉及到计算机系统中…...

剪映自动化工具来了:AI帮你自动剪辑成片

文章目录 📖 介绍 📖 🏡 演示环境 🏡 📒 AI赋能剪映自动化剪辑 📒 🎯 设计理念 🔧 核心功能 📦 安装与使用 ⚓️ 相关链接 ⚓️ 📖 介绍 📖 在视频创作中,剪辑工作往往耗时耗力。从素材导入、字幕匹配、BGM选择到最终导出,每一个环节都需要创作者投入大…...

从裸机开发到RTOS:嵌入式系统进阶指南

1. 裸机开发的本质与局限性裸机开发,顾名思义就是在没有任何操作系统支持下直接对硬件进行编程。这种方式在嵌入式系统入门阶段非常普遍,尤其适合资源极其有限的8位单片机(如51系列)或简单应用场景。但当我们面对STM32这类性能强大…...

MS5540C传感器驱动开发:类SPI协议与校准算法详解

1. MS5540C传感器库深度解析:面向嵌入式工程师的底层驱动开发指南 MS5540C系列是TE Connectivity(原Measurement Specialties)推出的高精度、低功耗数字压力/温度复合传感器,广泛应用于潜水设备、气象站、工业过程监控及水下机器人…...

OpenClaw与企业微信/飞书/钉钉深度集成方案

第1章 引言 1.1 OpenClaw简介与定位 OpenClaw是一个现代化的AI Agent运行框架,专为构建企业级智能助手和应用而设计。它采用模块化架构,通过统一的Gateway接口支持多种通信渠道的接入,让AI能力能够无缝融入企业现有的协作生态中。 OpenClaw的核心特性包括: 多渠道统一接…...

PCBA加工中极性元件的识别与防错指南

1. 极性元件在PCBA加工中的重要性在PCBA(印刷电路板组装)加工过程中,极性元件就像电路中的"单行道"——方向错了,整个系统就会瘫痪。作为一名有十年经验的电子工程师,我见过太多因为极性元件反向导致的批量性…...