使用HashMap实现,对一个字符集进行哈夫曼编码

news2024/11/18 15:23:06

最终达到的效果:

调用一个类

class HuffmanCodin{.....}

使用类中的静态方法,获取哈夫曼编码:


事前准备——哈夫曼树的节点定义

class Node implements Comparable<Node> {

    int weight;//权重
    Node left;
    Node right;
    char ch;//关键字,字符
    String code;//相应的哈夫曼编码

    public Node(char ch, int weight) {//构造方法,键值对
        this.weight = weight;
        this.ch = ch;
    }

    //构造方法,只设置出现频率
    public Node(int weight) {
        this.weight = weight;
    }



    //重写compareTo方法
    @Override
    public int compareTo(Node node) {

        if(this.weight - node.weight==0){//如果两个字符出现的频率一样,那么就比较字典序(两个字符一定是不同的)
            return this.ch-node.ch;
        }
        return this.weight - node.weight;//默认排列升序
    }



//重写toString方法
//效果:[ 字符 -> 010 ]
    @Override
    public String toString() {//重写之后,等一下打印就可以直接用引用就可以了
        return "[" + this.ch + " -> " + this.code + "]  ";
    }

}

步骤:

想要达到上述效果,大致可以分为这几步:

1、字符集转化成一个一个的键值对;

2、键值对转节点,节点放入一个集合

3、依据集合创建哈夫曼树。

4、对哈夫曼树的叶子节点进行哈夫曼编码

下面我们一点一点来解决


1、字符集转键值对

这里就要使用到HashMap了:

HashMap的

Key=一个字符

Value=权重(就是一个字符在字符集出现的频率,当然也不完全是,等一下会讲到)

public class Test {

    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);

        System.out.println("输入的字符集:");
        String arr = in.nextLine();
        char[] chars = arr.toCharArray();//转化成字符数组

        Node root=HuffmanCoding.createTree(chars);//调用这个静态方法,具体实现看下一块代码      
    }

}
class HuffmanCoding {

    public static Node createTree(char[] arr) {

        Map.Entry<Character, Integer>[] entries = charGetEntry(arr);//获取键值对


}

charGetEntry()方法就是专门用来把字符集转化成一个一个的键值对的,然后返回这个类型:

Map.Entry<Character, Integer>[]

为什么是这个类型?

因为HashMap不能直接同时访问Character也就是字符,以及Integer接也就是对应字符的权重。

如果要访问键值对,需要调用HashMap的setEntry方法,setEntry方法会返回Map.Entry<Character, Integer>[]类型的数组

而Map.Entry中有访问和修改关键字和值的方法。

charGetEntry()方法:

private static Map.Entry<Character, Integer>[] charGetEntry(char[] arr) {

        //定义Hashmap储存不重复的键值对
        Map<Character, Integer> map = new HashMap<>(arr.length);//长度肯定不会超过arr的长度
        for (char ch : arr) {
            map.put(ch, 0);//权值默认先给0,等一下处理
        }

        //定义Entry[]这个集合,用来存放键值对
        Map.Entry<Character, Integer>[] entrys = new Map.Entry[map.size()];//长度刚好就是map的长度

        int i = 0;
        for (Map.Entry<Character, Integer> entry : map.entrySet()) {//遍历每一个entry,以此把每一个键值对放到集合entrys中

            entrys[i++] = entry;
        }

        //现在就可以赋值weight了
        i=entrys.length-1;//从后往前遍历,当然从前往后也可以
        while(i>=0){
            int n=0;
            for (int j = 0; j <arr.length ; j++) {
                if(entrys[i].getKey()==arr[j]){//两个字符一样,那么频率++
                    n++;
                }
            }
            entrys[i--].setValue(n);
        }

        //程序到达这里,键值对已经储存完毕,下面直接返回集合即可
        return entrys;
    }

如下第一步完成:


2、键值对转节点

上一步我们已经获得了所有的键值对,储存在entries中,现在只需创建一个List<Node>类型的集合,获遍历entries,获取键值对即可:

        //放入节点中(用集合来管理)
        int length = entries.length;
        List<Node> nodeList = new ArrayList<>(length);//长度一定和entries是一样的
        while (length > 0) {

//用Node的构造方法,创建结点,第一个参数是关键子,第二个参数是权重
            nodeList.add(new Node(entries[length - 1].getKey(), entries[length - 1].getValue()));
            length--;
        }

此时,第二步完成,nodeList就是一个储存着所有结点的集合。

3、构造哈夫曼树

此时类

HuffmanCoding

的静态方法createTree已经定义好了:

    public static Node createTree(char[] arr) {

        Map.Entry<Character, Integer>[] entries = charGetEntry(arr);//获取键值对,已经完成,保存在了entries中

        //放入节点中(用集合来管理)
        int length = entries.length;
        List<Node> nodeList = new ArrayList<>(length);//长度一定和entries是一样的
        while (length > 0) {
            nodeList.add(new Node(entries[length - 1].getKey(), entries[length - 1].getValue()));
            length--;
        }

        while (nodeList.size() > 1) {//只要大于一,就合并
            Collections.sort(nodeList);//先排升序,重写了用weight比较

            Node a = nodeList.remove(0);
            Node b = nodeList.remove(0);
            Node newNode = new Node(a.weight + b.weight);//这个是双亲节点
            newNode.left = a;
            newNode.right = b;
            nodeList.add(newNode);
        }

        return nodeList.remove(0);//还剩下的一个节点,就是哈夫曼树的根节点
    }

4、叶结点编码

下面运用的是递归,对叶子结点进行赋值0或者1(左结点是0,右结点时1):

 //这个函数可以把Node的code修改
    public static void coding(Node root, StringBuilder sb) {

        if (root == null) return;//如果只有一个节点,code==“”---->空字符串

        root.code = sb.toString();//先根节点
        if (root.left == null && root.right == null) {
            return;//直接返回
        }

        //如果不是叶子节点,那么一定有左右孩子----》因为这是哈夫曼树

        sb.append("0");//先左边,所以加一个0
        coding(root.left, sb);//递归

        sb.replace(sb.length() - 1, sb.length(), "1");//把最后一个替换成1,因为要走右边了

        coding(root.right, sb);//递归
        sb.delete(sb.length() - 1, sb.length());//也要删除,删除的区间是:左开右闭的!

    }

现在这个HuffmanCoding这个类就可以对一个字符集进行哈夫曼编码了,当然如果想要打印对应的值,需要写一个打印叶子结点的方法:

  //前序遍历打印叶子结点
    public static void showChar(Node root) {
        if (root == null) return;
        if (root.left == null && root.right == null) {//这是一个叶子节点,直接打印然后返回
            System.out.println(root);
            return;
        }
        //不是叶子结点,就遍历左右子树
        showChar(root.left);
        showChar(root.right);
    }

测试

最终哈夫曼编码类的源码:

class HuffmanCoding {

    public static Node createTree(char[] arr) {

        Map.Entry<Character, Integer>[] entries = charGetEntry(arr);//获取键值对,已经完成,保存在了entries中

        //放入节点中(用集合来管理)
        int length = entries.length;
        List<Node> nodeList = new ArrayList<>(length);//长度一定和entries是一样的
        while (length > 0) {
            nodeList.add(new Node(entries[length - 1].getKey(), entries[length - 1].getValue()));
            length--;
        }

        while (nodeList.size() > 1) {//只要大于一,就合并
            Collections.sort(nodeList);//先排升序,重写了用weight比较

            Node a = nodeList.remove(0);
            Node b = nodeList.remove(0);
            Node newNode = new Node(a.weight + b.weight);//这个是双亲节点
            newNode.left = a;
            newNode.right = b;
            nodeList.add(newNode);
        }

        return nodeList.remove(0);//还剩下的一个节点,就是哈夫曼树的根节点
    }



