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

news2024/11/23 17:22:43

目录

一.设计任务&&要求:

二.方案设计报告:

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 方法只需要打印字符和其对应的频次即可
        @Override
        public 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 方法只需要打印字符和其对应的频次即可
        @Override
        public 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()){
            //找到编码 并计算字符串编码后所占的bits
            node.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.coloradmin.cn/o/1841088.html

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

相关文章

昇思25天学习打卡营第1天|基本介绍及快速入门

1.第一天学习总体复盘 1&#xff09;成功注册昇思大模型平台&#xff0c;并成功申请算力&#xff1b; 2)在jupyter环境下学习初学入门/初学教程的内容&#xff1b; 在基本介绍部分&#xff0c;快速撸了一边内容&#xff0c;有了一个基本的了解&#xff08;没理解到位的计划采用…

Part 6.2.3 欧拉函数

欧拉函数φ(x) 表示了小于x的数字中&#xff0c;与x互质的数字个数。 关于欧拉函数的基本知识>欧拉函数的求解< [SDOI2008] 仪仗队 题目描述 作为体育委员&#xff0c;C 君负责这次运动会仪仗队的训练。仪仗队是由学生组成的 N N N \times N NN 的方阵&#xff0c;…

在VScode中创建PHP环境

一、下载PHP Server 和 PHP Debug这两个扩展 二、下载完成之后&#xff0c;在VScode中&#xff0c;打开我们写代码的文件 这里是我事先创建好的一些文件&#xff0c;本次环境搭建只需要创建一个.php后缀的文件即可。 先选中.php文件&#xff0c;再点击文件。 点击首选项&#x…

配置CentOS 7通过MSTSC连接远程桌面

正文共&#xff1a;777 字 14 图&#xff0c;预估阅读时间&#xff1a;1 分钟 前面我们介绍了如何通过VNC连接Ubuntu的远程桌面&#xff08;Ubuntu 18.04开启远程桌面连接&#xff09;&#xff0c;也介绍了如何使用微软的MSTSC来连接Ubuntu的远程桌面&#xff08;如何通过MSTSC…

Flink 1.19.1 standalone 集群模式部署及配置

flink 1.19起 conf/flink-conf.yaml 更改为新的 conf/config.yaml standalone集群: dev001、dev002、dev003 config.yaml: jobmanager address 统一使用 dev001&#xff0c;bind-port 统一改成 0.0.0.0&#xff0c;taskmanager address 分别更改为dev所在host dev001 config.…

Vue63-配置代理-方式二

一、请求前缀&#xff1a;能灵活的控制走不走代理 1-1、请求前缀 有请求前缀的走代理服务器&#xff1b; 没有请求前缀的不走代理服务器。 修改代码中的请求地址&#xff0c;加上请求前缀 报错的原因&#xff1a; 解决方式&#xff1a; 1-2、ws配置项、changeOrigin配置项 二…

智能合约新项目 链上智能合约前端H5源码 智能合约区块链 以太坊前端调用智能合约

智能合约新项目 链上智能合约前端H5源码 智能合约区块链 以太坊前端调用智能合约 源码下载&#xff1a;https://download.csdn.net/download/m0_66047725/89402192 更多资源下载&#xff1a;关注我。

Mendix 创客访谈录|医疗设备领域的数字化转型利器

本期创客 尚衍亮 爱德亚&#xff08;北京&#xff09;医疗科技有限公司 应用开发和数字化事业部开发经理 大家好&#xff0c;我叫尚衍亮。毕业于软件工程专业&#xff0c;有6年的软件开发经验。从2021年开始&#xff0c;我在爱德亚&#xff08;北京&#xff09;医疗科技有限公司…

StarkNet System Architecture 系统架构

文章目录 Starknet架构排序器,证明器和节点、验证者、Starnet Core排序器 Sequencer证明器 Prover节点验证者StarkNet Core工作原理TransactionsStarknet架构 原文链接: https://david-barreto.com/starknets-architecture-review/#more-4602 StarkNet 有五个组成部分。分别…

新手装修 避坑课2.0:装修之前一定要做好功课(55节课)

课程下载&#xff1a;https://download.csdn.net/download/m0_66047725/89388333 更多资源下载&#xff1a;关注我。 课程目录 第01节1.装修前准备工作.mp4 第02节开篇.mp4 第03节2.装修需要提前定好的设备和材料.mp4 第04节3.自装还是找装修公司.mp4 第05节4.自装怎么找…

客观评价,可道云teamOS搭建的企业网盘,如Windows本地电脑一般的使用体验真的蛮不错

不管是企业网盘还是私有网盘&#xff0c;简单易用一直是我比较在意的。快速能上手使用&#xff0c;甚至不需要习惯一套新的操作逻辑&#xff0c;代表着不需要学习适应&#xff0c;能够迅速投入正常使用。 在这个过程中&#xff0c;可道云teamos以其Windows电脑般的流畅体验&am…

Ubuntu网络管理命令:nslookup

安装Ubuntu桌面系统&#xff08;虚拟机&#xff09;_虚拟机安装ubuntu桌面版-CSDN博客 nslookup命令主要用来查询域名信息&#xff0c;实际上主要是将域名转换为相应的IP地址&#xff0c;或者将IP地址转换成相应的域名。nslookup命令为用户提供了两种工作模式&#xff0c;分别…

无引擎游戏开发(2):最简游戏框架 | EasyX制作井字棋小游戏I

一、EasyX中的坐标系 不同于数理中的坐标系&#xff0c;EasyX中的y轴是竖直向下的 二、渲染缓冲区 之前的程序添加了这三个函数改善了绘图时闪烁的情况: 小球在"画布“上移动的过程就是我们在调用绘图函数&#xff0c;这个”画布“就是渲染缓冲区&#xff0c;先绘制的内…

2024人工智能指数报告(二):技术性能

背景 从2017年开始&#xff0c;斯坦福大学人工智能研究所&#xff08;HAI&#xff09;每年都会发布一份人工智能的研究报告&#xff0c;人工智能指数报告&#xff08;AII&#xff09;&#xff0c;对上一年人工智能相关的数据进行跟踪、整理、提炼并进行可视化。这份指数报告被认…

Java宝藏实验资源库(2)字节流

一、实验目的 掌握输入输出流的基本概念。掌握字节流处理类的基本结构。掌握使用字节流进行输入输出的基本方法。 二、实验内容、过程及结果 *17.10 (Split files) Suppose you want to back up a huge file (e.g., a 10-GB AVI file) to a CD-R. You can achieve it by split…

前端技术栈三(vue+Axios)

一、Vue 1 基本介绍 1.1 Vue 是什么? Vue (读音 /vjuː/&#xff0c;类似于 view) 是一个前端框架, 易于构建用户界面 Vue 的核心库只关注视图层&#xff0c;不仅易于上手&#xff0c;还便于与第三方库或项目整合 支持和其它类库结合使用 开发复杂的单页应用非常方便 Vue 是…

Python的三种方式显示图片

from PIL import Image import numpy as np im Image.open("img.png") #方法一&#xff1a;使用PIL库显示图片 a np.array(im) imImage.fromarray(a) im.show() import matplotlib.pyplot as plt #方法二&#xff1a;使用matplotlib库显示图片 plt.imshow(a) plt.s…

java 不可变集合的创建和Stream流的使用

文章目录 一、创建不可变的集合1.1为什么创建不可变的集合1.2 创建List、Set和Map的不可变集合1.2.1 创建List的不可变集合1.2.2 创建Set 的不可变集合1.2.3 创建Map的不可变集合 二、使用集合 的Stream 流2.1 Stream的使用步骤2.2 Stream的方法 三、如何获取Stream 流对象四、…

使用 GCD 实现属性的多读单写

使用 Grand Central Dispatch (GCD) 实现多读单写的属性 首先需要确保在多线程环境下的线程安全性。可以使用 GCD 提供的读写锁机制 dispatch_rwlock_t 或者 dispatch_queue_t 来实现这个功能。 Swift版本的实现 怎样创建一个并发队列 &#xff1f;// 使用 Swift 来实现的首…

swift使用swift-protobuf协议通讯,使用指北

什么是Protobuf Protobuf&#xff08;Protocol Buffers&#xff09;协议&#x1f609; Protobuf 是一种由 Google 开发的二进制序列化格式和相关的技术&#xff0c;它用于高效地序列化和反序列化结构化数据&#xff0c;通常用于网络通信、数据存储等场景。 为什么要使用Proto…