课程设计---哈夫曼树的编码与解码(Java详解)

目录

一.设计任务&&要求:

二.方案设计报告:

2.1 哈夫曼树编码&译码的设计原理:

2.3设计目的:

2.3设计的主要过程:

2.4程序方法清单:

三.整体实现源码:

四.运行结果展示:

五.总结与反思:


一.设计任务&&要求:

题目要求:测试数据是一段任意的英文,也可以是一段完整的中文,采用哈夫曼算法进行编码,可输出对应的字符编码的解码

哈夫曼编码是一种最优变长码,即带权路径最小。这种编码有很强的应用背景,是数据压缩中的一个重要理论依据。对输入的一串文字符号实现哈夫曼编码,再对哈夫曼编码生成的代码串进行译码,输出字符串。要求完成以下功能:

1.针对给定的字符串,建立哈夫曼树。

2.生成哈夫曼编码。

3.对编码字符串译码

二.方案设计报告:

2.1 哈夫曼树编码&译码的设计原理:

  • 哈夫曼编译码器的主要功能是先建立哈夫曼树,然后利用建好的哈夫曼树生成哈夫曼编码后进行译码。在数据通信中,通常需要将传送文字转换成由二进制字符0,1组成的二进制串,称之为编码。构建一个哈夫曼树,设定哈夫曼树中的左分支为0,右分支代表1,则从根结点到每个叶子节点所经过的路径组成的0和1的序列便为该节点对应字符的编码,称之为哈夫曼编码。最简单的二进制编码方式是等长编码。若采用不等长编码,让出现频率高的字符具有较短的编码,让出现频率低的字符具有较长的编码,这样可以有效缩短传送文字的总长度。哈夫曼树则是用于构造使编码总长最短,最节省空间成本的编码方案。
  • 2.3设计目的:

  • (1) 巩固和加深对数据结构课程所学知识的理解,了解并掌握数据结构与算法的设计方法;
    (2) 初步掌握软件开发过程的问题分析、系统设计、程序编码、测试等基本方法和技能;
    (3) 提高综合运用所学的理论知识和方法,独立分析和解决问题的能力;
    (4) 训练用系统的观点和软件开发一般规范进行软件开发,培养软件工作者所应具备的科学的工作方法和作风;
    (5) 培养查阅资料,独立思考问题的能力。

2.3设计的主要过程:

     1.哈夫曼树叶子节点的创建

叶子节点需要存储字符,及其出现的频率,指向左右子树的指针和将来字符所编码成的二进制数字。这里用一个静态内部来来初始化树的叶子节点:

  //用一个静态内部类来初始化树的节点static class Node{char ch;     //记录字符int freq;    //统计每个字符出现的频次Node left;Node right;String code;  //编码public Node(char ch) {this.ch = ch;}public Node(int freq, Node left, Node right) {this.freq = freq;this.left = left;this.right = right;}//判断是否是叶子节点->哈夫曼树是满二叉树boolean isLeaf(){return left == null;}public char getCh() {return ch;}public void setCh(char ch) {this.ch = ch;}public int getFreq() {return freq;}public void setFreq(int freq) {this.freq = freq;}//重写的toString 方法只需要打印字符和其对应的频次即可@Overridepublic String toString() {return "Node{" +"ch=" + ch +", freq=" + freq +'}';}}

      2.构建哈夫曼树

 构建过程:首先要统计每个字符出现的频率①.将统计了出现频率的字符,放入优先级队列中,利用优先级队列的特点,将字符按照出现的频率从小到大排序②.每次出队两个频次最低的元素,给它们找一个父亲节点③.将父亲节点放入队列中,重复②~③两个步骤④.当队列中只剩一个元素时,哈夫曼树就构建完了 

 //构造哈夫曼树//->由于这里是一个自定义的类,我们需要传入比较器,按照节点的频次进行比较PriorityQueue<Node> q = new PriorityQueue<>(//通过Comparator 的方法来获得Node节点的其中一个属性Comparator.comparingInt(Node::getFreq));for(Node node : hash.values()){q.offer(node);}while(q.size() >= 2){Node x = q.poll();Node y = q.poll();int freq = x.freq + y.freq;q.offer(new Node(freq,x,y));}

       3.哈夫曼编码

