霍夫曼编码原理以及代码实现

article/2025/9/24 16:48:04

        霍夫曼编码压缩能够实现对于自然语言文件空间大幅压缩。对于普通的文本文件字符,简单起见,如果字符为ASCII,则文本中的每个字符使用7bit来表示,如果文本中有大量的重复相同序列,使用ASCII编码来保存存储会造成大量的空间浪费,现在利用霍夫曼编码将文本字符串编码,用较少的比特位表示频率较高的字符,用较多的比特位表示频率较低的字符。

霍夫曼编码的核心思想:

       统计文本串的每个字符出现的频率,构造前缀码的单词查找树,根据单词查找树来构造编译表,根据编译表来逐个编码每个字符,得到最终的编码01序列;对于解码,根据编码01序列,结合单词查找树来译码出文本字符串。

前缀码

        将字符串每个字符按照特定规则编码成01序列时,同时也需要能够根据01序列,能够译码出原始字符串,要求每个字符的编码都不能是其他字符编码前缀,这样最终编码序列进行译码结果是唯一的,这样的字符编码成为前缀码。ASCII编码每位字符使用7bit来表示是一种定长前缀码,霍夫曼编码是一种变长前缀码,即每个字符的编码长度不一致,频率越高则编码长度越短。

单词查找树

可以使用单词查找树来表示前缀码,单词查找树的叶子结点对应具体的字符,每个节点的左路径看成0,右路径看成1,每个字符的01编码序列为从根路径到该字符叶子结点的路径编码。

                        

如上所示是对于序列ABRACADABRA!的两种前缀码表示,每个字符编码为根节点到字符叶子结点路径01编码,不同的前缀码压缩后的比特程度不同,霍夫曼编码是一种最优前缀码,即编码后长度最短。

具体霍夫曼树的构造过程如下:

定义霍夫曼树节点数据结构

        //霍夫曼树节点private static class Node implements Comparable<Node>{//叶子节点表示编码字符  非叶子节点为空private char ch;//字符出现的频率private int freq;private final Node left, right;Node(char ch, int freq, Node left, Node right) {this.ch = ch;this.freq = freq;this.left = left;this.right = right;}public boolean isLeaf(){return left==null && right==null;}//后面使用优先级队列来保存Node节点  注意Node节点排序规则  freq小的在前 @Overridepublic int compareTo(Node o) {return this.freq - o.freq;//return o.freq - this.freq;}@Overridepublic String toString() {StringBuilder builder = new StringBuilder();builder.append("Node [ch=");builder.append(ch);builder.append(", freq=");builder.append(freq);builder.append(", left=");builder.append(left);builder.append(", right=");builder.append(right);builder.append("]");return builder.toString();}	}

由于后序要根据节点的字符频率freq来进行排序,因此Node实现Comparable接口。

假定待编码字符串为ASCII字符,取字母表基数为R=256,利用数组索引来统计记录字符以及出现频率的关系。

               char[] input = s.toCharArray();//要压缩的字符串字符个数int total = input.length;//统计字符串中每个字符出现的频率int[] freq = new int[R];for(int i=0; i<total; i++){freq[input[i]]++;}System.out.println("freq = " + Arrays.toString(freq));  

霍夫曼树的构造

使用优先级队列,遍历字符索引数组,将出现的字符以及频率,依次加入到优先级队列中并根据出现频率来进行降序排列;

