使用Java实现哈夫曼编码

前言

哈夫曼编码是一种经典的无损数据压缩算法,它通过赋予出现频率较高的字符较短的编码,出现频率较低的字符较长的编码,从而实现压缩效果。这篇博客将详细讲解如何使用Java实现哈夫曼编码,包括哈夫曼编码的原理、具体实现步骤以及完整的代码示例。

哈夫曼编码原理

哈夫曼编码的基本原理可以概括为以下几个步骤:

  1. 统计字符频率:遍历输入数据,统计每个字符出现的频率。
  2. 构建哈夫曼树:根据字符的频率构建一棵哈夫曼树。树的每个节点代表一个字符及其频率,树的叶子节点代表具体的字符。
  3. 生成哈夫曼编码:通过遍历哈夫曼树生成每个字符的哈夫曼编码。左子树表示’0’,右子树表示’1’。
  4. 编码数据:将原始数据根据哈夫曼编码表转换为二进制数据。
  5. 解码数据:根据哈夫曼树将二进制数据还原为原始字符。

实现步骤

1. 定义哈夫曼树的节点类

首先定义一个内部类Node,用于表示哈夫曼树的节点。每个节点包含字符、频率、左子节点和右子节点。实现Comparable<Node>接口用于在优先队列中排序。

private static class Node implements Comparable<Node> {private final char ch;     // 字符private final 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;}// 判断是否为叶子节点private boolean isLeaf() {assert ((left == null) && (right == null)) || ((left != null) && (right != null));return (left == null) && (right == null);}// 根据频率比较节点,用于优先队列public int compareTo(Node that) {return this.freq - that.freq;}
}
2. 构建哈夫曼树

根据字符频率构建哈夫曼树。我们使用优先队列来实现该步骤。

private static Node buildTrie(int[] freq) {// 初始化优先队列,并将每个字符及其频率作为单节点树插入队列MinPQ<Node> pq = new MinPQ<Node>();for (char c = 0; c < R; c++)if (freq[c] > 0)pq.insert(new Node(c, freq[c], null, null));// 不断合并频率最小的两棵树,直到剩下一棵树while (pq.size() > 1) {Node left = pq.delMin();Node right = pq.delMin();Node parent = new Node('\0', left.freq + right.freq, left, right);pq.insert(parent);}return pq.delMin();
}
3. 生成哈夫曼编码表

通过遍历哈夫曼树生成每个字符的哈夫曼编码。