通过将每个叶子节点保存好的字符编码利用StringBuilder中的append()方法拼接起来后返回即可

 //编码操作:public String encode(){char[] chars = str.toCharArray();StringBuilder sb = new StringBuilder();for(char c : chars){sb.append(hash.get(c).code);}return sb.toString();}

     4.哈夫曼译码

 从根节点开始,寻找数字对应的字符编码,如果是0向右走,如果是数字1向左走,如果没有走到头(一个字符的编码结尾),每一步数字的索引cur++,每找到一个编码字符,在将node重置为根节点,接着重个节点开始继续往下寻找,一直找到字符串末尾即可

 /***  从根节点开始,寻找数字对应的字符*  数字是0 向右走,数字是1 向左走*  如果没有走到头,每一步数字的索引 cur++*  走到头就可以 找到编码字符,再将node 重置为根节点* @param str* @return*///解码操作:public String decode(String str){char[] chars = str.toCharArray();StringBuilder sb = new StringBuilder();int cur = 0;Node node = root;while(cur < chars.length){if(!node.isLeaf()){//非叶子节点if(chars[cur] == '0'){//向左走node = node.left;}else if(chars[cur] == '1'){//向右走node =node.right;}//每走完一步 cur++;cur++;if(node.isLeaf()){sb.append(node.ch);//每找到一个叶子节点,就重置后再次查找,直到遍历完整个数组node = root;}}}return sb.toString();}
  • 大致模块图:

设计流程图:

2.4程序方法清单:

①.构造哈夫曼树:public HuffmanTree(){

}//这里我选择在函数的构造方法中将哈夫曼树给先构造完

②.编码:public String encode(){};

③.解码:pulbic String decode(){};

④.找到编码,并计算其对应的bit位:private int findCode(Node node,StringBuilder code){};

⑤.打印菜单:menu(){};

⑥.测试函数:main(){};

模块展示:

三.整体实现源码:

