哈夫曼编码详解

article/2025/9/24 18:03:56

一:基本介绍

        哈夫曼编码也翻译为    赫夫曼编码(Huffman Coding),又称霍夫曼编码,是一种编码方式, 属于一种程序算法 赫夫曼编码是赫哈夫曼树在电讯通信中的经典的应用之一。 赫夫曼编码广泛地用于数据文件压缩。其压缩率通常在20%~90%之间 赫夫曼码是可变字长编码(VLC)的一种。Huffman于1952年提出一种编码方法,称之为最佳编码

1.1:原理剖析

        1:通信领域中信息的处理方式1-定长编码

将 i like like like java do you like a java 定长编码       // 共40个字符(包括空格)  

105 32 108 105 107 101 32 108 105 107 101 32 108 105 107 101 32 106 97 118 97 32 100 111 32 121 111 117 32 108 105 107 101 32 97 32 106 97 118 97  //对应Ascii码

01101001 00100000 01101100 01101001 01101011 01100101 00100000 01101100 01101001 01101011 01100101 00100000 01101100 01101001 01101011 01100101 00100000 01101010 01100001 01110110 01100001 00100000 01100100 01101111 00100000 01111001 01101111 01110101 00100000 01101100 01101001 01101011 01100101 00100000 01100001 00100000 01101010 01100001 01110110 01100001 //对应的二进制 按照二进制来传递信息,总的长度是  359   (包括空格)

        2:  通信领域中信息的处理方式2-变长编码

i like like like java do you like a java       // 共40个字符(包括空格)

d:1 y:1 u:1 j:2  v:2  o:2  l:4  k:4  e:4 i:5  a:5  (空格):9  // 各个字符对应的个数 0=(空格)  ,  1=a, 10=i, 11=e, 100=k, 101=l, 110=o, 111=v, 1000=j, 1001=u, 1010=y, 1011=d  

说明:按照各个字符出现的次数进行编码,原则是出现次数越多的,则编码越小,比如 空格出现了9 次, 编码为0 ,其它依次类推.

按照上面给各个字符规定的编码,则我们在传输  "i like like like java do you like a java" 数据时,编码就是 10010110100...  

字符的编码都不能是其他字符编码的前缀,符合此要求的编码叫做前缀编码, 即不能匹配到重复的编码,例如 1=a,110=o,那么a是o的前缀所以该编码不是前缀编码

        3:通信领域中信息的处理方式3-赫夫曼编码

i like like like java do you like a java       // 共40个字符(包括空格)

d:1 y:1 u:1 j:2  v:2  o:2  l:4  k:4  e:4 i:5  a:5   :9  // 各个字符对应的个数 按照上面字符出现的次数构建一颗赫夫曼树, 次数作为权值.

根据赫夫曼树,给各个字符规定编码 (前缀编码), 向左的路径为0 ,向右的路径为1 

编码如下: o: 1000   u: 10010  d: 100110  y: 100111  i: 101 a : 110     k: 1110    e: 1111       j: 0000       v: 0001 l: 001          : 01

按照上面的赫夫曼编码,我们的"i like like like java do you like a java"   字符串对应的编码为 (注意这里我们使用的无损压缩) 1010100110111101111010011011110111101001101111011110100001100001110011001111000011001111000100100100110111101111011100100001100001110

长度为 : 133

说明: 原来长度是  359 , 压缩了  (359-133) / 359 = 62.9% 此编码满足前缀编码, 即字符的编码都不能是其他字符编码的前缀。不会造成匹配的多义性

注意, 这个赫夫曼树根据排序方法不同,也可能不太一样,这样对应的赫夫曼编码也不完全一样,但是wpl 是一样的,都是最小的,字符总长度相同。 比如: 我们让每次生成的新的二叉树总是排在权值相同的二叉树的最后一个和最前面

二:案例——哈夫曼压缩

功能: 根据赫夫曼编码压缩数据的原理,需要创建 "i like like like java do you like a java" 对应的赫夫曼树

思路: (1) CodeNode{ data (存放数据), weight (权值), left  和 right }                                               (2) 得到  "i like like like java do you like a java"   对应的 byte[] 数组                                             (3)  编写一个方法,将准备构建赫夫曼树的Node 节点放到 List  , 形式 [CodeNode[date=97 ,weight = 5], CodeNode[date=32,weight = 9]......],  体现 d:1 y:1 u:1 j:2  v:2  o:2  l:4  k:4  e:4 i:5  a:5   :9                                                                                                                                           (4) 可以通过List 创建对应的赫夫曼树                                                                                          (5)根据哈夫曼树生成哈夫曼编码表,左路径为0,右路径为1                                                      (6)将转换后的字符串转换为字节数组

