Huffman编码

news2025/1/27 12:52:23

目录

  • 背景
  • Huffman编码
  • 代码部分

背景

   在数据传输,保存的时候,特别是在数据量特别大的时候传输,保存数据是一件特别麻烦的事。比如逛淘宝的时候,首页会有很多商家展示自己产品的高清图片,如果不对图片进行压缩服务端保存图片会占用大量空间,在将图片传输给客户端的时候传输过程会特别耗时。

  英文字母大小写总共就52个,一本英文书籍几十上百万的英文单词都是由这52个字符排列组合而成,从这个信息可以不难看出这52个字符肯定是大量重复了。

  一本中文小说几百万字,也都是由常用的几千个汉字组合而成。如果是一本玄幻小说,那么在相近的章节中一定大量的重复出现人名,地名,功法境界,以及主角在一段时间内修炼的功法。至于主角名字更是贯穿整本小说一定大量重复。还有作者的风格一定是保持一致的,因此文章有时候在描写一些紧张氛围或者描写反面人物功法比较强悍会反复使用同一个词:比如描述敌人很强的就有:恐怖如斯;功法的境界:在主角的修炼体系中分很多境界,一般情况下每个境界都会细分:前,中,后,圆满,这几个词连带武功境界感觉会重复很多次。还有其他的人名,地名,句式等等都可能大量重复

  图片是由像素点组成的而每个像素点是由rgb:三元素组成。这些都是由0~255的数字表示,因此可以将图片看作一堆数字。一张小的图片的像素点至少也有几千,如果是高清图片估计有上百万个像素点。每一个像素点都是三个0~255的数字,组成图片的像素点一定存在大量的重复数字;一些图片有很大范围的背景色这种情况下,数字更是会发生大量的重复。

  不管是中文,英文还是图片的像素点,如果数据量很大那么肯定存在大量重复字符。一个英文字符是8bit,一个中文字符使用UTF-8编码会占3个字节24bit;如果要压缩数据,从字符编码角度考虑应该怎么去压缩呢?可以思考三秒(PS:就这几段句子已经重复出现重复了很多次了)


Huffman编码

   现在有一段英文文本AAAAAAAAABBCCCDDCCCBBBAAAAABB,这段文本A 出现14次,B出现7次,C出现6次,D出现2次。正常编码,这个文本所占bit:(14+7+6+2)* 8 = 232bit。

  • A的编码是:0100 0001
  • B的编码是:0100 0010
  • C的编码是:0100 0011
  • D的编码是:0100 0100

  我们可以思考下,A,B,C,D这四个字符在这段文本中需要用8bit来表示吗?是不是可以用更少的字符来表示这几个字符?重新给这4个字符编码:

  • A-> 1
  • B-> 10
  • C-> 11
  • D-> 100

  重新编码之后,这段文本所占bit:14 * 1 + 2 * 7 + 2 * 6 + 2 * 3 = 46bit;重新编码之后的文本大小只占原文本的20%左右。在这个例子中,A编码最短,BC次之,D最长我们是按照字母顺序来编码的吗?显然不是的,在给字符编码时,是按照字符使用的频率来决定该如何编码。如果使用次数多也就是频率高的就尽量用短编码,使用次数少频率低就用长编码。这样才能尽可能的降低压缩后的字符长度。Huffman编码在对字符重新编码时的指导思想就是这样的。

  我们继续上面的例子,将A,B,C,D按照上面的方法编码并创建一个对照表。

ABCD
11011100

   根据重新编码后的字符,上面英文文本的二进制编码是 :111111111101011111110010011110111111101010111111010,根据这段编码我们该如何解码,将压缩后的二进制码还原成字符?