import java.util.*;public class HuffmanTree {//用一个静态内部类来初始化树的节点static class Node{char ch;     //记录字符int freq;    //统计每个字符出现的频次Node left;Node right;String code;  //编码public Node(char ch) {this.ch = ch;}public Node(int freq, Node left, Node right) {this.freq = freq;this.left = left;this.right = right;}//判断是否是叶子节点->哈夫曼树是满二叉树boolean isLeaf(){return left == null;}public char getCh() {return ch;}public void setCh(char ch) {this.ch = ch;}public int getFreq() {return freq;}public void setFreq(int freq) {this.freq = freq;}//重写的toString 方法只需要打印字符和其对应的频次即可@Overridepublic String toString() {return "Node{" +"ch=" + ch +", freq=" + freq +'}';}}String str;Node root;Map<Character,Node> hash = new HashMap<>();public HuffmanTree(){}public HuffmanTree(String str){this.str = str;//统计字符出现的频次char[] chars = str.toCharArray();for(char ch : chars){if(!hash.containsKey(ch)){hash.put(ch,new Node(ch));}Node node = hash.get(ch);node.freq++;}for(Node node : hash.values()){System.out.println(node);}//构造哈夫曼树//->由于这里是一个自定义的类,我们需要传入比较器,按照节点的频次进行比较PriorityQueue<Node> q = new PriorityQueue<>(//通过Comparator 的方法来获得Node节点的其中一个属性Comparator.comparingInt(Node::getFreq));for(Node node : hash.values()){q.offer(node);}while(q.size() >= 2){Node x = q.poll();Node y = q.poll();int freq = x.freq + y.freq;q.offer(new Node(freq,x,y));}root = q.poll();//System.out.println(root);//计算每个字符的编码 以及其一共包含的bit位System.out.println("输出编码信息:");int sum = findCode(root,new StringBuilder());for(Node node : hash.values()){//打印节点及其编码信息System.out.println(node + " " + node.code);}System.out.println("总共占有的bit位是:" + sum);}//找到编码,并计算其对应的bit位private int findCode(Node node,StringBuilder code){int sum = 0;if(node.isLeaf()){//找到编码 并计算字符串编码后所占的bitsnode.code = code.toString();sum = node.freq * code.length();}else{sum += findCode(node.left,code.append("0"));code.deleteCharAt(code.length() - 1);sum += findCode(node.right,code.append("1"));code.deleteCharAt(code.length() - 1);}return sum;}//编码操作:public String encode(){char[] chars = str.toCharArray();StringBuilder sb = new StringBuilder();for(char c : chars){sb.append(hash.get(c).code);}return sb.toString();}/***  从根节点开始,寻找数字对应的字符*  数字是0 向右走,数字是1 向左走*  如果没有走到头,每一步数字的索引 cur++*  走到头就可以 找到编码字符,再将node 重置为根节点* @param str* @return*///解码操作:public String decode(String str){char[] chars = str.toCharArray();StringBuilder sb = new StringBuilder();int cur = 0;Node node = root;while(cur < chars.length){if(!node.isLeaf()){//非叶子节点if(chars[cur] == '0'){//向左走node = node.left;}else if(chars[cur] == '1'){//向右走node =node.right;}//每走完一步 cur++;cur++;if(node.isLeaf()){sb.append(node.ch);//每找到一个叶子节点,就重置后再次查找,直到遍历完整个数组node = root;}}}return sb.toString();}static final int all_block_nums = 100;public static void memu() throws InterruptedException {//菜单加载页面for(int i = 0;i < all_block_nums;i++){System.out.printf("\r[%d%%]>",i*100/(all_block_nums-1));for(int j = 1;j <= i*20/(all_block_nums);j++){System.out.print("▉");Thread.sleep(2);}}System.out.println();System.out.println("-------------------------------------------");System.out.println("----------                     ------------");System.out.println("--------    欢迎使用哈夫曼编码      ----------");System.out.println("---------     1.编码与解码          ---------");System.out.println("----------    0.退出              ----------");System.out.println("-------------------------------------------");}public static void main(String[] args) throws InterruptedException {Scanner sc = new Scanner(System.in);while(true){memu();String str = "0000";System.out.println("请选择:");int input = sc.nextInt();switch (input){case 0:System.out.println("你选择了退出程序~~~");break;case 1 :System.out.println("你选择了编码与解码");System.out.println("请输入要编码的字符串:");String in = sc.next();HuffmanTree huffmanTree = new HuffmanTree(in);str = huffmanTree.encode();System.out.println("编码后的字符串为:");System.out.println(str);System.out.println("将刚才编码好的字符串进行解码:");String cur = huffmanTree.decode(str);System.out.println("解码后的字符串:");System.out.println(cur);}if(input == 0) break;}}
}

四.运行结果展示:

五.总结与反思:

这次课程设计的心得体会通过实践我的收获如下:
① 巩固和加深了对数据结构的理解,提高综合运用本课程所学知识的能力。
② 培养了我选用参考书,查阅手册及文献资料的能力。培养独立思考,深入研究,分析问题、解决问题的能力。
③ 通过实际编译系统的分析设计、编程调试,掌握应用软件的分析方法和工程设计方法。
④ 通过课程设计,培养了我严肃认真的工作作风,逐步建立正确的生产观念、经济观念和全局观念。

通过本次数据结构的课设计,我学习了很多在上课没懂的知识,并对求哈夫曼树及哈夫曼编码/译码的算法有了更加深刻的了解,更巩固了课堂中学习有关于哈夫曼编码的知识,真正学会一种算法了。当求解一个算法时,不是拿到问题就不加思索地做,而是首先要先对它有个大概的了解,接着再详细地分析每一步怎么做,无论自己以前是否有处理过相似的问题,只要按照以上的步骤,必会顺利地做出来。

结语: 写博客不仅仅是为了分享学习经历,同时这也有利于我巩固知识点,总结该知识点,由于作者水平有限,对文章有任何问题的还请指出,接受大家的批评,让我改进。同时也希望读者们不吝啬你们的点赞+收藏+关注,你们的鼓励是我创作的最大动力!

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

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

相关文章

javaSE:继承