package com.atgguigu.huffmanTree;import java.util.*;public class HuffmanCode {static Map<Byte,String> hCode = new HashMap<>();  //存储字符对应编码static StringBuilder stringBuilder = new StringBuilder();   //拼接路径生成编码public static void main(String[] args) {String s = "i like like like java do you like a java";byte[] bytes = s.getBytes();System.out.println(Arrays.toString(bytes));System.out.println("没有压缩的原长度:"+bytes.length);byte[] zip = zip(s);System.out.println(Arrays.toString(zip));System.out.println("哈夫曼编码压缩后的长度:"+zip.length);}/*** 封装方法*/private static byte[] zip(String s){byte[] bytes = s.getBytes(); //获取字节数组List<CodeNode> nodes = getNodes(bytes);  //构建集合存储字符和权值CodeNode huffmanTree = createHuffmanTree(nodes);  //构建哈夫曼树getCode(huffmanTree);  //根据哈夫曼树获取对应编码表byte[] zip = zip(bytes, hCode);  //将字节数组转化为哈夫曼编码字节数组return zip;}/*** 统计字符数目构建对象列表* @param bytes* @return*/private static List<CodeNode> getNodes(byte[] bytes){ArrayList<CodeNode> nodes = new ArrayList<>();Map<Byte,Integer> map = new HashMap<>();//统计每个字符数量for (byte b : bytes) {Integer count = map.get(b);if(count == null){map.put(b,1);}else {map.put(b,count+1);}}//构建CodeNode对象for(Map.Entry<Byte,Integer> entry: map.entrySet()){nodes.add(new CodeNode(entry.getKey(),entry.getValue()));}return  nodes;}//创建哈夫曼树private static CodeNode createHuffmanTree(List<CodeNode> codeNodes){while (codeNodes.size() > 1){Collections.sort(codeNodes);CodeNode leftNode = codeNodes.get(0);CodeNode rightNode = codeNodes.get(1);CodeNode parent = new CodeNode(leftNode.weight+rightNode.weight);parent.left = leftNode;parent.right = rightNode;codeNodes.remove(leftNode);codeNodes.remove(rightNode);codeNodes.add(parent);}return codeNodes.get(0);}//前序遍历public static void preOrder(CodeNode node){if(node != null){node.preOrder();}else {System.out.println("你玩我?空树还传过来");}}//哈夫曼编码表/**** @param node 节点* @param code  路径* @param s  拼接路径*/private static  void getCode(CodeNode node, String code, StringBuilder s){StringBuilder s2 = new StringBuilder(s);s2.append(code);if(node != null){if(node.data == null){ //非叶子结点getCode(node.left, "0",s2);  //左递归getCode(node.right, "1",s2);  //左递归}else { //叶子结点hCode.put(node.data,s2.toString());}}}private static  Map<Byte,String> getCode(CodeNode node){if (node == null){return null;}getCode(node.left,"0",stringBuilder);getCode(node.right,"1",stringBuilder);return hCode;}//压缩生成哈夫曼编码/**** @param bytes 这时原始的字符串对应的 byte[]* @param hCode 生成的赫夫曼编码map* @return 返回赫夫曼编码处理后的 byte[]* 举例: String content = "i like like like java do you like a java"; =》 byte[] contentBytes = content.getBytes();* 返回的是 字符串 "1010100010111111110010001011111111001000101111111100100101001101110001110000011011101000111100101000101111111100110001001010011011100"* => 对应的 byte[] huffmanCodeBytes  ,即 8位对应一个 byte,放入到 huffmanCodeBytes* huffmanCodeBytes[0] =  10101000(补码) => byte  [推导  10101000=> 10101000 - 1 => 10100111(反码)=> 11011000= -88 ] 第一个1代表符号-后面反码* huffmanCodeBytes[1] = -88*/private static byte[] zip(byte[] bytes,Map<Byte,String> hCode){StringBuilder stringBuilder = new StringBuilder();for (byte b : bytes) {stringBuilder.append(hCode.get(b));}//统计byte[] 的长度int len ;if(stringBuilder.length() % 8 == 0){len =stringBuilder.length() / 8;}else {len =stringBuilder.length() / 8 + 1;}byte[] bs = new byte[len];int index = 0;for (int i =0;i<stringBuilder.length();i+=8){String strByte;if(i+8 > stringBuilder.length()-1){strByte = stringBuilder.substring(i);}else {strByte = stringBuilder.substring(i,i+8);}bs[index] = (byte) Integer.parseInt(strByte,2);index++;}return bs;}}class CodeNode implements Comparable<CodeNode>{Byte data; //存放数据(字符)int weight; //权值CodeNode left;CodeNode right;public CodeNode( int weight) {this.weight = weight;}public CodeNode(Byte data, int weight) {this.data = data;this.weight = weight;}@Overridepublic int compareTo(CodeNode o) {return this.weight - o.weight;  //小到大排序}@Overridepublic String toString() {return "CodeNode{" +"data=" + data +", weight=" + weight +'}';}//前序遍历public void preOrder(){System.out.print("==>"+this);if(this.left != null){this.left.preOrder();}if(this.right != null){this.right.preOrder();}}}

二:案例——哈夫曼解压

 

package com.atgguigu.huffmanTree;import java.util.*;public class HuffmanCode {static Map<Byte,String> hCode = new HashMap<>();  //存储字符对应编码static StringBuilder stringBuilder = new StringBuilder();   //拼接路径生成编码public static void main(String[] args) {String s = "i like like like java do you like a java";byte[] bytes = s.getBytes();System.out.println(Arrays.toString(bytes));System.out.println("没有压缩的原长度:"+bytes.length);byte[] zip = zip(s);System.out.println(Arrays.toString(zip));System.out.println("哈夫曼编码压缩后的长度:"+zip.length);byte[] decode = decode(hCode, zip);System.out.println("解压:"+new String(decode));}/**解压* 将一个byte 转成一个二进制的字符串, 如果看不懂,可以参考我讲的Java基础 二进制的原码,反码,补码* @param b 传入的 byte* @param flag 标志是否需要补高位如果是true ,表示需要补高位,如果是false表示不补, 如果是最后一个字节,无需补高位* @return 是该b 对应的二进制的字符串,(注意是按补码返回)*/private static String byteToBitString(boolean flag, byte b) {//使用变量保存 bint temp = b; //将 b 转成 int//如果是正数我们还存在补高位if(flag) {temp |= 256; //按位与 256  1 0000 0000  | 0000 0001 => 1 0000 0001}String str = Integer.toBinaryString(temp); //返回的是temp对应的二进制的补码if(flag) {return str.substring(str.length() - 8);} else {return str;}}//编写一个方法,完成对压缩数据的解码/***解压* @param huffmanCodes 赫夫曼编码表 map* @param huffmanBytes 赫夫曼编码得到的字节数组* @return 就是原来的字符串对应的数组*/private static byte[] decode(Map<Byte,String> huffmanCodes, byte[] huffmanBytes) {//1. 先得到 huffmanBytes 对应的 二进制的字符串 , 形式 1010100010111...StringBuilder stringBuilder = new StringBuilder();//将byte数组转成二进制的字符串for(int i = 0; i < huffmanBytes.length; i++) {byte b = huffmanBytes[i];//判断是不是最后一个字节boolean flag = (i == huffmanBytes.length - 1);stringBuilder.append(byteToBitString(!flag, b));}//把字符串安装指定的赫夫曼编码进行解码//把赫夫曼编码表进行调换,因为反向查询 a->100 100->aMap<String, Byte>  map = new HashMap<String,Byte>();for(Map.Entry<Byte, String> entry: huffmanCodes.entrySet()) {map.put(entry.getValue(), entry.getKey());}//创建要给集合,存放byteList<Byte> list = new ArrayList<>();//i 可以理解成就是索引,扫描 stringBuilderfor(int  i = 0; i < stringBuilder.length(); ) {int count = 1; // 小的计数器boolean flag = true;Byte b = null;while(flag) {//1010100010111...//递增的取出 key 1String key = stringBuilder.substring(i, i+count);//i 不动,让count移动,指定匹配到一个字符b = map.get(key);if(b == null) {//说明没有匹配到count++;}else {//匹配到flag = false;}}list.add(b);i += count;//i 直接移动到 count}//当for循环结束后,我们list中就存放了所有的字符  "i like like like java do you like a java"//把list 中的数据放入到byte[] 并返回byte b[] = new byte[list.size()];for(int i = 0;i < b.length; i++) {b[i] = list.get(i);}return b;}}class CodeNode implements Comparable<CodeNode>{Byte data; //存放数据(字符)int weight; //权值CodeNode left;CodeNode right;public CodeNode( int weight) {this.weight = weight;}public CodeNode(Byte data, int weight) {this.data = data;this.weight = weight;}@Overridepublic int compareTo(CodeNode o) {return this.weight - o.weight;  //小到大排序}@Overridepublic String toString() {return "CodeNode{" +"data=" + data +", weight=" + weight +'}';}//前序遍历public void preOrder(){System.out.print("==>"+this);if(this.left != null){this.left.preOrder();}if(this.right != null){this.right.preOrder();}}}

http://chatgpt.dhexx.cn/article/TY5Dogwv.shtml

相关文章

赫夫曼编码

一 基本介绍 1 赫夫曼编码也翻译为哈夫曼编码(Huffman Coding)&#xff0c;又称霍夫曼编码&#xff0c;是一种编码方式, 属于一种程序算法。 2 赫夫曼编码是赫哈夫曼树在电讯通信中的经典的应用之一。 3 赫夫曼编码广泛地用于数据文件压缩。 其压缩率通常在20%&#xff5e;9…

哈夫曼编码(Huffman Coding)原理详解

哈夫曼编码 哈夫曼编码&#xff0c;又称为哈夫曼编码&#xff08;Huffman Coding&#xff09; 是一种可变长编码&#xff08; VLC, variable length coding)&#xff09;方式&#xff0c;比起定长编码的 ASCII 编码来说&#xff0c;哈夫曼编码能节省很多的空间&#xff0c;因…

霍夫曼编码详解

本专栏包含信息论与编码的核心知识&#xff0c;按知识点组织&#xff0c;可作为教学或学习的参考。markdown版本已归档至【Github仓库&#xff1a;information-theory】&#xff0c;需要的朋友们自取。或者公众号【AIShareLab】回复 信息论 也可获取。 文章目录 霍夫曼编码最佳…

霍夫曼编码(Huffman Coding)

霍夫曼编码(Huffman Coding)是一种编码方法,霍夫曼编码是可变字长编码(VLC)的一种。 霍夫曼编码使用变长编码表对源符号(如文件中的一个字母)进行编码,其中变长编码表是通过一种评估来源符号出现机率的方法得到的,出现机率高的字母使用较短的编码,反之出现机率低的则使用…

Typecho 博客美化

以前用typecho博客&#xff0c;现在学生服务器到期了&#xff0c;先记录到csdn上&#xff0c;以后工作有钱了&#xff0c;再租个服务器写博客:) 代码高亮 https://www.typechodev.com/plugin/482.html 天气功能 使用心知天气带的插件就可以轻松实现了。 https://www.seniverse…

Hexo博客之博客美化

https://lqgjava.github.io/2019/08/24/Hexo博客之博客美化/ 只需阅读这一篇文章&#xff0c;就可以让你的博客变得丰富多彩&#xff0c;有添加卡通人物&#xff0c;添加鼠标点击爱心&#xff0c;添加鼠标指针样式 添加彩色滚动变换、添加背景音乐、添加动态彩带等&#xff0…

CSDN博客美化

排列博客分类 管理博客-分类专栏下修改 双击分类名称&#xff0c;即可编辑&#xff0c;输入“#”“空格”“分类名称”可将一级分类改成二级分类。 显示的效果 分类图标设置 编辑-设置图标&#xff0c;可以这里找阿里素材库 效果图

【CSDN】CSDN博客美化教程

一、效果 二、MarkDown简明教程 MarkDown简明教程 三、摘要 添加摘要&#xff0c;增加可读性。 PS. 其实&#xff0c;可以根据深度学习算法&#xff0c;自动生成摘要。 四、自定义博客栏目 五、修改皮肤

博客美化作业详细教程

博客美化作业 第一步 下载作业资源 第二步 在文件夹内打开下载资源 单击后缀为HTML文件的网页文件&#xff0c;右击用编译器打开 这些软件都可以&#xff08;除了紫色的&#xff09; 第三步 准备工作 首先让我们重新看下作业要求&#xff1a; 必须使用“类选择器”来美化网…

【全网最全的博客美化系列教程】04.访客量统计的实现

全网最全的博客美化系列教程相关文章目录 【全网最全的博客美化系列教程】01.添加Github项目链接 【全网最全的博客美化系列教程】02.添加QQ交谈链接 【全网最全的博客美化系列教程】03.给博客添加一只萌萌哒的小仓鼠 【全网最全的博客美化系列教程】04.访客量统计的实现 【全网…

Hexo博客美化之——IP签名图一网打尽

love421个人博客地址&#xff1a;https://www.makedreamsir.xyz IP签名图可以实时显示来访者的坐标&#xff0c;IP地址&#xff0c;操作系统&#xff0c;浏览器等等。 使用方法&#xff1a;将下面生成的链接插入到合适的位置即可。 项目原地址&#xff1a;点我打开 原作者博…

Web前端:博客美化:一、模板美化

1、选用模板simplememory 2、写css放在 这些会覆盖掉原来的css样式 我是在网上找的css代码二次加工的 : &#xff09; /*1、针对simplememory的修改*/ #google_ad_c1, #google_ad_c2 {display:none;} .syntaxhighlighter a, .syntaxhighlighter div, .syntaxhighlighter code, …

Hexo博客之主题美化

据说 NexT 是使用最多的Hexo主题,原因当然是比较漂亮啦!这个项目托管于github上,你可以fork一下,贡献代码。NexT官网上面给出了详细的主题配置过程,这里只是我的博客使用的一些配置以及NexT网站上配置中需要补充的部分。如果你是从头开始配置,请参考NexT官网。这篇文章介…

csdn写博客美化代码显示

本人写博客只是随笔记录&#xff0c;并不是很正规&#xff0c;见谅。 最近在写博客的时候发现自己贴的代码块就只显示代码&#xff0c;而一些前辈的代码块是这样的&#xff1a; 上图只看格式&#xff0c;代码无实际意义。 下面是我的代码块&#xff1a; 我写的代码块就…

【学习之博客美化】matery主题

为了方便我们这里使用 "Visual Studio Code"打开文件 将博客改为中文 目录&#xff1a;E:\IsQiyaBlog 将 " _config.yml " 文件中代码段 language: en 改为 language: zh-CN 修改主题菜单栏 将它们填上内容 新建分类 categories 页&#xff1a; hexo …

Hexo博客美化之蝴蝶(butterfly)主题魔改

这里写自定义目录标题 首先下载主题配置博客主题_config.yaml配置前须知_config.yaml配置简单介绍 如果参考我的脚手架&#xff0c;大家可以阅读readme和changelog文件&#xff0c;和蝴蝶主题官方文档。 tip: 由于butterfly主题升级至3.0.1&#xff0c;所提供的源码不在进行维护…

Vuepress博客美化技巧

文章目录 导航栏透明背景图填充跳转样式悬浮气泡背景图填充导航栏透明图片中的点击向下跳转的样式悬浮气泡 导航栏透明背景图填充跳转样式悬浮气泡 背景图填充 效果图&#xff1a; 下面的小框扩展到大框&#xff0c;让后面的导航栏透明更完善一些。 编辑Theme/components/Ho…

个人博客美化界面

先看一下我的界面 进入个人博客———管理——设置 在CSS代码框中输入一段代码&#xff0c;如下&#xff1a; /*simplememory*/ #google_ad_c1, #google_ad_c2 {display:none;} .syntaxhighlighter a, .syntaxhighlighter div, .syntaxhighlighter code, .syntaxhighlighter t…

Wordpress(Argon)主题博客美化

前言 常言道&#xff1a;工欲善其事必先利其器&#xff0c;在发表文章前&#xff0c;美化博客&#xff0c;使其利于自己的观看与管理极其重要&#xff0c;所以我四处搜寻&#xff0c;得到了以下美化代码。 但是&#xff0c;有时候我们走得太远&#xff0c;会忘了为什么出发。…

Hexo博客美化

Hexo博客美化 我们搭建好自己的博客以后&#xff0c;可以在hexo官网的 主题 中选择自己喜欢的博客主题&#xff0c;可以直接在主题中点开预览主题样式或者打开主题项目的 github 地址&#xff0c;将该主题下载到我们自己的博客项目中的 theme 文件夹中&#xff0c;然后在 _con…