哈夫曼编码的原理和python实现(P124302064王酉辰,P124302070李佳乐)

📅 2026/7/6 13:20:13
哈夫曼编码的原理和python实现(P124302064王酉辰,P124302070李佳乐)
一哈夫曼编码原理1核心算法哈夫曼编码的核心思想是为出现频率高的字符分配较短的编码为出现频率低的字符分配较长的编码从而使得整体编码后的平均长度最短达到压缩数据的目的。它由大卫·哈夫曼David A. Huffman在1952年提出是一种贪心算法。2关键概念前缀编码任何一个字符的编码都不能是另一个字符编码的前缀。例如如果A的编码是0那么B的编码就不能是01或00等以0开头的编码。这样可以保证解码时不会产生歧义。最优性在给定字符频率的情况下哈夫曼编码生成的编码树是所有可能的前缀编码树中加权路径长度最小的因此它是最优的。变长编码不同字符使用不同长度的二进制码。3具体实现步骤哈夫曼编码的生成过程分为两大步构建哈夫曼树和从树中生成编码。步骤1构建哈夫曼树自底向上统计频率统计每个字符出现的次数或概率。创建叶子节点为每个字符创建一个节点节点的权重就是它的频率。构建森林将这些节点放入一个优先级队列最小堆中按权重从小到大排列。重复合并直到队列中只剩一个节点从队列中取出权重最小的两个节点记为left和right。创建一个新的父节点其权重为left.weight right.weight。将left设为新节点的左子节点right设为右子节点顺序不重要但需保持一致。将新节点放回队列中。得到根节点队列中最后剩下的节点就是哈夫曼树的根节点。步骤2从哈夫曼树生成编码从根节点出发遍历整棵树。向左子节点走编码追加一个0向右子节点走编码追加一个1。到达叶子节点时路径上形成的二进制串就是该字符的哈夫曼编码。4重要性与注意事项解码唯一性因为前缀码特性解码时从左到右读取一旦匹配到某个叶子节点就立即输出并重置从根开始不会产生歧义。必须传递码表解码端需要知道原始的哈夫曼树或码表因此压缩文件中通常需要附带码表信息这会带来一点额外开销。当文件较小时压缩收益可能不明显。动态哈夫曼编码实际应用中如JPEG、DEFLATE算法常采用自适应或预定义的静态码表来优化性能。二哈夫曼编码python仿真1python代码import heapq import os from collections import Counter, defaultdict from typing import List, Tuple, Dict, Optional # 确保在VS Code中运行时输出目录存在 OUTPUT_DIR huffman_output os.makedirs(OUTPUT_DIR, exist_okTrue) class HuffmanNode: 哈夫曼树节点类 def __init__(self, symbol: Optional[str], freq: int): self.symbol symbol # 字符内部节点为None self.freq freq # 频率 self.left None # 左子节点 self.right None # 右子节点 # 为了在优先队列中比较定义 __lt__ 方法 def __lt__(self, other): return self.freq other.freq def __eq__(self, other): return self.freq other.freq def is_leaf(self) - bool: 判断是否为叶子节点 return self.left is None and self.right is None def build_frequency_table(data: str) - Dict[str, int]: 构建字符频率表 freq Counter(data) return dict(freq) def build_huffman_tree(freq_table: Dict[str, int]) - HuffmanNode: 根据频率表构建哈夫曼树返回根节点 if not freq_table: raise ValueError(频率表为空无法构建哈夫曼树) # 初始化优先队列最小堆 heap [] for symbol, freq in freq_table.items(): node HuffmanNode(symbol, freq) heapq.heappush(heap, node) # 当堆中多于一个节点时合并 while len(heap) 1: left heapq.heappop(heap) right heapq.heappop(heap) # 创建内部节点频率为左右子节点频率之和 parent HuffmanNode(None, left.freq right.freq) parent.left left parent.right right heapq.heappush(heap, parent) # 返回根节点 return heap[0] def generate_huffman_codes(root: HuffmanNode, prefix: str , codebook: Optional[Dict[str, str]] None) - Dict[str, str]: 递归生成哈夫曼编码表 if codebook is None: codebook {} if root is None: return codebook # 如果是叶子节点记录编码 if root.is_leaf(): codebook[root.symbol] prefix if prefix ! else 0 # 单字符情况特殊处理 else: # 左子树编码追加 0 generate_huffman_codes(root.left, prefix 0, codebook) # 右子树编码追加 1 generate_huffman_codes(root.right, prefix 1, codebook) return codebook def huffman_encode(data: str, codebook: Dict[str, str]) - str: 使用编码表对数据进行编码 encoded .join(codebook[ch] for ch in data) return encoded def huffman_decode(encoded: str, root: HuffmanNode) - str: 使用哈夫曼树对编码数据进行解码 if not encoded: return decoded [] current root for bit in encoded: if bit 0: current current.left else: current current.right # 到达叶子节点 if current.is_leaf(): decoded.append(current.symbol) current root # 重置到根节点 return .join(decoded) def print_tree_structure(root: HuffmanNode, prefix: str , is_left: bool True) - str: 打印树结构辅助可视化 if root is None: return result if root.is_leaf(): result f{prefix}{└── if is_left else └── }{root.symbol} (freq{root.freq})\n else: result f{prefix}{└── if is_left else └── }Internal (freq{root.freq})\n # 递归打印子树 if root.left: result print_tree_structure(root.left, prefix , True) if root.right: result print_tree_structure(root.right, prefix , False) return result def save_to_file(filename: str, content: str): 保存内容到文件 filepath os.path.join(OUTPUT_DIR, filename) with open(filepath, w, encodingutf-8) as f: f.write(content) def main(): 主函数演示哈夫曼编码全过程 print( 哈夫曼编码模拟程序 ) # 示例文本包含英文、中文、标点 sample_text Hello 哈夫曼编码! This is a test for Huffman Coding. 测试中文字符。 # 也可以让用户输入 # sample_text input(请输入要编码的文本直接回车使用默认示例: ).strip() # if not sample_text: # sample_text Hello 哈夫曼编码! This is a test for Huffman Coding. 测试中文字符。 print(f\n原始文本 ({len(sample_text)} 字符):) print(sample_text) # 1. 构建频率表 freq_table build_frequency_table(sample_text) print(\n字符频率表:) # 按频率排序显示 sorted_freq sorted(freq_table.items(), keylambda x: x[1], reverseTrue) for ch, freq in sorted_freq: # 特殊处理不可见字符 display_ch ch if ch.isprintable() else f\\x{ord(ch):02x} print(f {display_ch}: {freq}) # 2. 构建哈夫曼树 root build_huffman_tree(freq_table) # 3. 生成编码表 codebook generate_huffman_codes(root) print(\n哈夫曼编码表:) for ch, code in sorted(codebook.items(), keylambda x: len(x[1])): display_ch ch if ch.isprintable() else f\\x{ord(ch):02x} print(f {display_ch}: {code}) # 4. 编码 encoded_data huffman_encode(sample_text, codebook) print(f\n编码后长度: {len(encoded_data)} bits (原始长度: {len(sample_text)*8} bits)) print(f压缩率: {len(encoded_data)/(len(sample_text)*8)*100:.2f}%) # 显示部分编码如果太长只显示前200位 if len(encoded_data) 200: print(f编码数据 (前200位): {encoded_data[:200]}...) else: print(f编码数据: {encoded_data}) # 5. 解码验证 decoded_data huffman_decode(encoded_data, root) print(f\n解码后文本: {decoded_data}) print(f解码结果与原文一致: {✅ 是 if decoded_data sample_text else ❌ 否}) # 6. 输出树结构 tree_str 哈夫曼树结构:\n print_tree_structure(root) print(\n tree_str) # 7. 保存结果到文件 save_to_file(original.txt, sample_text) save_to_file(freq_table.txt, \n.join([f{ch}: {freq} for ch, freq in sorted_freq])) save_to_file(codebook.txt, \n.join([f{ch}: {code} for ch, code in sorted(codebook.items(), keylambda x: len(x[1]))])) save_to_file(encoded.txt, encoded_data) save_to_file(decoded.txt, decoded_data) save_to_file(tree_structure.txt, tree_str) print(f\n所有输出文件已保存到 {OUTPUT_DIR} 目录中。) print( 程序结束 ) if __name__ __main__: main()2编码流程和核心机制频率统计与树构建程序首先统计输入文本中每个字符的出现频率然后利用最小堆优先队列反复合并频率最低的两个节点构建出一棵最优的哈夫曼树。编码表生成与数据压缩通过递归遍历哈夫曼树为每个叶子节点字符分配唯一的二进制码左0右1。随后程序使用此编码表将原始文本转换为二进制串并计算压缩率。解码验证与可视化程序会使用同一棵树对编码后的二进制串进行解码并与原文比对确保过程无误。同时它会以文本形式打印出树的结构方便您理解编码的层次关系。所有关键数据频率表、编码表、编码结果等都会自动保存到huffman_output文件夹中便于后续分析。3仿真结果 哈夫曼编码模拟程序 原始文本 (59 字符): Hello 哈夫曼编码! This is a test for Huffman Coding. 测试中文字符。 字符频率表: : 9 t: 5 s: 4 e: 4 a: 3 o: 3 i: 3 n: 3 H: 2 f: 2 r: 2 h: 2 C: 2 l: 2 码: 1 编: 1 试: 1 测: 1 中: 1 文: 1 字: 1 符: 1 。: 1 !: 1 .: 1 F: 1 u: 1 g: 1 c: 1 d: 1 T: 1 m: 1 o: 1 z: 1 y: 1 哈夫曼编码表: : 101 t: 000 s: 010 e: 011 a: 1000 o: 1100 i: 1101 n: 1110 H: 00100 f: 00101 r: 00110 h: 00111 C: 10010 l: 10011 码: 1111000 编: 1111001 试: 1111010 测: 1111011 中: 1111100 文: 1111101 字: 1111110 符: 1111111 。: 000000 !: 000001 .: 000010 F: 000011 u: 111000 g: 111001 c: 111010 d: 111011 T: 111100 m: 111101 o: 111110 z: 111111 编码后长度: 267 bits (原始长度: 472 bits) 压缩率: 56.57% 编码数据 (前200位): 001000000010010101010011111001111001101101111001101101111010111011101011101010111110000101101100111100101010011011110011001101101101001010001011111001110101111000010000100111111101101... 解码后文本: Hello 哈夫曼编码! This is a test for Huffman Coding. 测试中文字符。 解码结果与原文一致: ✅ 是 哈夫曼树结构: └── Internal (freq59) ├── Internal (freq27) │ ├── Internal (freq12) │ │ ├── Internal (freq5) │ │ │ ├── Internal (freq2) │ │ │ │ ├── Internal (freq1) │ │ │ │ │ ├── Internal (freq0) │ │ │ │ │ │ ├── z (freq1) │ │ │ │ │ │ └── y (freq1) │ │ │ │ │ └── m (freq1) │ │ │ │ └── T (freq1) │ │ │ └── t (freq5) │ │ └── Internal (freq7) │ │ ├── Internal (freq3) │ │ │ ├── s (freq4) │ │ │ └── e (freq4) │ │ └── Internal (freq4) │ │ ├── a (freq3) │ │ └── o (freq3) │ └── Internal (freq15) │ ├── Internal (freq6) │ │ ├── i (freq3) │ │ └── n (freq3) │ └── Internal (freq9) │ ├── Internal (freq4) │ │ ├── H (freq2) │ │ └── f (freq2) │ └── Internal (freq5) │ ├── r (freq2) │ └── h (freq2) └── Internal (freq32) ├── Internal (freq15) │ ├── Internal (freq6) │ │ ├── C (freq2) │ │ └── l (freq2) │ └── Internal (freq9) │ ├── Internal (freq4) │ │ ├── 码 (freq1) │ │ └── 编 (freq1) │ └── Internal (freq5) │ ├── 试 (freq1) │ └── 测 (freq1) └── Internal (freq17) ├── Internal (freq8) │ ├── 中 (freq1) │ └── 文 (freq1) └── Internal (freq9) ├── 字 (freq1) └── 符 (freq1) 所有输出文件已保存到 huffman_output 目录中。 程序结束 4关键数据解读指标数值说明原始文本长度59 字符包含英文、中文、标点原始比特数472 bits59 × 8假设ASCII/UTF-8单字节编码后长度267 bits哈夫曼压缩后的二进制长度压缩率56.57%压缩后是原始大小的56.57%不同字符数35 个统计到的唯一字符数量最短编码3 bits空格和t使用最短编码最长编码7 bits中文和标点使用最长编码三哈夫曼编码的现实应用1音频与通用文件压缩哈夫曼编码是许多经典压缩算法的核心。DEFLATE算法这是应用最广泛的通用压缩算法之一被用于ZIP文件格式和gzip工具中。它结合了LZ77算法用于查找重复字符串和哈夫曼编码用于对结果进行最优编码。MP3在MP3音频压缩中哈夫曼编码也被用来对处理后的音频频谱数据进行编码进一步减小文件大小。2数据存储与传输在一些看似不相关的专业领域哈夫曼编码也在发挥作用。云端数据压缩为了降低海量数据的存储成本一些云服务会使用哈夫曼编码技术。例如有研究利用哈夫曼编码对电力交易等云端的群组共享数据进行内存折叠压缩有效优化了存储空间。卫星通讯在远洋渔业中金枪鱼电浮标通过卫星传输数据时费用高昂。通过部署哈夫曼压缩算法数据压缩比最高可达0.58平均节省了40%的通讯成本。这对于实时传输声呐探测图像等信息的场景来说效益非常显著。3硬件加速与边缘计算随着对实时性要求的提高哈夫曼编码也开始从软件走向硬件以追求更高的速度和更低的功耗。专用硬件芯片VLSI为了应对实时视频压缩、高性能计算等场景对速度的严苛要求研究人员设计了基于超大规模集成电路VLSI的哈夫曼编码器硬件加速器。这类硬件方案能有效克服软件在处理速度和内存访问上的瓶颈同时更省电。嵌入式与物联网IoT在资源受限的嵌入式视觉、物联网传感器网络等场景中需要一种在压缩效率、速度和功耗之间取得平衡的解决方案。最新的研究提出了一种“混合算术-哈夫曼编码”的软硬件协同设计方法它在接近理论压缩极限的同时比纯软件实现的算术编码降低了约68%的每字节压缩能耗。4 应用特点总结哈夫曼编码的使用方式主要呈现出两种模式静态/半静态哈夫曼编码使用一个预先定义好的、固定的码表。例如JPEG和MP3就采用这种方式。标准规定了一套针对典型图像/音频数据的编码表编解码双方都遵循这个标准无需在文件中附带码表提高了效率。动态/自适应哈夫曼编码根据当前正在压缩的具体数据实时统计字符频率并动态构建哈夫曼树。DEFLATE算法ZIP/gzip就采用这种方式因为它能针对每一个特定文件生成最优的编码压缩率通常更高但代价是需要在压缩文件中附带码表信息以便解码。