在这里插入图片描述
  根据上面的编码我们可以看出,在解压时没法恢复源文本了。最开始的9个1,可以有多种解析方式:可以4个C 一个A,这种情况下A位置变化都有5种。还可以有3C,3A的组合。。。。等等其他组合根本没有办法确定这9个1该如何编码。

  那该如何编码呢?既要按照字符使用频率来编码,又要在解压时能够复原文本。这时候Huffman树就隆重出场了Huffman树的构建很简单,但是构建过程非常巧妙。Huffman树的构建规则:

  • Huffman树是一个二叉树
  • 所有字符节点都是叶子节点
  • 每次都用2个最少使用频率节点来构建新节点;

  这是我自己总结的构造Huffman树的规则,看不懂太正常,在我第一次接触Huffman树估计也是看不懂。下面会用上面的例子图解如何构建一颗Huffman树。


  最开始有 A,B,C,D四个节点,经过排序之后由较小的2个节点生成新的节点。因此选择C,D来生成新节点:8;生成新节点之后C,D节点不再参与后续的过程。

  参与第二次构建Huffman树的新节点:8,B(7),A(14),这3个节点再比较使用次数: 7 < 8 < 14 ;因此使用 B(7),8节点来构建新节点:15;

  第三次参与构建新节点的的节点: 15,A(14);直接用这2个节点生成root节点;Huffman树构建结束。

  可以看出来,使用频率最高的A,从根节点到A的路径是最短的,而使用频率最小的D,从根节点到D的距离是最长的。这路劲长短正好对应了使用频率的高低。如果一个节点链接left节点用 0 表示;链接 right 节点用 1表示;(反过来也可以)那么从root节点到叶子节点的路径就可以用01字符串来表示了;


  A,B,C,D构建的Huffman树形成了新的编码:

ABCD
101001000

  英文:AAAAAAAAABBCCCDDCCCBBBAAAAABB经过构建Huffman树重新编码之后的压缩文本是:1111111110101001001001000000001001001010101111110101一共52字符;只有原文的52/232 = 22%左右大小;

  回到关键问题,如何解压呢?

  也很简单,在解压时只需要对照Huffman树来解压就行。从root节点往下找,找到叶子节点就找到了对应的字符。用这种方式顺序读取二进制码对照Huffman树就可以解析文本了。

代码部分

  • Huffman树的构建:

   //Huffman树节点
    class HMNode{
        int val;
        HMNode left;
        HMNode right;
        boolean leaf;//是否叶子节点
        char str;

        public HMNode(int val){
            this.val = val;
        }
        public HMNode(int val,char str){
            this.val = val;
            leaf = true;
            this.str = str;
        }

        @Override
        public String toString() {
            return "val="+val+";char="+(str=='\0' ? '_':str);
        }
    }


//统计文本各个字符使用频率
    public Map<Character,Integer> count(String text){
        Map<Character,Integer> map = new HashMap<>();
        for (int i = 0; i <text.length() ; i++) {
            char t = text.charAt(i);
            if(map.containsKey(t)){
                map.compute(t,(key,val)->val+1);
            }else{
                map.computeIfAbsent(t,key->1);
            }
        }
        return map;
    }

//构建Huffman树
    public HMNode buildHuffManTree(String text){
        Map<Character,Integer> map = count(text);
        List<HMNode> nodes = new ArrayList<>(map.size()<<1);
        map.forEach((key,val)->nodes.add(new HMNode(val,key)));
        nodes.sort((n1, n2)-> n1.val-n2.val);
        int start = 0;
        while(nodes.size()-2>=start){
            HMNode root  = new HMNode(nodes.get(start).val+nodes.get(start+1).val);
            root.left = nodes.get(start);
            root.right = nodes.get(start+1);
            nodes.add(root);
            nodes.sort((n1, n2)-> n1.val-n2.val);
            start+=2;
        }
        return nodes.get(start);
    }
  • 测试
    @Test
    public void test(){
        String text = "aaabbbbbccccccddddee";
        HMNode root =buildHuffManTree(text);
        List nodes =  new ArrayList();
        nodes.add(root);
        print(nodes);

    }


    public void print(List<HMNode>nodes){

        List<HMNode> c = new ArrayList<>();
        System.out.println("\n");
        for (HMNode hmNode : nodes){
            if(hmNode.left != null)c.add(hmNode.left);
            if(hmNode.right != null)c.add(hmNode.right);
            System.out.print(hmNode+ "\t");
        }
        System.out.println("\n");
        if(!c.isEmpty())print(c);
    }
//结果:char=_	===> 表示新生成的节点
												val=20;char=_	
	


						val=9;char=_									val=11;char=_	



		val=4;char=d				val=5;char=b	 		val=5;char=_	   val=6;char=c	



													val=2;char=e	val=3;char=a	


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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