private static void buildCode(String[] st, Node x, String s) {if (!x.isLeaf()) {// 递归遍历左子树,路径加'0'buildCode(st, x.left, s + '0');// 递归遍历右子树,路径加'1'buildCode(st, x.right, s + '1');} else {// 叶子节点,记录字符的编码st[x.ch] = s;}
}
4. 压缩数据

读取输入数据,生成哈夫曼编码表,输出编码后的二进制数据。

public static void compress() {// 读取输入字符串并转换为字符数组String s = BinaryStdIn.readString();char[] input = s.toCharArray();// 计算每个字符的频率int[] freq = new int[R];for (int i = 0; i < input.length; i++)freq[input[i]]++;// 构建哈夫曼树Node root = buildTrie(freq);// 建立字符编码表String[] st = new String[R];buildCode(st, root, "");// 输出哈夫曼树以便解码使用writeTrie(root);// 输出原始未压缩的字节数BinaryStdOut.write(input.length);// 使用哈夫曼编码压缩输入for (int i = 0; i < input.length; i++) {String code = st[input[i]];for (int j = 0; j < code.length(); j++) {if (code.charAt(j) == '0') {BinaryStdOut.write(false);} else if (code.charAt(j) == '1') {BinaryStdOut.write(true);} else throw new IllegalStateException("Illegal state");}}// 关闭输出流BinaryStdOut.close();
}
5. 解码数据

读取哈夫曼树和编码后的二进制数据,解码还原原始数据。

public static void expand() {// 从输入流中读取哈夫曼树Node root = readTrie();// 读取原始字节数int length = BinaryStdIn.readInt();// 使用哈夫曼树解码输入的二进制数据并输出字符for (int i = 0; i < length; i++) {Node x = root;while (!x.isLeaf()) {boolean bit = BinaryStdIn.readBoolean();if (bit) x = x.right;else x = x.left;}BinaryStdOut.write(x.ch, 8);}BinaryStdOut.close();
}

完整代码

以下是完整的哈夫曼编码实现代码:

public class Huffman {// 定义扩展ASCII字符集的大小private static final int R = 256;// 防止实例化private Huffman() { }// 哈夫曼树的节点类,实现了Comparable接口以便于优先队列排序private static class Node implements Comparable<Node> {private final char ch;     // 字符private final 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;}// 判断是否为叶子节点private boolean isLeaf() {assert ((left == null) && (right == null)) || ((left != null) && (right != null));return (left == null) && (right == null);}// 根据频率比较节点,用于优先队列public int compareTo(Node that) {return this.freq - that.freq;}}// 压缩方法public static void compress() {// 读取输入字符串并转换为字符数组String s = BinaryStdIn.readString();char[] input = s.toCharArray();// 计算每个字符的频率int[] freq = new int[R];for (int i = 0; i < input.length; i++)freq[input[i]]++;// 构建哈夫曼树Node root = buildTrie(freq);// 建立字符编码表String[] st = new String[R];buildCode(st, root, "");// 输出哈夫曼树以便解码使用writeTrie(root);// 输出原始未压缩的字节数BinaryStdOut.write(input.length);// 使用哈夫曼编码压缩输入for (int i = 0; i < input.length; i++) {String code = st[input[i]];for (int j = 0; j < code.length(); j++) {if (code.charAt(j) == '0') {BinaryStdOut.write(false);} else if (code.charAt(j) == '1') {BinaryStdOut.write(true);} else throw new IllegalStateException("Illegal state");}}// 关闭输出流BinaryStdOut.close();}// 构建哈夫曼树private static Node buildTrie(int[] freq) {// 初始化优先队列,并将每个字符及其频率作为单节点树插入队列MinPQ<Node> pq = new MinPQ<Node>();for (char c = 0; c < R; c++)if (freq[c] > 0)pq.insert(new Node(c, freq[c], null, null));// 不断合并频率最小的两棵树,直到剩下一棵树while (pq.size() > 1) {Node left = pq.delMin();Node right = pq.delMin();Node parent = new Node('\0', left.freq + right.freq, left, right);pq.insert(parent);}return pq.delMin();}// 输出哈夫曼树,用于解码private static void writeTrie(Node x) {if (x.isLeaf()) {BinaryStdOut.write(true);BinaryStdOut.write(x.ch, 8);return;}BinaryStdOut.write(false);writeTrie(x.left);writeTrie(x.right);}// 生成哈夫曼编码表private static void buildCode(String[] st, Node x, String s) {if (!x.isLeaf()) {// 递归遍历左子树,路径加'0'buildCode(st, x.left, s + '0');// 递归遍历右子树,路径加'1'buildCode(st, x.right, s + '1');} else {// 叶子节点,记录字符的编码st[x.ch] = s;}}// 解码方法public static void expand() {// 从输入流中读取哈夫曼树Node root = readTrie();// 读取原始字节数int length = BinaryStdIn.readInt();// 使用哈夫曼树解码输入的二进制数据并输出字符for (int i = 0; i < length; i++) {Node x = root;while (!x.isLeaf()) {boolean bit = BinaryStdIn.readBoolean();if (bit) x = x.right;else x = x.left;}BinaryStdOut.write(x.ch, 8);}BinaryStdOut.close();}// 从输入流中读取哈夫曼树private static Node readTrie() {boolean isLeaf = BinaryStdIn.readBoolean();if (isLeaf) {return new Node(BinaryStdIn.readChar(), -1, null, null);} else {return new Node('\0', -1, readTrie(), readTrie());}}// 主方法,根据参数决定执行压缩或解码public static void main(String[] args) {if (args[0].equals("-")) compress();else if (args[0].equals("+")) expand();else throw new IllegalArgumentException("Illegal command line argument");}
}

总结

哈夫曼编码是一种高效的无损数据压缩算法。本文通过详细的代码示例展示了如何使用Java实现哈夫曼编码的压缩和解压功能。从统计字符频率、构建哈夫曼树、生成哈夫曼编码表到最终的编码和解码,涵盖了哈夫曼编码的全部核心步骤。希望这篇博客能够帮助你更好地理解哈夫曼编码的实现原理和具体的编码实践。

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

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

相关文章

使用VLLM部署llama3量化版

1.首先去魔塔社区下载量化后的llama3模型 git clone https://www.modelscope.cn/huangjintao/Meta-Llama-3-8B-Instruct-AWQ.git 2.跑起来模型 1&#xff09;python -m vllm.entrypoints.openai.api_server --model /home/cxh/Meta-Llama-3-8B-Instruct-AWQ --dtype auto --…

【管理咨询宝藏134】麦肯锡咨询公司为DB物流公司价格体系优化设计方案

本报告首发于公号“管理咨询宝藏”&#xff0c;如需阅读完整版报告内容&#xff0c;请查阅公号“管理咨询宝藏”。 【管理咨询宝藏134】麦肯锡咨询公司为DB物流公司价格体系优化设计方案 【格式】PDF版本 【关键词】麦肯锡、物流、价格战略、定价体系 【核心观点】 - 与竞争对…

TrainingArguments、ModelArguments、DataArguments参数使用(@dataclass)

文章目录 前言一、@dataclass装饰器说明二、transformers.HfArgumentParser参数使用Demo三、field函数四、llava模型参数1、模型参数设置2、数据参数设置3、训练参数设置4、参数解析5、参数传递6、参数添加前言 理解llava相关参数传递方法,有利于我们对模型修改模块使用参数来…

【mysql 安装启动失败】 没有网下 libssl.so.10 not found 如何解决?

问题描述&#xff1a; libssl.so.10 > not found libcrypto.so.10 > not found [rootmysql tools]# ls -l /usr/sbin/mysqld -rwxr-xr-x. 1 root root 64290024 Sep 14 2022 /usr/sbin/mysqld [rootmysql tools]# ldd /usr/sbin/mysqldlinux-vdso.so.1 (0x00007fff97105…

拒绝零散碎片, 一文理清MySQL的各种锁

系列文章目录 学习MySQL先有全局观&#xff0c;细说其发展历程及特点 Mysql常用操作&#xff0c;谈谈排序与分页 拒绝零散碎片&#xff0c; 一文理清MySQL的各种锁&#xff08;收藏向&#xff09; 系列文章目录一、MySQL的锁指什么二、排他与共享三、全局锁&#xff08;Global…

USB学习——12、usb初始化和插拔驱动软件流程大致框架描述

usb初始化和插拔驱动软件流程大致框架描述&#xff1a; 当设备启动时&#xff0c;usb的主机控制器设备驱动&#xff08;HCD&#xff09;和 usb的root hub会先初始化&#xff1a; 1、xhci-plat.c主机控制器驱动那里&#xff0c;__usb_creat_hcd创建usb主机数据结构&#xff0c;m…

【C++】数据类型、函数、头文件、断点调试、输入输出、条件与分支、VS项目设置

四、基本概念 这部分和C语言重复的部分就简写速过&#xff0c;因为我之前写过一个C语言的系列&#xff0c;非常详细。C和C这些都是一样的&#xff0c;所以这里不再一遍遍重复码字了。感兴趣的同学可以翻看我之前的C语言系列文章。 1、数据类型 编程的本质就是操作数据。 操…

从零入手人工智能(4)—— 逻辑回归

1.小故事 一家金融科技公司&#xff0c;公司的首席执行官找到团队提出了一个紧迫的问题&#xff1a;“我们如何提前知道哪些客户可能会违约贷款&#xff1f;” 这让团队陷入了沉思&#xff0c;经过激烈讨论团队中的数据分析师提议&#xff1a;“我们可以尝试使用逻辑回归来预测…

基于51单片机的函数信号发生器

基于51单片机函数信号发生器 &#xff08;仿真&#xff0b;程序&#xff0b;原理图&#xff0b;设计报告&#xff09; 功能介绍 具体功能&#xff1a; 1.LCD1602液晶显示波形种类和频率值&#xff08;10-100HZ&#xff09;&#xff1b; 2.按键设置波形种类和设定频率步进值…

UE5近战对抗系统Tutorial

文章目录 BP_Character 组合攻击Notify State 检测攻击BP_Character 攻击反馈BP_Character 生命系统BP_Character 死亡效果BP_Character 武器系统BP_Enemy 初始化和行为树 BP_Character 组合攻击 首先我们获取攻击动画&#xff0c;在这里使用的是 Easy Combo Buffering 的攻击…

嵌入式系统软件架构设计方法

1.嵌入式系统软件架构设计的目的 嵌入式系统软件架构是开发大型嵌入式系统密集型软件贯穿始终的关键桥梁&#xff0c;同时软件架构也是软件开发的基础。架构设计的目的是&#xff1a; 保证应用的代码逻辑清晰&#xff0c;避免重复的设计&#xff1b;实现软件的可移植性&#…

聊聊JSON

引言 JSON的概念 JSON&#xff08;JavaScript Object Notation&#xff09;是一种轻量级的数据交换格式&#xff0c;它基于JavaScript的一个子集&#xff0c;但独立于语言&#xff0c;这意味着它可以被许多编程语言轻松解析。JSON的简洁性和易读性使其成为Web开发中数据交换的…

jnp.linalg.norm

jnp.linalg.norm 是 JAX 中用于计算向量或矩阵的范数的函数。JAX 是一个用于高性能机器学习研究的 Python 库&#xff0c;它提供了与 NumPy 类似的 API&#xff0c;但支持自动微分和加速计算。jnp 是 JAX 的 NumPy 接口。 jnp.linalg.norm 的基本语法 jnp.linalg.norm(x, ord…

办公软件都需要学习哪些?沈阳计算机办公软件培训

学习办公软件可以提高你的工作效率和专业技能。以下是一些常见的办公软件及其需要学习的关键内容&#xff1a; 1. Microsoft Word 或 Google Docs&#xff08;文字处理软件&#xff09; 基本操作&#xff1a;创建、保存、打开和打印文档。文本格式化&#xff1a;字体、段落、…

掌握 NumPy:高效数组处理综合指南(第 2/2 部分)

照片由 兹比内克布里瓦尔 on Unsplash 一、介绍 欢迎来到我关于 NumPy 的教程的第二部分&#xff01;之前&#xff0c;我们已经介绍了以下列表中的前 7 章。现在在这篇文章中&#xff0c;我们将从第 8 章一直到第 14 章。 Numpy 安装数组初始化Numpy 数组限制计算速度和内存使用…

量子计算:1 从薛定谔的猫开始

大模型技术论文不断&#xff0c;每个月总会新增上千篇。本专栏精选论文重点解读&#xff0c;主题还是围绕着行业实践和工程量产。若在某个环节出现卡点&#xff0c;可以回到大模型必备腔调或者LLM背后的基础模型重新阅读。而最新科技&#xff08;Mamba,xLSTM,KAN&#xff09;则…

序列1bp插入有什么影响

1bp插入突变&#xff08;1个碱基插入&#xff09;在基因序列中通常会引起以下几种影响&#xff1a; 移码突变&#xff08;Frameshift Mutation&#xff09;&#xff1a; 插入的一个碱基会改变插入点之后所有的密码子&#xff0c;导致读取框的移动。这种变化通常会引起整个蛋白质…

PCL 点云RANSAC+SVD提取平面

文章目录 一、简介二、实现代码三、实现效果参考资料一、简介 这里的思路很简单,RANSAC算法具有很不错的抗噪性,但是得到的平面仍然是基于原始数据中的点,这会导致提取出的平面不够精确;因此我们可以基于RANSAC算法提取出的点云,使用最小二乘法拟合出更为精确的平面参数。…

基于PHP的民宿管理系统

有需要请加文章底部Q哦 可远程调试 基于PHP的民宿管理系统 一 介绍 此民宿管理系统基于原生PHP开发&#xff0c;数据库mysql&#xff0c;前端jquery.js和echarts.js。系统角色分为用户和管理员。用户可以在线浏览和预订民宿&#xff0c;管理员登录后台进行相关管理等。(在系统…

【python】OpenCV——Color Correction

文章目录 cv2.aruco 介绍imutils.perspective.four_point_transform 介绍skimage.exposure.match_histograms 介绍牛刀小试遇到的问题 参考学习来自 OpenCV基础&#xff08;18&#xff09;使用 OpenCV 和 Python 进行自动色彩校正 cv2.aruco 介绍 一、cv2.aruco模块概述 cv2.…