课程设计---哈夫曼树的编码与解码(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 方法只需要打印字符和其对应的频次即可
        @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;
        }
    }
}

四.运行结果展示:

五.总结与反思:

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

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

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

SkyWalking 极简入门

1. 概述 1.1 概念 SkyWalking 是什么&#xff1f; FROM Apache SkyWalking 分布式系统的应用程序性能监视工具&#xff0c;专为微服务、云原生架构和基于容器&#xff08;Docker、K8s、Mesos&#xff09;架构而设计。 提供分布式追踪、服务网格遥测分析、度量聚合和可视化一体…

milvus元数据解析工具milvusmetagui介绍使用

简介 milvusmetagui是一款用来对milvus的元数据进行解析的工具&#xff0c;milvus的元数据存储在etcd上&#xff0c;而且经过了序列化&#xff0c;通过etcd-manager这样的工具来查看是一堆二进制乱码&#xff0c;因此开发了这个工具对value进行反序列化解析。 在这里为了方便交…

建材租赁管理系统软件教程,操作简单佳易王租赁管理系统操作教程

建材租赁管理系统软件教程&#xff0c;操作简单佳易王租赁管理系统操作教程 一、软件操作教程 以下软件操作教程以&#xff0c;佳易王租赁管理系统为例说明 软件文件下载可以点击最下方官网卡片——软件下载——试用版软件下载 租赁登记&#xff1a; a、租赁登记可以记录日…

sklearn之各类朴素贝叶斯原理

sklearn之贝叶斯原理 前言1 高斯朴素贝叶斯1.1 对连续变量的处理1.2 高斯朴素贝叶斯算法原理 2 多项式朴素贝叶斯2.1 二项分布和多项分布2.2 详细原理2.3 如何判断是否符合多项式贝叶斯 3 伯努利朴素贝叶斯4 类别贝叶斯4 补充朴素贝叶斯4.1 核心原理4.2 算法流程 前言 如果想看…

Python | Leetcode Python题解之第160题相交链表

题目&#xff1a; 题解&#xff1a; class Solution:def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:A, B headA, headBwhile A ! B:A A.next if A else headBB B.next if B else headAreturn A

吴恩达机器学习 第三课 week1 无监督学习算法(上)

目录 01 学习目标 02 无监督学习 03 K-means聚类算法 3.1 K-means聚类算法原理 3.2 k-means算法实现 3.3 利用k-means算法压缩图片 04 总结 01 学习目标 &#xff08;1&#xff09;了解无监督学习算法 &#xff08;2&#xff09;掌握K-means聚类算法实现步骤 &#xff…

it职业生涯规划系统的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;管理员管理&#xff0c;职业介绍管理&#xff0c;答题管理&#xff0c;试题管理&#xff0c;基础数据管理 前台账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;在线答题&#xff0…

基于SSM+Jsp的书店仓库管理系统

摘要&#xff1a;仓库作为储存货物的核心功能之一&#xff0c;在整个仓储中具有非常重要的作用&#xff0c;是社会物质生产的必要条件。良好的仓库布局环境能够对货物进入下一个环节前的质量起保证作用&#xff0c;能够为货物进入市场作好准备&#xff0c;在设计中我们根据书店…

基于matlab的RRT算法路径规划(附带案例源码)

文章中的所有案例均为博主手动复现&#xff0c;用于记录博主学习路径规划的过程&#xff0c;如有不妥&#xff0c;欢迎在评论区交流 目录 1 标准RRT1.1 算法原理1.2 演示 2 GBRRT2.1 算法原理2.2 算法演示 3 RRT-STAR3.1 算法原理3.2 算法演示 4 RRT-CONNECT4.1 算法原理4.2 算…

实现农业现代化与乡村振兴战略的融合发展方案

政策背景 “一号文件”精神贯彻 数字乡村试点精神全面实施 工业化思维谋划农业发展 数字乡村建设纳入县级“十四五”发展规划 乡村振兴实施目标 2020年&#xff1a;乡村振兴取得重要进展 2035年&#xff1a;乡村振兴取得决定性进展&#xff0c;农业农村现代化基本实现 205…