经历百度、美团两次被裁后,我能在小公司躺平吗?

百度裁员后我进入体制内&#xff0c;专心学习自动化 百度被裁后&#xff0c;我意识到自学效果不佳&#xff0c;跟不上职场的所需&#xff0c;于是有了系统学习的想法。 这时的新工作是在体制内&#xff0c;工作强度不大&#xff0c;时间上也比较自由&#xff0c;便正式成为了…

非零基础自学Golang 第12章 接口与类型 12.5 类型断言

非零基础自学Golang 文章目录非零基础自学Golang第12章 接口与类型12.5 类型断言12.5.1 ok-pattern12.5.2 switch-type第12章 接口与类型 12.5 类型断言 类型断言是使用在接口变量上的操作。 简单来说&#xff0c;接口类型向普通类型的转换就是类型断言。 类型断言的语法是…

【关于时间序列的ML】项目 1 :使用 Python 进行 Covid-19 病例 预测

&#x1f50e;大家好&#xff0c;我是Sonhhxg_柒&#xff0c;希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流&#x1f50e; &#x1f4dd;个人主页&#xff0d;Sonhhxg_柒的博客_CSDN博客 &#x1f4c3; &#x1f381;欢迎各位→点赞…

rk3568 | 瑞芯微平台GPIO引脚驱动编写

最近在玩瑞芯微平台的产品&#xff0c;移植了几个设备的驱动&#xff0c;遇到了一些问题&#xff0c;总结后发现大部分问题都出在了GPIO配置的问题上&#xff0c;写下本篇文章&#xff0c;用来分享一下调试的心得。 有喜欢瑞芯微的朋友&#xff0c;可以加我好友&#xff0c;拉…

JVM的作用,结构

源文件经过编译&#xff0c;生成字节码文件 JVM执行字节码文件&#xff08;实际上就是将字节码解释成具体平台上的机器指令&#xff09; jdk&#xff0c;jre&#xff0c;jvm三者的关系&#xff1a; jvm的组成&#xff1a; (1)类加载器子系统:负责将.class文件加载到JVM中 (2)…

初学编程,我们应该怎么做,十年老鸟带你入门。

问问自己学编程的真正目的&#xff0c;仅仅是想应付考试考证&#xff0c;还是真心想从事编程方面的工作。仅仅处于功利性而不是真心喜欢&#xff0c;人生苦短&#xff0c;劝不要来浪费时间&#xff0c;找其它真心喜欢的事情。不是社会喜欢的&#xff0c;不是父母喜欢的&#xf…

这6个微信隐藏功能你真的知道吗?学到就是赚到

我们常用的软件——微信&#xff0c;它有着许多隐藏功能&#xff0c;这些功能其实是很好用的&#xff0c;但是一直被我们忽略掉&#xff0c;现在我整理出来了&#xff0c;一起来看看吧。1.登录设备管理 我们平常在其它设备登录&#xff0c;第一次登录需要二次验证才能成功&…

jdk11新特性——新的Epsilon垃圾收集器

目录一、Epsilon垃圾收集器概述二、Epsilon垃圾收集器用法三、Epsilon垃圾收集器代码示例四、使用Epsilon垃圾收集器的原因五、使用Epsilon垃圾收集器的主要用途一、Epsilon垃圾收集器概述 A NoOp Garbage CollectorJDK上对这个特性的描述是: 开发一个处理内存分配但不实现任何…

【Flink】Flink GET operation failed: Server side error 从blobserver下载错误

文章目录 1.概述2.服务器端1.概述 flink 报错 Flink GET operation failed: Server side error 从blobserver下载错误 java.io.IOException: GET operation failed: Server side error: /tmp/hadoop-www/nm-lo

技术栈入门-------Redis

使用redis的准备工作 1、在虚拟机上安装redis&#xff08;前提是安装了docker容器&#xff09; 上面使用到的命令 docker pull redis mkdir -p /mydata/redis/confdocker run -p 6379:6379 --name redis -v /mydata/redis/data:/data \ -v /mydata/redis/conf/redis.conf:/et…

Python相关软件下载教程

