哈夫曼编码(上)

文章目录

    • 问题引入
    • 哈夫曼编码的编写
      • 总述
      • 步骤一
      • 步骤二
      • 步骤三
      • 步骤四
    • 实现代码如下

在这里插入图片描述

问题引入

哈夫曼编码通常用于通信领域,是对较长信息进行压缩,然后发送到指定的位置,是为了节省发送信息占用的空间。
通常来说,如果信息中字符的重复次数越多,那么哈夫曼编码后所占的空间就越小,这也是我们为什么使用哈夫曼编码的原因,同时,哈夫曼编码还是天然的前缀编码,这让它与其他编码方式(定长编码,变长编码)相比,具有天然的优势。

哈夫曼编码的编写

总述

1.将字符串对应的字节数组变为list集合
2.创建list集合对应的哈夫曼树
3.得到对应的哈夫曼编码
4.根据哈夫曼编码得到最后压缩的byte[]

步骤一

首先我们需要一个字符串,此字符串将会用哈夫曼编码压缩为最后的byte数组。
比如为"i like like like java do you like a java"。
我们需要一个Node节点用来存储数据和对应的权值,这个节点为二叉树的节点。
我们将此字符串变为字节数组,然后统计各个字符出现的次数,将该字符作为Node节点的存储数据,
出现的次数作为Node节点对应的权值,统计之后将所有的节点放入List集合中保存。