在 Ubuntu 18.04.4 LTS上安装 netmap

文章目录 步骤运行配置文件编译安装使用netmap 步骤 sudo su sudo apt-get update sudo apt install build-essential sudo apt-get install -y git sudo apt-get install -y linux-headers-$(uname -r)rootVM-20-6-ubuntu:/home/ubuntu/netmap/LINUX# git clone https://gith…

反激开关电源EMI电路选型及计算

EMI &#xff1a;开关电源对电网或者其他电子产品的干扰 EMI &#xff1a;传导与辐射 共模电感的滤波电路&#xff0c;La和Lb就是共模电感线圈。这两个线圈绕在同一铁芯上&#xff0c;匝数和相位都相 同(绕制反向)。 这样&#xff0c;当电路中的正常电流&#xff08;差模&…

回归算法详解

回归算法详解 回归分析是一类重要的机器学习方法&#xff0c;主要用于预测连续变量。本文将详细讲解几种常见的回归算法&#xff0c;包括线性回归、岭回归、Lasso 回归、弹性网络回归、决策树回归和支持向量回归&#xff08;SVR&#xff09;&#xff0c;并展示它们的特点、应用…

Ubuntu-基础工具配置

基础工具配置 点击左下角 在弹出界面中点击 以下命令都是在上面这个界面执行&#xff08;请大家注意空格&#xff09; 命令输入完后&#xff0c;回车键就是执行,系统会提示输入密码&#xff08;就是你登录的密码&#xff09; 1.安装net工具 &#xff1a;&#xff08;ifconfi…

uniapp 微信小程序自定义分享图片

场景&#xff1a;微信小程序用户&#xff0c;点击小程序里商品的分享按钮时&#xff0c;想要不同的商品展示不用的分享内容&#xff0c;比如分享图片上展示商品的图片、价格等信息。分享的UI图如下&#xff1a; 实现方法&#xff1a; 1. 分享按钮&#xff1a;<button open-…

Mysten Labs宣布推出Walrus:一种去中心化存储和数据可用性协议

Walrus是为区块链应用和自主代理提供的创新去中心化存储网络。Walrus存储系统今天以开发者预览版的形式发布&#xff0c;面向Sui开发者征求反馈意见&#xff0c;并预计很快会向其他Web3社区广泛推广。 通过采用纠删编码创新技术&#xff0c;Walrus能够快速且稳健地将非结构化数…

Day10—Spark SQL基础

Spark SQL介绍 ​ Spark SQL是一个用于结构化数据处理的Spark组件。所谓结构化数据&#xff0c;是指具有Schema信息的数据&#xff0c;例如JSON、Parquet、Avro、CSV格式的数据。与基础的Spark RDD API不同&#xff0c;Spark SQL提供了对结构化数据的查询和计算接口。 Spark …

人工智能指数报告

2024人工智能指数报告&#xff08;一&#xff09;&#xff1a;研发 前言 全面分析人工智能的发展现状。 从2017年开始&#xff0c;斯坦福大学人工智能研究所&#xff08;HAI&#xff09;每年都会发布一份人工智能的研究报告&#xff0c;人工智能指数报告&#xff08;AII&…

网络安全:入侵检测系统的原理与应用

文章目录 网络安全&#xff1a;入侵检测系统的原理与应用引言入侵检测系统简介IDS的工作原理IDS的重要性结语 网络安全&#xff1a;入侵检测系统的原理与应用 引言 在我们的网络安全系列文章中&#xff0c;我们已经涵盖了从SQL注入到端点保护的多个主题。本篇文章将探讨入侵检…

Apple - Authorization Services Programming Guide

本文翻译整理自&#xff1a;Authorization Services Programming Guide&#xff08;更新日期&#xff1a;2011-10-19 https://developer.apple.com/library/archive/documentation/Security/Conceptual/authorization_concepts/01introduction/introduction.html#//apple_ref/d…