前言 想要在电脑端运行python程序&#xff0c;需要先下载三个软件&#xff1a;Python解释器&#xff0c;编辑器&#xff08;使用Visual Studio Code&#xff0c;简称VS Code&#xff09;&#xff0c;python软件包管理系统&#xff08;简称pip&#xff09;。 一、MacOS系统安装…

如何使用ArcGIS进行点抽稀(优化版)

概述 有的时候我们手上的数据很密集&#xff08;比如POI数据&#xff09;&#xff0c;全部加载出来会很挤&#xff0c;在我们只需要部分数据的情况下就需要对其进行抽稀&#xff0c;这里为大家介绍一种比较简单的抽稀方法&#xff0c;希望能对大家有所帮助。 按百分比抽稀 在…

ICG-Hydrazide,用于光热治疗或光动力治疗

ICG能够强烈地吸收光能将其转化为热能或产生单线态氧&#xff0c;可用于光热治疗(PTT)或光动力治疗(PDT)。 英文名称&#xff1a;ICG-Hydrazide 外观&#xff1a;固体/粉末 质量纯度&#xff1a;95% 储存条件&#xff1a;-20℃ 结构式&#xff1a; 凯新生物运输说明: 极低…

Linux学习-87-LNMP一键安装过程

17.16 LNMP安装的前期准备&#xff08;LNMP一键安装包下载&#xff09; 手工安装 LNMP 环境&#xff0c;那么同样需要安装大概 10多个源码包&#xff08;根据版本和功能不同而不同&#xff09;。不过&#xff0c;现在网上非常流行的 LNMP 环境的搭建过程是采用 LNMP 一键安装包…

25岁,放弃4年所学专业,年薪20W+,我选择了转行。

25岁,没被迫转行, 是主动选择转行&#xff0c;放弃海外20W年薪“稳定”生活&#xff0c;目前已辞职&#xff0c;正在休整准备寻找工作中。希望我的经历可以给大家带来一点启发和借鉴。 首先自我介绍下。90后大叔&#xff0c;土木工程专业&#xff0c;2017年毕业于中南搬砖摇篮…

非零基础自学Golang 第12章 接口与类型 12.6 小结 12.7 知识拓展

非零基础自学Golang 文章目录非零基础自学Golang第12章 接口与类型12.6 小结12.7 知识拓展12.7.1 非侵入式接口第12章 接口与类型 12.6 小结 了解什么是鸭子类型。掌握接口的定义与实现。了解什么是接口嵌入。掌握空接口的常见用法&#xff0c;如空接口的赋值操作。掌握类型断…

C语言知识学习归纳总结(逐梦篇专栏合集)

上一专栏: 项目实战合集 逐梦编程&#xff0c;让中华屹立世界之巅。 简单的事情重复做,重复的事情用心做,用心的事情坚持做&#xff1b; 文章目录前言一、逐梦篇目录合集二、不忘初心"Hello world"总结壁纸&#xff08;与你同在&#xff09;前言 不知不觉中&#x…

详解事务:Mysql事务、Spring事务、分布式事务

详解事务&#xff1a;Mysql事务、Spring事务、分布式事务&#xff08;一&#xff09;Mysql事务【1】4种隔离级别【2】测试案例&#xff08;二&#xff09;Spring事务【1】准备测试代码&#xff08;1&#xff09;添加依赖&#xff08;2&#xff09;配置文件&#xff08;3&#x…

《classnames源码》阅读笔记

源码目录总览 参考官方文档中的内容&#xff0c;我们可以知道classnames 有一个主要版本&#xff08;index&#xff09;和两个替代版本 &#xff08;分别是dedupe 和 bind&#xff09;。在看目录的时候也可以发现 classnames 具有多个对外暴露的入口。 index.js是classnames的主…

Spring MVC【参数的获取与数据返回】

Spring MVC【参数的获取与上传】&#x1f34e;一. 获取参数&#x1f352;1.1 获取单个参数&#x1f352;1.2 获取多个参数&#x1f352;1.3 获取多个参数&#x1f352;1.4 获取URL中参数 (PathVariable)&#x1f34e;二. 上传文件 (RequestPart)&#x1f352;2.1 postman模拟上…