在谈继承之前&#xff0c;我们先观察下面这个代码&#xff1a; //定义一个猫类 class Cat {public String name;public int age;public float weigth;public void eat(){System.out.println(this.name"正在吃饭");}public void mimi(){System.out.println(this.nam…

YoloV9改进策略:注意力篇|BackBone改进|自研像素和通道并行注意力模块(独家原创)

摘要 本文使用FFA-Net的注意力改进YoloV9,FFA-Net提出了通道注意力和像素注意力相结合的方式,提高Block的表征能力,我把这两种注意力结合起来改进YoloV8的BackBone,取得了非常好的效果,即插即用,简单易懂,非常适合大家入手。 论文翻译:《FFA-Net:用于单图像去雾的特征…

nccl 03 记 回顾:从下载,编译到调试 nccl-test

1&#xff0c; 下载与编译 1.1 源码下载 $ git clone https://github.com/NVIDIA/nccl.git 1.2 编译 1.2.1 一般编译&#xff1a; $ make -j src.build 1.2.2 特定架构gpu 编译 $ make -j src.build NVCC_GENCODE"-gencodearchcompute_80,codesm_80" A10…

探究布局模型:从LayoutLM到LayoutLMv2与LayoutXLM

LAYOUT LM 联合建模文档的layout信息和text信息&#xff0c; 预训练 文档理解模型。 模型架构 使用BERT作为backbone&#xff0c; 加入2-D绝对位置信息&#xff0c;图像信息 &#xff0c;分别捕获token在文档中的相对位置以及字体、文字方向、颜色等视觉信息。 2D位置嵌入 …

装备制造行业数据分析指标体系

数字化飞速发展的时代&#xff0c;多品种、定制化的产品需求、越来越短的产品生命周期、完善的售后服务、极佳的客户体验和快速的交货速度等&#xff0c;使得装备制造行业的经营环境越来越复杂&#xff0c;企业竞争从拼产品、拼价格迈向拼服务&#xff0c;装备制造企业正处于数…

阿里云 debian10.3 sudo apt-get updat 报错的解决方案

阿里云全新的debian10.3(buster)镜像&#xff0c;却无法正常执行 sudo apt-get update。主要报错信息如下&#xff1a; Err:6 http://mirrors.cloud.aliyuncs.com/debian buster-backports Release404 Not Found [IP: 100.100.2.148 80] Err:3 http://mirrors.cloud.aliyuncs…

无引擎游戏开发(1):EasyX图形库引入 + 跟随鼠标移动的小球

来自bilibili up主的Voidmatrix的视频教程&#xff1a;【从零开始的C游戏开发】 一、图形库引入 EasyX在国内文档最多&#xff0c;而且功能函数齐全&#xff0c;最适合入门。 环境配置&#xff1a;vs2022 &#xff08;官网下载免费版&#xff09; 百度搜EasyX官方&#xff0…

后方穿行预警系统技术规范(简化版)

后方穿行预警系统技术规范(简化版) 1 系统概述2 预警区域3 预警目标4 预警条件5 指标需求1 系统概述 RCTA后方穿行预警系统工作在驾驶员有倒车意向的时候。在倒车过程中当驾驶员视线因周围障碍物被遮挡而产生碰撞风险时,系统通过光学信号对驾驶员进行提醒。 2 预警区域 RCT…

前端入门篇(五十二)练习6:transition过渡小动画

所以应该先找到第n个li&#xff0c;找到li再找img&#xff0c;li没有找错&#xff0c;底下又各自只有一个img&#xff0c;解决 ul li:nth-child(1) img { } 描述文字从下往上&#xff1a; 一开始描述也在框框下面&#xff0c;当hover时&#xff0c;translateY(0)&#xff0…

【JS重点18】原型链(面试重点)

一&#xff1a;原型链底层原理 以下面一段代码为例&#xff0c;基于原型对象&#xff08;Star构造函数的原型对象&#xff09;的继承使得不同构造函数的原型对象关联在一起&#xff08;此处是最大的构造函数Object原型对象&#xff09;&#xff0c;并且这种关联的关系是一种链…

CleanShot X for Mac v4.7 屏幕滚动长截图录像工具(保姆级教程,小白轻松上手,简单易学)