    private static Map.Entry<Character, Integer>[] charGetEntry(char[] arr) {
        //定义Hashmap储存不重复的键值对
        Map<Character, Integer> map = new HashMap<>(arr.length);//长度肯定不会超过arr的长度
        for (char ch : arr) {
            map.put(ch, 0);//权值默认先给0,等一下处理
        }

        //定义Entry[],又来放键值对(可以访问的)
        Map.Entry<Character, Integer>[] entrys = new Map.Entry[map.size()];//长度刚好就是map的长度
        int i = 0;
        for (Map.Entry<Character, Integer> entry : map.entrySet()) {
            entrys[i++] = entry;
        }

        //先在赋值weight
        i=entrys.length-1;
        while(i>=0){
            int n=0;
            for (int j = 0; j <arr.length ; j++) {
                if(entrys[i].getKey()==arr[j]){//两个字符一样,那么频率佳佳
                    n++;
                }
            }
            entrys[i--].setValue(n);
        }

        //程序到达这里,键值对已经储存完毕
        return entrys;
    }

    //这个函数可以把Node的code修改
    public static void coding(Node root, StringBuilder sb) {

        if (root == null) return;//如果只有一个节点,code==“”---->空字符串

        root.code = sb.toString();//先根节点
        if (root.left == null && root.right == null) {
            return;//直接返回
        }

        //如果不是叶子节点,那么一定有左右孩子----》因为这是哈夫曼树

        sb.append("0");//先左边,所以加一个0
        coding(root.left, sb);//递归

        sb.replace(sb.length() - 1, sb.length(), "1");//把最后一个替换成1,因为要走右边了

        coding(root.right, sb);//递归
        sb.delete(sb.length() - 1, sb.length());//也要删除,删除的区间是:左开右闭的!

    }

    

    //前序遍历打印叶子结点
    public static void showChar(Node root) {
        if (root == null) return;
        if (root.left == null && root.right == null) {//这是一个叶子节点,直接打印然后返回
            System.out.println(root);
            return;
        }
        //不是叶子结点,就遍历左右子树
        showChar(root.left);
        showChar(root.right);
    }

}

类中静态方法使用的演示:

关于哈夫曼编码的解码

会了编码,其实解码就很容易了

值得注意的是:

不同的字符集合,对应的哈夫曼编码是有所差异的

所以如果要进行解码,那么必须直到每一个字符对应出现的频率

解码思路:

在接收到字符频度表之后,创建一颗哈夫曼树,每次从root结点开始遍历,从第一个字符开始,是0就往左树走,是1就往右走,直到叶子结点即可解码。

具体实现:

    public static void deCoding(Map.Entry<Character, Integer>[] arrEntry, String arr) {//需要接收字符频度表 and 哈夫曼编码
        Node Cur= createTree(arrEntry);//调用了重载的创建哈夫曼树的方法

        char[] chars = arr.toCharArray();

        int cur = 0;//表示读取编码的位置

        Node root=Cur;//Cur保存了哈夫曼树的根结点
        while (cur < chars.length) {//读完就退出循环
            while (root.left != null && root.right != null) {//没有到叶子结点就一直循环
                if (chars[cur] == '1') {//是1走右边
                    cur++;
                    root = root.right;
                } else {//是二走左边
                    cur++;
                    root = root.left;
                }
            }

            //如果跳出了循环说明已经是叶子结点了
            System.out.println(root);
            root=Cur;
        }

    }

因为我们在createTree()方法中传入的类型是Map.Entry<Character, Integer>[],

所以需要对createTree()方法,

进行一次重载,

这个重载其实不麻烦,只要改一下接口,就可以了:

    private static Node createTree(Map.Entry<Character, Integer>[] entries){//重载方法,用在解码时调用这个方法

        //放入节点中(用集合来管理)
        int length = entries.length;//只改了这一行,和方法的唯一参数!!!!!
        
        
        
        List<Node> nodeList = new ArrayList<>(length);//长度一定和entries是一样的
        while (length > 0) {
            nodeList.add(new Node(entries[length - 1].getKey(), entries[length - 1].getValue()));
            length--;
        }

        while (nodeList.size() > 1) {//只要大于一,就合并
            Collections.sort(nodeList);//先排升序,重写了用weight比较

            Node a = nodeList.remove(0);
            Node b = nodeList.remove(0);
            Node newNode = new Node(a.weight + b.weight);//这个是双亲节点
            newNode.left = a;
            newNode.right = b;
            nodeList.add(newNode);
        }

        return nodeList.remove(0);//还剩下的一个节点,就是哈夫曼树的根节点
    }

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

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

相关文章

Zabbix5.0——安装与部署

目录 一、zabbix-server(192.168.206.134) 监控方 1. 环境准备 2.安装zabbix 2.1 准备zabbix-repo 2.2清理缓存 2.3安装zabbix主包&#xff08;服务器和代理&#xff09; 2.4安装zabbix前端包 3. 数据库安装 3.1 授权zabbix账号 3.2导入数据库&#xff08;初始化zabbix&#x…

切实有效的提高VMWARE游戏性能-各版本通杀 vm17pro

这里的游戏性能&#xff0c;当然了&#xff0c;特别指出的是3D性能&#xff0c;毕竟现在2D也很少了。 因为平时没啥事&#xff0c;所以&#xff0c;无聊就跟朋友挂挂游戏&#xff0c;没事写点代码折腾下。所以&#xff0c;免不了跟VMWARE搭上边。走了很多的弯路&#xff0c;中…

Linux-信号执行

1. 信号什么时候被处理 当进程从内核态返回到用户态的时候&#xff0c;进行信号的检测和处理 什么内核态&#xff0c;什么又是用户态呢&#xff1f; 当进程在CPU上运行时&#xff0c;内核态&#xff1a;允许进程访问操作系统的代码和数据&#xff0c;用户态&#xff1a;进程只…

Kubernetes容器技术详解

kubernetes Kubernetes&#xff08;K8s&#xff09;由Google打造&#xff0c;是一款功能强大、灵活可扩展的容器编排平台&#xff0c;引领云原生技术潮流。 Kubernetes主要解决以下4大点&#xff1a; 1.自动化运维平台 如下图所示&#xff1a; Kubernetes携手Docker&#xf…

【go项目01_学习记录08】

学习记录 1 模板文件1.1 articlesStoreHandler() 使用模板文件1.2 统一模板 1 模板文件 重构 articlesCreateHandler() 和 articlesStoreHandler() 函数&#xff0c;将 HTML 抽离并放置于独立的模板文件中。 1.1 articlesStoreHandler() 使用模板文件 . . . func articlesSt…

【动态规划】:路径问题_地下城游戏

朋友们、伙计们&#xff0c;我们又见面了&#xff0c;本专栏是关于各种算法的解析&#xff0c;如果看完之后对你有一定的启发&#xff0c;那么请留下你的三连&#xff0c;祝大家心想事成&#xff01; C 语 言 专 栏&#xff1a;C语言&#xff1a;从入门到精通 数据结构专栏&…

怎么用AI软件设计字体

一 工具准备 软件&#xff1a;Adobe illustrator 如下网盘自取 链接&#xff1a;https://pan.baidu.com/s/1hlImpN4QlsSkOLLUxINOGA 提取码&#xff1a;love 安装的时候看不全界面&#xff0c;多按几下tab键就能看到按钮。 直接找一款喜欢的字体修改&#xff0c;字体包如下…

PyCharm安装教程(超详细图文教程)

一、下载和安装 1.进入PyCharm官方下载&#xff0c;官网下载地址&#xff1a; https://www.jetbrains.com/pycharm/download/ 专业版安装插件放网盘了&#xff0c;网盘下载即可&#xff1a;itcxy.xyz/229.html2.安装 1.下载后找到PyCharm安装包&#xff0c;然后双击双击.ex…

【连连国际注册_登录安全分析报告】

连连国际注册/登录安全分析报告 前言 由于网站注册入口容易被黑客攻击&#xff0c;存在如下安全问题&#xff1a; 暴力破解密码&#xff0c;造成用户信息泄露短信盗刷的安全问题&#xff0c;影响业务及导致用户投诉带来经济损失&#xff0c;尤其是后付费客户&#xff0c;风险巨…

【ytb数据采集器】按关键词批量爬取视频数据,界面软件更适合文科生!

一、背景介绍 1.1 爬取目标 用Python独立开发的爬虫工具&#xff0c;作用是&#xff1a;通过搜索关键词采集油管的搜索结果&#xff0c;包含14个关键字段&#xff1a;关键词,页码,视频标题,视频id,视频链接,发布时间,视频时长,频道名称,频道id,频道链接,播放数,点赞数,评论数…

Eigen求解线性方程组

1、线性方程组的应用 线性方程组可以用来解决各种涉及线性关系的问题。以下是一些通常可以用线性方程组来解决的问题&#xff1a; 在实际工程和科学计算中&#xff0c;求解多项式方程的根有着广泛的应用。 在控制系统的设计中&#xff0c;我们经常需要求解特征方程的根来分析…

链式二叉树的基本操作1

1.概念回顾 讲二叉树的基本操作之前&#xff0c;我们回顾一下二叉树的概念 在讲树之前&#xff0c;我们的每讲一种数据结构&#xff0c;无外乎就是在讲它们的增删查改&#xff0c;但是在树这里&#xff0c;就有了不小变化。 2.结点的定义 既然是链式二叉树&#xff0c;那必须…

Python sqlite3库 实现 数据库基础及应用 输入地点,可输出该地点的爱国主义教育基地名称和批次的查询结果。

目录 【第11次课】实验十数据库基础及应用1-查询 要求: 提示: 运行结果&#xff1a; 【第11次课】实验十数据库基础及应用1-查询 声明&#xff1a;著作权归作者所有。商业转载请联系作者获得授权&#xff0c;非商业转载请注明出处。 1.简答题 数据库文件Edu_Base.db&#…

FreeRTOS的任务详解、创建与删除

目录 1、任务详解 1.1 什么是任务&#xff1f; 1.2 任务的特点 1.3 任务的状态 1.4 任务的优先级 1.5 任务的堆和栈 2、任务的创建与删除 2.1 相关API 2.2 函数解析 2.2.1 xTaxkCreate() 2.2.2 xTaskCreateStatic() 2.2.3 vTaskDelete() 3、实战案例 3.1 创建两个…

​Inf-DiT:Upsampling Any-Resolution Image、Vidu、MVDiff、Trio-ViT

本文首发于公众号&#xff1a;机器感知 ​Inf-DiT&#xff1a;Upsampling Any-Resolution Image、Vidu、MVDiff、Trio-ViT Inf-DiT: Upsampling Any-Resolution Image with Memory-Efficient Diffusion Transformer Diffusion models have shown remarkable performance in im…

C++:STL-string

前言 本文主要介绍STL六大组件中的容器之一&#xff1a;string&#xff0c;在学习C的过程中&#xff0c;我们要将C视为一个语言联邦&#xff08;摘录于Effective C条款一&#xff09;。如何理解这句话呢&#xff0c;我们学习C&#xff0c;可将其分为四个板块&#xff1b;分别为…

基于springboot实现医院药品管理系统项目【项目源码+论文说明】

基于springboot实现医院药品管理系统演示 摘要 身处网络时代&#xff0c;随着网络系统体系发展的不断成熟和完善&#xff0c;人们的生活也随之发生了很大的变化&#xff0c;人们在追求较高物质生活的同时&#xff0c;也在想着如何使自身的精神内涵得到提升&#xff0c;而读书就…

python-类和对象

1、设计一个 Circle类来表示圆,这个类包含圆的半径以及求面积和周长的函数。再使用这个类创建半径为1~10的圆,并计算出相应的面积和周长。 &#xff08;1&#xff09;源代码&#xff1a; import math class Circle: def __init__(self, r): self.r r #面积 def area(self): r…

嵌入式开发九:STM32时钟系统

时钟对于单片机来说是非常重要的&#xff0c;它为单片机工作提供一个稳定的机器周期从而使系统能够正常运行。时钟系统犹如人的心脏&#xff0c;一旦有问题整个系统就崩溃。我们知道 STM32 属于高级单片机&#xff0c;其内部有很多的外设&#xff0c;但不是所有外设都使用同一时…

IO 5.9号

创建一对父子进程&#xff1a; 父进程负责向文件中写入 长方形的长和宽 子进程负责读取文件中的长宽信息后&#xff0c;计算长方形的面积 #include <myhead.h>int main(int argc, const char *argv[]){int retvalfork();if(retval>0){float length,width;int wfdopen(…