//将对应字符串对应的byte数组转为list集合
private static List<Node2> getNodes(byte[] bytes){//创建一个ListList<Node2> nodes = new ArrayList<Node2>();//存储每一个byte出现的次数Map<Byte,Integer> counts = new HashMap<>();for (byte b : bytes) {Integer count = counts.get(b);if(count == null){counts.put(b,1);}else {counts.put(b,count + 1);}}//把每一个键值对转成一个Node对象,并加入nodes集合for (Map.Entry<Byte, Integer> entry : counts.entrySet()) {nodes.add(new Node2(entry.getKey(),entry.getValue()));}return nodes;}
//创建Node,存数据和权值
class Node2 implements Comparable<Node2>{Byte data; //存放数据本身,比如'a' = 97int weight; //权值,表示字符出现的次数Node2 left;Node2 right;public Node2(Byte data, int weight) {this.data = data;this.weight = weight;}@Overridepublic int compareTo(Node2 o) {return this.weight - o.weight;}@Overridepublic String toString() {return "Node2{" + "data=" + data + ", weight=" + weight + '}';}//前序遍历public void preOrder(){System.out.println(this);if(this.left != null){this.left.preOrder();}if(this.right != null){this.right.preOrder();}}
}

步骤二

接下来我们就需要用我们刚刚得到的list集合来创建哈夫曼树了。
将list集合排序,把list集合的前两个节点拿出来,作为新树的两个子节点,然后将该新树放回list集合中,将原本拿出的两个子节点从list集合中移除,然后再次排序,重复上面步骤,就能得到一颗哈夫曼树。
当哈夫曼树构建完成,list集合就剩下一个节点,此节点就是哈夫曼树的根节点。
假设list集合中放的node节点的权值为1 2 4 5 6.
简述过程如下:

在这里插入图片描述

//通过list创建赫夫曼树private static Node2 createHuffmanTree(List<Node2> nodes){while (nodes.size() > 1){//排序Collections.sort(nodes);//取出前两个最小的二叉树Node2 left = nodes.get(0);Node2 right = nodes.get(1);//创建一课新的二叉树,它的根节点没有data,只有权值Node2 parent = new Node2(null, left.weight + right.weight);parent.left = left;parent.right = right;nodes.add(parent);//将已经处理的两个二叉树从nodes删除nodes.remove(left);nodes.remove(right);}return nodes.get(0);}

步骤三

哈夫曼树我们已经构建完毕了,接下来我们需要得到对应的哈夫曼编码。
我们规定,哈夫曼树中的节点,从该节点到左子节点路径的值为0,到右子节点的值为1。
接下来就是递归的创建赫夫曼编码表了,此表我们用Map<Byte,String> buffmanCodes来表示,map的key表示对应的字符,map的value表示赫夫曼编码,还需要一个StringBuilder stringBuilder,用来存放某个叶子节点的路径。
从根节点开始,当我们遇到非叶子节点就递归处理,向左递归,将0加入stringBuilder,然后向右递归,将1加入stringBuilder。当我们遇到叶子节点时,stringBuilder已经将该路径的0或1收集完毕,将该节点的data作为key,stringBuilder里存储的字符串作为value存入buffmanCodes。
简述过程如下:
在这里插入图片描述

//1.将赫夫曼编码表存放Map<Byte,String>形式
static Map<Byte,String> buffmanCodes = new HashMap<Byte,String>();
//2.在生成赫夫曼编码表时,需要去拼接路径,定义一个StringBuilder 存储某个叶子节点的路径
static StringBuilder stringBuilder = new StringBuilder();
private static void getCodes(Node2 node,String code,StringBuilder stringBuilder){StringBuilder builder = new StringBuilder(stringBuilder);//将code加入builderbuilder.append(code);if(node != null){//判断当前节点是叶子节点还是非叶子节点if(node.data == null){ //非叶子节点//递归处理//向左递归getCodes(node.left,"0",builder);//向右递归getCodes(node.right,"1",builder);}else{//找到叶子节点buffmanCodes.put(node.data,builder.toString());}}}

步骤四

根据赫夫曼编码将数据压缩得到最后的byte[]。
首先利用buffmanCodes 将 bytes转成 赫夫曼编码对应的字符串,用StringBuilder stringBuilder来接收。
接下来我们统计我们要压缩byte的长度int len,如果stringBuilder的长度恰好为8的倍数,则len = stringBuilder.length / 8,否则就是len = stringBuilder.length / 8 + 1,如果基础好的话很容易想到
int len = (stringBuilder.length() + 7) / 8。
最后我们创建压缩后的byte数组byte[] huffmanCodeBytes = new byte[len]。
读取stringBuilder的值,每八位作为一个字节,将该字节放入huffmanCodeBytes,当len的长度为8的倍数时,我们就按上述处理方法即可,但是当len的长度不为8的倍数,则最后几位的取法应有所不同。
简述过程如下:
在这里插入图片描述

 private static byte[] zip(byte[] contentBytes,Map<Byte,String> huffmanCodes){//1.利用huffmanCodes 将 bytes转成 赫夫曼编码对应的字符串StringBuilder stringBuilder = new StringBuilder();for (byte b : contentBytes) {stringBuilder.append(huffmanCodes.get(b));}int len;if(stringBuilder.length() % 8 == 0){len = stringBuilder.length() / 8;}else {len = stringBuilder.length() / 8 + 1;}//创建存储压缩后的bute数组byte[] huffmanCodeBytes = new byte[len];int index = 0; //记录是第几个byte//因为每8位对应一个byte,所以步长 + 8for (int i = 0; i < stringBuilder.length(); i+=8) {String strByte;if(i + 8 > stringBuilder.length()){strByte = stringBuilder.substring(i);}else {strByte = stringBuilder.substring(i,i + 8);}//将strByte转为一个byte,放入huffmanCodeByteshuffmanCodeBytes[index++] = (byte) Integer.parseInt(strByte,2);}return huffmanCodeBytes;}

实现代码如下

public class HuffmanCode {public static void main(String[] args) {String str = "i like like like java do you like a java";byte[] bytes = str.getBytes(StandardCharsets.UTF_8);byte[] huffmanCodeBytes = huffmanZip(bytes);System.out.println("压缩后的结果为 : " + Arrays.toString(huffmanCodeBytes));}private static byte[] huffmanZip(byte[] contentBytes){List<Node2> nodes = getNodes(contentBytes);//根据nodes创建的赫夫曼树Node2 root = createHuffmanTree(nodes);//得到对应的赫夫曼编码Map<Byte, String> huffmanCodes = getCodes(root);//根据赫夫曼编码huffmanCodes得到最后压缩的byte[]byte[] huffmanCodeBytes = zip(contentBytes, huffmanCodes);return huffmanCodeBytes;} private static byte[] zip(byte[] contentBytes,Map<Byte,String> huffmanCodes){//1.利用huffmanCodes 将 bytes转成 赫夫曼编码对应的字符串StringBuilder stringBuilder = new StringBuilder();for (byte b : contentBytes) {stringBuilder.append(huffmanCodes.get(b));}int len;if(stringBuilder.length() % 8 == 0){len = stringBuilder.length() / 8;}else {len = stringBuilder.length() / 8 + 1;}//创建存储压缩后的bute数组byte[] huffmanCodeBytes = new byte[len];int index = 0; //记录是第几个byte//因为每8位对应一个byte,所以步长 + 8for (int i = 0; i < stringBuilder.length(); i+=8) {String strByte;if(i + 8 > stringBuilder.length()){strByte = stringBuilder.substring(i);}else {strByte = stringBuilder.substring(i,i + 8);}//将strByte转为一个byte,放入huffmanCodeByteshuffmanCodeBytes[index++] = (byte) Integer.parseInt(strByte,2);}return huffmanCodeBytes;}//为了调用方便,我们重载getCodesprivate static Map<Byte,String> getCodes(Node2 root){if(root == null){return null;}else {getCodes(root.left,"0",stringBuilder);getCodes(root.right,"1",stringBuilder);return buffmanCodes;}}static Map<Byte,String> buffmanCodes = new HashMap<Byte,String>();static StringBuilder stringBuilder = new StringBuilder(); private static void getCodes(Node2 node,String code,StringBuilder stringBuilder){StringBuilder builder = new StringBuilder(stringBuilder);//将code加入builderbuilder.append(code);if(node != null){//判断当前节点是叶子节点还是非叶子节点if(node.data == null){ //非叶子节点//递归处理//向左递归getCodes(node.left,"0",builder);//向右递归getCodes(node.right,"1",builder);}else{//找到叶子节点buffmanCodes.put(node.data,builder.toString());}}}private static List<Node2> getNodes(byte[] bytes){//创建一个ListList<Node2> nodes = new ArrayList<Node2>();//存储每一个byte出现的次数Map<Byte,Integer> counts = new HashMap<>();for (byte b : bytes) {Integer count = counts.get(b);if(count == null){counts.put(b,1);}else {counts.put(b,count + 1);}}//把每一个键值对转成一个Node对象,并加入nodes集合for (Map.Entry<Byte, Integer> entry : counts.entrySet()) {nodes.add(new Node2(entry.getKey(),entry.getValue()));}return nodes;}private static Node2 createHuffmanTree(List<Node2> nodes){while (nodes.size() > 1){//排序Collections.sort(nodes);//取出前两个最小的二叉树Node2 left = nodes.get(0);Node2 right = nodes.get(1);//创建一课新的二叉树,它的根节点没有data,只有权值Node2 parent = new Node2(null, left.weight + right.weight);parent.left = left;parent.right = right;nodes.add(parent);//将已经处理的两个二叉树从nodes删除nodes.remove(left);nodes.remove(right);}return nodes.get(0);}
}//创建Node,存数据和权值
class Node2 implements Comparable<Node2>{Byte data; //存放数据本身,比如'a' = 97int weight; //权值,表示字符出现的次数Node2 left;Node2 right;public Node2(Byte data, int weight) {this.data = data;this.weight = weight;}@Overridepublic int compareTo(Node2 o) {return this.weight - o.weight;}@Overridepublic String toString() {return "Node2{" + "data=" + data + ", weight=" + weight + '}';}//前序遍历public void preOrder(){System.out.println(this);if(this.left != null){this.left.preOrder();}if(this.right != null){this.right.preOrder();}}
}

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何自定义Linux命令

说明&#xff1a;本文介绍如何将自己常用的命令设置为自定义的命令&#xff0c;以下操作在阿里云服务器CentOS上进行。 修改配置文件 修改配置文件前&#xff0c;先敲下面的命令查看当前系统配置的shell版本 echo $SHELL或者 echo $0区别在于&#xff0c;$SHELL查看的是系统…

合并连个有序链表(递归)

21. 合并两个有序链表 - 力扣&#xff08;LeetCode&#xff09; 2.讲解算法原理 2.1重复子问题 2.2只关心其中的一个子问题是如何解决的 2.3细节&#xff0c;递归出口 3.小总结 &#xff08;循环&#xff08;迭代&#xff09;VS 递归&#xff09;&#xff08;递归VS深搜&…

【数字IC设计】芯片设计中的RDC

RDC问题定义 在芯片设计中,RDC是reset domain crossing 的缩写,类似于CDC(clock domain crossing),由于现在SOC芯片是有很多ECUs组成,为了使整个系统能够快速从复位中恢复, 用户希望SOC里面每个ECU模块都可以有自己独立的异步复位信号,这样可以在出问题的时候只复位有错…

我研究了4年的小行星,在交论文的前一天......炸了!

绞尽脑汁地想选题&#xff0c;跟导师约谈&#xff0c;各种托关系找受访者&#xff0c;一次一次地试验&#xff0c;只为得到理想的数据&#xff0c;无数个挑灯夜读的夜晚&#xff0c;反反复复地Proof Read… 终于&#xff0c;你敲完了Conclusion的最后一个字… 看着word里显示的…

机器学习第37周周报 GGNN

文章目录 week37 GGNN摘要Abstract一、文献阅读1. 题目2. abstract3. 网络架构3.1 数据处理部分3.2 门控图神经网络3.3 掩码操作 4. 文献解读4.1 Introduction4.2 创新点4.3 实验过程4.3.1 传感器设置策略4.3.2 数据集4.3.3 实验设置4.3.4 模型参数设置4.3.5 实验结果 5. 结论 …

GDPU unity游戏开发 角色控制器与射线检测

在你的生活中&#xff0c;你一直扮演着你的角色&#xff0c;别被谁控制了。 小试 1. 创建一个角色控制器&#xff0c;通过键盘控制角色控制器的移动&#xff0c;角色控制器与家具发生碰撞后&#xff0c;通过Debug语句打印出被碰撞物体的信息(搜索OnControllerColliderHit的使用…

十个最适合论文写作的GPTs及其应用

文章目录 一、GPTs让一切皆有可能二、最适合论文写作的GPTs及其应用1、[Paper Search Engine](https://chat.openai.com/g/g-9v5gHG9Bo)2、[Academic Paper Specialist&#xff08;学术论文撰写专家&#xff09;](https://chat.openai.com/g/g-jryw3pfsH)3、[Paper Connect 论文…

01-win10安装Qt5

Qt5安装教程 下载Qt5官网下载(下载很慢)镜像网站下载(有些版本没有资源)迅雷下载(推荐)百度网盘下载(推荐)安装Qt5下载Qt5 官网下载(下载很慢) 【注意】:官网下载非常慢,没有镜像下载时常20+ Qt 官网有一个专门的资源下载网站,所有的开发环境和相关工具都可以从这…

实现二叉树的基本操作

博主主页: 码农派大星. 关注博主带你了解更多数据结构知识 1我们先来模拟创建一个二叉树 public class TestBinaryTreee {static class TreeNode{public char val;public TreeNode left;public TreeNode right;public TreeNode(char val) {this.val val;}}public TreeNode …

商家转账到零钱怎么开通?一步步教你玩转微信营销新利器

在数字化营销日新月异的今天&#xff0c;微信支付凭借其便捷、安全的特点&#xff0c;成为了商家不可或缺的支付工具。而其中的“商家转账到零钱”功能&#xff0c;更是为商家提供了一个全新的营销利器。今天&#xff0c;我们就来详细解读一下如何开通这一功能&#xff08;我处…

windows编译opencv4.9

opencv很多人在windows上编译感觉特别麻烦&#xff0c;没有linux下方便&#xff0c;设定以下三点&#xff0c;我们几乎会无障碍。 1 安装cuda&#xff0c;cudnn 安装好cuda&#xff0c;cudnn&#xff0c;把cudnn的头文件&#xff0c;库等等拷贝到cuda的安装目录下面&#xff…

5. 简单说一说uniapp中的语法吧

前言 如果你 知道Vue3并且对Vue3的语法有一定了解&#xff0c;请跳过这一章&#xff0c;由于后续项目主要是基于Vue3TypeScript&#xff0c;因此提前简单概述一些Vue3的基础语法~ 本文的目的是 期望通过对本文的阅读后能对Vue3的每个语法有一个简单的印象&#xff0c;至少要知…