Mac分享吧 文章目录 一、下载软件二、部分特有功能效果1、截图软件的普遍常用功能&#xff08;画框、箭头、加文字等&#xff09;都具备&#xff0c;不再详细介绍2、ABCD、1234等信息标注&#xff08;每按一下鼠标&#xff0c;即各是A、B、C、D...等&#xff09;3、截图更换背…

SQL注入-下篇

HTTP注入 一、Referer注入 概述 当你访问一个网站的时候&#xff0c;你的浏览器需要告诉服务器你是从哪个地方访问服务器的。如直接在浏览器器的URL栏输入网址访问网站是没有referer的&#xff0c;需要在一个打开的网站中&#xff0c;点击链接跳转到另一个页面。 Less-19 判…

第29讲:Ceph集群使用RBD块存储设备与K8S的PV集成

文章目录 1.Ceph集群使用RBD块存储与K8S集成简介2.Ceph集群RBD块存储与K8S PV存储卷集成2.1.创建K8S集群PV使用的块存储2.2.创建K8S集群访问RBD块存储设备的认证用户2.3.将认证用户的Key存储在K8S Secret资源中2.4.在K8S集群的所有节点中安装Ceph命令2.5.创建PV及PVC资源使用RB…

C#开发-集合使用和技巧(八)集合中的排序Sort、OrderBy、OrderByDescending

C#开发-集合使用和技巧&#xff08;八&#xff09;集合中的排序Sort、OrderBy、OrderByDescending List<T>.Sort()IEnumerable<T>.OrderBy()Enumerable<T>.OrderByDescending() 在C#中&#xff0c;List<T> 类提供了多种方法来进行排序&#xff0c;最常…

jax.nn.initializers.glorot_normal()

import jax import jax.numpy as jnp from jax import random import jax.nn.initializers as init# 设置随机数种子 key random.PRNGKey(42)# 定义权重的形状 shape (in_dim, out_dim)# 获取 Glorot 正态初始化函数 glorot_normal_init init.glorot_normal()# 初始化权重 w…

QT基础 - QMainWindow主窗口

目录 零. 简介 一. 菜单栏 二. 工具栏 三. 状态栏 四. 可停靠区域 五. 总结 零. 简介 QMainWindow 是 Qt 中用于构建主窗口的类。 它通常包含以下几个主要部分&#xff1a; 菜单栏&#xff1a;用于提供各种操作选项。工具栏&#xff1a;放置常用的操作按钮。中心区域&…

搭建Vue的环境

目录 # 开篇 步骤一&#xff0c;准备Vue 的环境 步骤二&#xff0c;下载Vue.js的包 步骤三&#xff0c;创建并打开写前端代码的文件夹 步骤四&#xff0c;在VSCode中引入Vue.js的包 步骤五&#xff0c;创建第一个vue.html Vue其他知识 Vue.config命令 # 开篇 介绍&…

详细分析Element Plus的el-pagination基本知识(附Demo)

目录 前言1. 基本知识2. Demo3. 实战 前言 需求&#xff1a;从无到有做一个分页并且附带分页的导入导出增删改查等功能 前提一定是要先有分页&#xff0c;作为全栈玩家&#xff0c;先在前端部署一个分页的列表 相关后续的功能&#xff0c;是Java&#xff0c;推荐阅读&#x…

数据结构:4.1.2二叉搜索树的插入

整个框架和FInd函数的实现是一样的&#xff0c;但是也有不同&#xff08;注意&#xff09; 35>30 向30的右子树 35<41 向41的左子树 35>33 向33的右子树&#xff0c;但33右边为空&#xff0c;所以35就挂在33的右边 因为要把35挂在33的右边&#xff0c;所以要把33的…

Solkane 冷媒性能计算软件-管路计算

下载 制冷管道设计 制冷管路的压降会降低制冷量&#xff0c;增大功耗。但不同部分的管路允许的压降的数量级是不同的。 制冷管路的压降不是唯一的考虑因素&#xff0c;制冷剂的流速往往比压降更重要。 制冷系统中&#xff0c;压缩机、阀、汽液分离器或其他附件上的连接件的尺…