循环操作,每次取出队列中的最小值以及次小值,将两个节点合并成一个新节点,新节点频率为两者之和,新节点左右孩子为两者,将新节点加入到优先级队列中排序,依次循环,直到队列中只有一个节点,此时该节点即为霍夫曼树的根节点。

        private static Node buildTree(int[] freq) {PriorityQueue<Node> pq = new PriorityQueue<>();for(int i=0; i<freq.length; i++){if(freq[i]>0){//System.out.println((char)i);pq.add(new Node((char)i, freq[i], null, null));}}System.out.println("pq = " + pq.toString());while(pq.size()>1){Node l1 = pq.poll();Node l2 = pq.poll();//System.out.println(l1.ch + " " + l1.freq + " " + l2.ch + " " + l2.freq);pq.add(new Node('\0', l1.freq+l2.freq, l1, l2));}			return pq.poll();}

根据编译表进行编码

根据构造的霍夫曼树,从根节点到某一具体的叶子节点,可以获取某一字符对应的01编码序列,遍历所有情况,得到每个字符与编码序列的对应关系,该对应关系即为编译表。利用String[R]来保存每个字符对应的01编码序列,递归思想,如果当前节点为叶子结点则将编码字符串存储在String[R]相应位置,如果当前节点不为叶子节点,则依次进入到左右子树中递归遍历,相应编码字符也要增加0或1,这里0或1是二进制1bit而不是字符'0''1',这里为了便于表示演示,使用字符来代替。

                //构造编译表  每个字符对应的01序列String[] st = new String[R];buildCode(st, root, "");System.out.println("st = " + Arrays.toString(st));private static void buildCode(String[] st, Node n, String s) {//System.out.println(n);if(n.isLeaf()){st[n.ch] = s;return;}//对于霍夫曼树 左边为0   右边为1//这里的'1'和'0'应为bit 二进制  这里为了显示方便使用字符'1'//'1'字符占用7个bits 而二进制1占用1bitsbuildCode(st, n.left, s+'0');buildCode(st, n.right, s+'1');		}

由编译表,即可以实现对于字符串的编码,遍历字符串的每一个字符x,到编译表String[x]中寻找相应的编码串,组合所有字符编码即可以得到最终编码比特串。

                StringBuilder sb = new StringBuilder();for(int i=0; i<total; i++){//System.out.println(input[i] + "  " + st[input[i]]);sb.append(st[input[i]]);}

根据霍夫曼树进行译码

由编码比特串译码原始字符串,需要使用单词查找树,由于霍夫曼编码是前缀码,对于编码比特串从根节点开始,0进入左子树,1进入右子树,如果某个节点为叶子结点则说明前段比特序列是该字符的编码,将该字符记录;重新从根节点开始继续遍历比特串即可。

        //给定二进制序列   根据霍夫曼树译码public static String expand(String s){StringBuilder sb = new StringBuilder();System.out.println("trieStr = " + trieStr);Node root = readTrie(trieStr);System.out.println("reaftrie = " + root);for(int i=0; i<s.length();){Node x = root;while(!x.isLeaf()){//从字符串中读取每一个字符来模拟读取二进制bitif(s.charAt(i++)=='1'){x = x.right;}else{x = x.left;}}//System.out.println(x.ch);sb.append(x.ch);//i++;}return sb.toString();}

由于编码以及译码场所可能不同,因此为了便于译码,应该将编码过程的单词查找树按照某种规则保存起来,然后译码时根据保存序列再构造出单词查找树。

霍夫曼树存储与加载

霍夫曼树的存储

使用前序遍历,如果节点为非叶子结点记录0,如果为叶子结点记录1和节点字符;频率不需要记录,译码时不需要使用频率,只需要根据比特串寻找从根路径到某个叶子节点即可。

         //由于java  String 特性函数传递不会改变值   private static void saveTrie(Node x, StringBuilder trieStr) {//中序遍历 如果是叶子结点打印1 打印节点字符值  如果是非叶子结点打印0//这里的'1'和'0'应为bit 二进制  这里为了显示方便使用字符'1'//'1'字符占用7个bits 而二进制1占用1bitsif(x.isLeaf()){trieStr.append('1');trieStr.append(x.ch);return;}else{trieStr.append('0');}saveTrie(x.left, trieStr);saveTrie(x.right, trieStr);}

霍夫曼树的加载

使用全局变量index来记录遍历比特串的索引位置, 如果比特位1则说明下一个为叶子结点字符,新建一个节点;如果为0,则说明后面为该节点的左右子树序列,递归可以得到最终霍夫曼树。

       //函数指针传递问题  int类型  函数参数传递  值拷贝一次 原来值不变//java函数参数传递问题//实参  形参   实参是对于形参的拷贝  原始类型相当于值拷贝   引用类型相当于引用拷贝//引用拷贝时由于实参引用以及形参引用指向同一个对象,因此形参值会改变实参值//将index值改成全局变量private static Node readTrie(StringBuilder s){index++;if(index>s.length()-1) return null;//System.out.println("index = " + index + " " + s.charAt(index));	if(s.charAt(index)=='1'){//System.out.println("node create = " + s.charAt(index+1));index++;return new Node(s.charAt(index), 1, null, null);}//假定此时index=i 需要进行两个递归调用  进入第一个递归调用  index=i+1   第一个调用返回  此时第二个传值仍为index=i+1//这里的效果应该为第一个调用index=i+1    第二个调用为index=i+2return new Node('\0', 0, readTrie(s), readTrie(s)); }

最终的编码代码

        //根据霍夫曼树  进行编码public static String compress(String s){char[] input = s.toCharArray();//要压缩的字符串字符个数int total = input.length;//统计字符串中每个字符出现的频率int[] freq = new int[R];for(int i=0; i<total; i++){freq[input[i]]++;}System.out.println("freq = " + Arrays.toString(freq));//构造霍夫曼树Node root = buildTree(freq);System.out.println("root = " + root);//构造编译表  每个字符对应的01序列String[] st = new String[R];buildCode(st, root, "");System.out.println("st = " + Arrays.toString(st));//保存解码用的单词查找树saveTrie(root, trieStr);System.out.println("trieStr = " + trieStr.toString());System.out.println("total = " + total);StringBuilder sb = new StringBuilder();for(int i=0; i<total; i++){//System.out.println(input[i] + "  " + st[input[i]]);sb.append(st[input[i]]);}System.out.println("origin length = " + input.length*7);System.out.println("compress length = " + sb.length() + " result = " + sb.toString());return sb.toString();}

最终的译码代码

        //给定二进制序列   根据霍夫曼树译码public static String expand(String s){StringBuilder sb = new StringBuilder();System.out.println("trieStr = " + trieStr);Node root = readTrie(trieStr);System.out.println("reaftrie = " + root);for(int i=0; i<s.length();){Node x = root;while(!x.isLeaf()){//从字符串中读取每一个字符来模拟读取二进制bitif(s.charAt(i++)=='1'){x = x.right;}else{x = x.left;}}//System.out.println(x.ch);sb.append(x.ch);//i++;}return sb.toString();}

测试如下;

                String s = "itwasthebestoftimesitwastheworstoftimesLF";String r = compress(s);String e = expand(r);System.out.println("expand = " + e);System.out.println("code rate = " + r.length()/((double)s.length()*7));

结果如下:

trieStr = 001t001f1h1i001e01w1o01s001m1a001F1L01b1r
total = 41
origin length = 287
compress length = 145 result = 0110010101110111000010110011111010011000101101000001111100100110011001010111011100001011001010101111111111000101101000001111100100110111101111100
trieStr = 001t001f1h1i001e01w1o01s001m1a001F1L01b1r
reaftrie = Node [ch=

这里使用字符‘0’来代替比特0,将字符串编码后得到的字符串实际为比特序列,即字符串长度为比特串长度。可知编码前字符串比特位287,编码后仅为145,编码效率约为50%。

https://github.com/ChenWenKaiVN/TreeSWT/blob/master/src/com/tree/string/compress/Huffman.java


http://chatgpt.dhexx.cn/article/2xsHA15Z.shtml

相关文章

霍夫曼编码(huffman coding) (java实现)

文章目录 一、浅谈赫夫曼编码二、获取赫夫曼编码1.获取字符出现的次数2.创建赫夫曼树3.指定编码 三、代码实现1.指定编码代码2.完整代码 总结 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考 一、浅谈赫夫曼编码 赫夫曼编码(Huffman Coding)&#xff0c…

霍夫曼树:霍夫曼编码(Huffman Tree:Huffman Coding)

一、简介 霍夫曼树常处理符号编写工作。根据整组数据中符号出现的频率高低&#xff0c;决定如何给符号编码。如果符号出现的频率越高&#xff0c;则给符号的码越短&#xff0c;相反符号的号码越长。 相关术语 路径&#xff1a;从书中一个节点到另一个节点之间的分支构成这两个…

霍夫曼编码

霍夫曼在1952年提出了霍夫曼编码&#xff0c;霍夫曼编码是一种无损的统计编码方法&#xff0c;利用信息符号概率分布特性来改编字长进行编码。适用于多元独立信源。霍夫曼编码对于出现概率大的信息符号用字长小的符号表示&#xff0c;对于出现概率小的信息用字长大的符号代替。…

霍夫曼(Huffman)编码算法详解之C语言版

一、Huffman编码 霍夫曼(Huffman)树是一类带权路径长度最短的二叉树树。Huffman树的一个非常重要的应用就是进行Huffman编码以得到0-1码流进行快速传输。 在电报收发等数据通讯中&#xff0c;常需要将传送的文字转换成由二进制字符0、1组成的字符串来传输。为了使收发的速度提…

哈夫曼编码

哈夫曼编码 概念前缀码的二叉树及权值哈夫曼编码的设计思想 实例伪代码 概念 哈夫曼编码是一种字符编码方式&#xff0c;是可变长编码的一种&#xff0c;1952年提出&#xff0c;依据字符在文件中出现的频率来建立一个用0,1串表示各字符&#xff0c;使平均每个字符的码长最短的…

图像处理—霍夫曼编码

图像压缩编码是专门研究图像数据压缩的技术&#xff0c;就是尽量减少表示数据图像所需要的数据量。 本章主要介绍图像压缩编码的基础知识&#xff0c;重点讲解常用的图像压缩编码方法&#xff0c;如霍夫曼编码、香农编码、算术编码、行程编码和预测编码及编码方法的MATLAB实现&…

哈夫曼编码(理解)

基础理解 什么是哈夫曼树&#xff08;Huffman Tree&#xff09; 给定N个带权值的叶子节点&#xff0c;如何构造出一个带权路径最小的二叉树&#xff1f; 在数据结构理论中&#xff0c;哈夫曼树又称为最优树&#xff0c;相关的知识点还有哈弗曼编码等。在正式介绍哈夫曼树之前…

学弟学妹们,学会霍夫曼编码后,再也不用担心网络带宽了!

CSDN 的学弟学妹们&#xff0c;大家好&#xff0c;我是沉默王二。 今天来给大家普及一下霍夫曼编码&#xff08;Huffman Coding&#xff09;&#xff0c;一种用于无损数据压缩的熵编码算法&#xff0c;由美国计算机科学家大卫霍夫曼在 1952 年提出——这么专业的解释&#xff…

哈夫曼编码详解

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

赫夫曼编码

一 基本介绍 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;点我打开 原作者博…