哈夫曼树(构建、编码、译码)(详细分析+C++代码实现)
D 哈夫曼树
题目要求
编写一个哈夫曼编码译码程序。针对一段文本,根据文本中字符出现频率构造哈夫曼树,给出每个字符的哈夫曼编码,并进行译码,计算编码前后文本大小。
为确保构建的哈夫曼树唯一,本题做如下限定:
- 选择根结点权值最小的两棵二叉树时,选取权值较小者作为左子树。
- 若多棵二叉树根结点权值相等,则先生成的作为左子树,后生成的作为右子树,具体来说:i) 对于单结点二叉树,优先选择根结点对应字母在文本中最先出现者,如文本为cba,三个字母均出现1次,但c在文本中最先出现,b第二出现,故则选择c作为左子树,b作为右子树。ii) 对于非单结点二叉树,先生成的二叉树作为左子树,后生成的二叉树作为右子树。iii. 若单结点和非单结点二叉树根结点权值相等,优先选择单结点二叉树。
- 生成哈夫曼编码时,哈夫曼树左分支标记为0,右分支标记为1。
输入格式:
输入为3行。第1行为一个字符串,包含不超过5000个字符,至少包含两个不同的字符,每个字符为a-z的小写字母。第2、3行为两个由0、1组成的字符串,表示待译码的哈夫曼编码。
输出格式:
输出第一行为用空格间隔的2个整数,分别为压缩前后文本大小,以字节为单位,一个字符占1字节,8个二进制位占1字节,若压缩后文本不足8位,则按1字节算。输出从第二行开始,每行为1个字符的哈夫曼编码,按各字符在文本中出现次数递增顺序输出,若多个字符出现次数相同,则按其在文本出现先后排列。每行格式为“字母:编码”。最后两行为两行字符串,表示译码结果,若译码失败,则输出INVALID。
输入样例:
cbaxyyzz
0100
011
输出样例:
8 3
c:100
b:101
a:110
x:111
y:00
z:01
zy
INVALID
题目分析
要点1:原文本字符数据整理
根据输入的字符串,整理字符的种类数,以及各字符的个数,并将其按照出现次数从小到大进行排列,若次数相同,则先出现的仍在前。
//数据预处理
//计算输入的文本出现的所有不同的字符和对应数量
const int N = 5010;
int h[N], idx,w[N]; //w数组存储字符在文本中出现的个数,idx最终保存不同的字符种类数,h数组存储对应字符的下标
char da[N]; //da数组存储字符
int PreLengh; //初始文本的长度
string line; //初始文本
void input() {cin >> line;PreLengh = line.size();for (char ch : line) { //遍历文本中的每一个字符if (w[h[ch]] == 0) { //字符的权重为0,该字符第一次出现da[++idx] = ch;h[ch] = idx;w[idx] = 1;}else {w[h[ch]]++; //否则,不是第一次出现,权重+1}}//数据录入结束,进行排序//冒泡排序,从小到大排列,权重相同的原来在前仍在前for (int i = 1; i <= idx; i++) {for (int j = 1; j <= idx - 1; j++) {if (w[j] > w[j + 1]) {swap(w[j], w[j + 1]);swap(da[j], da[j + 1]); //权重和数据都要交换}}}
}
要点2:构建huffman树
1.在森林中取权值最小的两个根结点s和nl,合并成一棵二叉树,并生成一个新结点T作为这两个结点的父亲,T的权值是它的两个子结点的权值之和。
2.对新森林重复上一步操作,直至森林中只有唯一的根结点时,终止操作。
//创建哈夫曼树
HuffmanTree* createHuffmanTree(char data[],int weight[],int n) {HuffmanTree* tree=new HuffmanTree;tree->m = n; //结点总数tree->H = new HuffmanNode * [tree->m + 1];HuffmanNode* p1, * p2, * p, * t;//初始化结点for (int i = 1; i <= tree->m; i++) {tree->H[i] = new HuffmanNode;tree->H[i]->INFO = data[i];tree->H[i]->Weight = weight[i];tree->H[i]->LLINK = NULL;tree->H[i]->RLINK = NULL;}//组合结点int i, j;for (int i = 1; i < tree->m; i++) { //遍历所有结点t = new HuffmanNode;p1 = tree->H[i]; //选取最小的两个结点作为左右子树p2 = tree->H[i + 1];t->LLINK = p1;t->RLINK = p2;t->Weight = p1->Weight + p2->Weight;p = t;j = i + 2;//比较排列,仍要保证从小到大排列while (j <= tree->m && (p->Weight) >= tree->H[j]->Weight) {tree->H[j - 1] = tree->H[j];j++;}//将新生成的树放入森林中tree->H[j - 1] = p;}return tree;
}
要点3:Huffman编码
要输出所有字符的编码,遍历思想,走左子树则+0,走右子树则+1,直至走到叶结点,为字符,存储为对应字符的Huffman编码。
//Huffman编码
//char标志字符,与其对应的Huffman编码
typedef unordered_map<char, string> UMCS;
UMCS HuffmanCode;
void CreateHuffmanCode(HuffmanNode* root, string code) {if (root == NULL) return;if (!root->LLINK && !root->RLINK) { //如果是叶结点,遍历到字符HuffmanCode[root->INFO] = code; }CreateHuffmanCode(root->LLINK, code + "0"); //左子树+0CreateHuffmanCode(root->RLINK, code + "1"); //右子树+1
}
要点4:对二进制进行译码
读入一整串的二进制数,遇到0就走左子树,遇到1就走右子树,直至走到叶结点,为字符,一个字符到此译码成功,将该字符串到总答案中。若此时还有编码剩余,则重新从树根开始,继续译码,直至读入全部二进制编码。
若全部二进制读入完毕,但此时指针不位于叶结点,证明译码失败,没有正确结束。输出"INVALID"。
//对二进制进行译码
void TransHuffmanCode(HuffmanNode* root) {HuffmanNode* t = root;for (int num = 2; num > 0; num--) {string op,ans="";cin >> op; //读入整串的二进制编码for (int i = 0; i < op.size(); i++) {char k = op[i];if (k == '0') t = t->LLINK; //如果是0,就走左指针if (k == '1') t = t->RLINK; //如果是1,就走右指针if (!t->LLINK && !t->RLINK) { //走到叶结点,译码成功,串入答案ansans = ans + t->INFO;if (i != op.size() - 1) t = root; //若还有编码未译完,重新返回树根,继续译码}}if (!(!t->LLINK && !t->RLINK)) cout<<"INVALID"; //如果译码到最后,没有走到叶结点,证明译码失败else cout << ans;cout << endl;t = root;}
}
完整代码
#include <iostream>
#include <cstring>
#include <unordered_map>
using namespace std;//Huffman结点
typedef struct HuffmanNode {char INFO; //信息域int Weight; //权值HuffmanNode* LLINK; //左链接HuffmanNode* RLINK; //右链接
}HuffmanNode;//Huffman树的构建
typedef struct HuffmanTree {HuffmanNode** H; //存储哈夫曼树结点的数组Hint m; //哈夫曼树结点总数
}HuffmanTree;//数据预处理
//计算输入的文本出现的所有不同的字符和对应数量
const int N = 5010;
int h[N], idx,w[N]; //w数组存储字符在文本中出现的个数,idx最终保存不同的字符种类数,h数组存储对应字符的下标
char da[N]; //da数组存储字符
int PreLengh; //初始文本的长度
string line; //初始文本
void input() {cin >> line;PreLengh = line.size();for (char ch : line) { //遍历文本中的每一个字符if (w[h[ch]] == 0) { //字符的权重为0,该字符第一次出现da[++idx] = ch;h[ch] = idx;w[idx] = 1;}else {w[h[ch]]++; //否则,不是第一次出现,权重+1}}//数据录入结束,进行排序//冒泡排序,从小到大排列,权重相同的原来在前仍在前for (int i = 1; i <= idx; i++) {for (int j = 1; j <= idx - 1; j++) {if (w[j] > w[j + 1]) {swap(w[j], w[j + 1]);swap(da[j], da[j + 1]); //权重和数据都要交换}}}
}//创建哈夫曼树
HuffmanTree* createHuffmanTree(char data[],int weight[],int n) {HuffmanTree* tree=new HuffmanTree;tree->m = n; //结点总数tree->H = new HuffmanNode * [tree->m + 1];HuffmanNode* p1, * p2, * p, * t;//初始化结点for (int i = 1; i <= tree->m; i++) {tree->H[i] = new HuffmanNode;tree->H[i]->INFO = data[i];tree->H[i]->Weight = weight[i];tree->H[i]->LLINK = NULL;tree->H[i]->RLINK = NULL;}//组合结点int i, j;for (int i = 1; i < tree->m; i++) { //遍历所有结点t = new HuffmanNode;p1 = tree->H[i]; //选取最小的两个结点作为左右子树p2 = tree->H[i + 1];t->LLINK = p1;t->RLINK = p2;t->Weight = p1->Weight + p2->Weight;p = t;j = i + 2;//比较排列,仍要保证从小到大排列while (j <= tree->m && (p->Weight) >= tree->H[j]->Weight) {tree->H[j - 1] = tree->H[j];j++;}//将新生成的树放入森林中tree->H[j - 1] = p;}return tree;
}//Huffman编码
//char标志字符,与其对应的Huffman编码
typedef unordered_map<char, string> UMCS;
UMCS HuffmanCode;
void CreateHuffmanCode(HuffmanNode* root, string code) {if (root == NULL) return;if (!root->LLINK && !root->RLINK) { //如果是叶结点,遍历到字符HuffmanCode[root->INFO] = code; }CreateHuffmanCode(root->LLINK, code + "0"); //左子树+0CreateHuffmanCode(root->RLINK, code + "1"); //右子树+1
}//计算压缩后文本的大小
int PostLength = 0;
void PostNum(UMCS HuffmanCode) {for (char k : line) { //从头到位按照原文本的逐一计算PostLength += HuffmanCode[k].size();}PostLength = (PostLength + 7) / 8; //以字节为单位计算,不足8位,按一字节算
}//打印输出huffman编码
void printHuffmanCode() {for (int i = 1; i <= idx; i++) { //da数组内存储的即为数据字符cout << da[i] << ":" << HuffmanCode[da[i]] << endl;}
}//对二进制进行译码
void TransHuffmanCode(HuffmanNode* root) {HuffmanNode* t = root;for (int num = 2; num > 0; num--) {string op,ans="";cin >> op; //读入整串的二进制编码for (int i = 0; i < op.size(); i++) {char k = op[i];if (k == '0') t = t->LLINK; //如果是0,就走左指针if (k == '1') t = t->RLINK; //如果是1,就走右指针if (!t->LLINK && !t->RLINK) { //走到叶结点,译码成功,串入答案ansans = ans + t->INFO;if (i != op.size() - 1) t = root; //若还有编码未译完,重新返回树根,继续译码}}if (!(!t->LLINK && !t->RLINK)) cout<<"INVALID"; //如果译码到最后,没有走到叶结点,证明译码失败else cout << ans;cout << endl;t = root;}
}
int main() {//数据预处理input();//创建Huffman树HuffmanTree* tree = createHuffmanTree(da, w, idx);//构造Huffman编码CreateHuffmanCode(tree->H[idx], "");//计算编码后文本大小PostNum(HuffmanCode);//输出压缩前后文本大小cout << PreLengh << ' ' << PostLength << endl;//输出各字符的Huffman编码printHuffmanCode();//对输入的Huffman二进制编码进行译码TransHuffmanCode(tree->H[idx]);return 0;
}
提交结果
相关文章:

哈夫曼树(构建、编码、译码)(详细分析+C++代码实现)
D 哈夫曼树 题目要求 编写一个哈夫曼编码译码程序。针对一段文本,根据文本中字符出现频率构造哈夫曼树,给出每个字符的哈夫曼编码,并进行译码,计算编码前后文本大小。 为确保构建的哈夫曼树唯一,本题做如下限定&…...

C++ 二叉搜索树
目录 概念 性能分析 二叉搜索树的插入 二叉树的查找 二叉树的前序遍历 二叉搜索树的删除(重点) 完整代码 key与value的使用 概念 对于一个二叉搜索树 若它的左子树不为空,则左子树上所有的节点的值都小于等于根节点的值若它的右子树不为空…...

docker构建Java项目镜像常用的Java版本,国内私有仓库公网快速下载,解决从docker.io无法下载的问题
2015工作至今,10年资深全栈工程师,CTO,擅长带团队、攻克各种技术难题、研发各类软件产品,我的代码态度:代码虐我千百遍,我待代码如初恋,我的工作态度:极致,责任ÿ…...
低代码系统-氚云、简道云表单控件对比
组件对比 氚云 简道云 是否都有 1 单行文本 单行文本 ☑️ 2 多行文本 多行文本 ☑️ 3 日期 日期时间 ☑️ 4 数字 数字 ☑️ 5 单选框 单选按钮组 ☑️ 6 复选框 复选框组 ☑️ 7 下拉框 下拉框 ☑️ 8 附件 附件 ☑️ 9 图片 图片 ☑️ 10 地址 地…...

为什么IDEA提示不推荐@Autowired❓️如果使用@Resource呢❓️
前言 在使用 Spring 框架时,依赖注入(DI)是一个非常重要的概念。通过注解,我们可以方便地将类的实例注入到其他类中,提升开发效率。Autowired又是被大家最为熟知的方式,但很多开发者在使用 IntelliJ IDEA …...

Unity在WebGL中拍照和录视频
原工程地址https://github.com/eangulee/UnityWebGLRecoder Unity版本2018.3.6f1,有点年久失修了 https://github.com/xue-fei/Unity.WebGLRecorder 修改jslib适配了Unity2021 效果图 录制的视频 Unity在WebGL中拍照和录视频...

爬虫基础之爬取某站视频
目标网址:为了1/4螺口买小米SU7,开了一个月,它值吗?_哔哩哔哩_bilibili 本案例所使用到的模块 requests (发送HTTP请求)subprocess(执行系统命令)re (正则表达式操作)json (处理JSON数据) 需求分析: 视频的名称 F12 打开开发者工具 or 右击…...

mongoDB常见指令
即使我们自己开发用不到mongoDB,但是接手别人项目的时候,别人如果用了,我们也要会简单调试一下 虽然mongoDB用的不是sql语句,但语句的逻辑都是相似的,比如查看数据库、数据表,增删改查这些 我们下面以doc…...

人工智能之深度学习_[5]-神经网络优化学习率衰减优化正则化方法
文章目录 神经网络入门二3 神经网络优化方法3.1 梯度下降算法回顾3.2 反向传播(BP算法)3.2.1 反向传播概念3.2.2 反向传播详解 3.3 梯度下降优化方法3.3.1 指数加权平均3.3.2 动量算法Momentum3.3.3 AdaGrad3.3.4 RMSProp3.3.5 Adam3.3.6 小结 4 学习率衰…...
Oracle之Merge into函数使用
Merge into函数为Oracle 9i添加的语法,用来合并update和insert语句。所以也经常用于update语句的查询优化: 一、语法格式: merge into A using B on (A.a B.a) --注意on后面带括号,且不能更新join的字段 when matched then upd…...
深度解析:哪种心磁图技术是心脏检查的精准之选?
在全球心血管疾病的阴影日益笼罩的今天,医学界正积极寻求一种无损、无创、无辐射的心脏健康监测方式。心磁图仪(MCG),这一前沿技术,凭借其独特的优势,悄然成为心脏电磁功能监测的新星。它不仅为心肌缺血、心…...

SpringBoot--基本使用(配置、整合SpringMVC、Druid、Mybatis、基础特性)
这里写目录标题 一.介绍1.为什么依赖不需要写版本?2.启动器(Starter)是何方神圣?3.SpringBootApplication注解的功效?4.启动源码5.如何学好SpringBoot 二.SpringBoot3配置文件2.1属性配置文件使用2.2 YAML配置文件使用2.3 YAML配置文件使用2.…...

单片机-STM32 IIC通信(OLED屏幕)(十一)
一、屏幕的分类 1、LED屏幕: 由无数个发光的LED灯珠按照一定的顺序排列而成,当需要显示内容的时候,点亮相关的LED灯即可,市场占有率很高,主要是用于户外,广告屏幕,成本低。 LED屏是一种用发光…...
观察者模式 - 观察者模式的应用场景
引言 观察者模式(Observer Pattern)是设计模式中行为型模式的一种,它定义了对象之间的一对多依赖关系,使得当一个对象的状态发生改变时,所有依赖于它的对象都会自动收到通知并更新。观察者模式广泛应用于事件处理系统…...

【C++】详细讲解继承(下)
本篇来继续说说继承。上篇可移步至【C】详细讲解继承(上) 1.继承与友元 友元关系不能继承 ,也就是说基类友元不能访问派⽣类私有和保护成员。 class Student;//前置声明class Same //基类 { public:friend void Fun(const Same& p, con…...

消息队列篇--原理篇--Pulsar(Namespace,BookKeeper,类似Kafka甚至更好的消息队列)
Apache Pulusar是一个分布式、多租户、高性能的发布/订阅(Pub/Sub)消息系统,最初由Yahoo开发并开源。它结合了Kafka和传统消息队列的优点,提供高吞吐量、低延迟、强一致性和可扩展的消息传递能力,适用于大规模分布式系…...

扬帆数据结构算法之舟,启航C++探索征途——LeetCode深度磨砺:顺序表技术精进实践
人无完人,持之以恒,方能见真我!!! 共同进步!! 文章目录 顺序表练习1.移除数组中指定的元素方法1(顺序表)方法2(双指针) 2.删除有序数组中的重复项…...

基于本地事务表+MQ实现分布式事务
基于本地事务表MQ实现分布式事务 引言1、原理2、本地消息表优缺点3、代码实现3.1、代码执行流程3.2、项目结构3.3、项目源码 引言 本地消息表的方案最初由ebay的工程师提出,核心思想是将分布式事务拆分成本地事务进行处理。本地消息表实现最终一致性。本文主要学习…...

数据结构:二叉树—面试题(一)
目录 1、相同的树 2、另一棵树的子树 3、翻转二叉树 4、平衡二叉树 5、对称二叉树 6、二叉树遍历 7、二叉树的分层遍历 1、相同的树 习题链接https://leetcode.cn/problems/same-tree/description/https://leetcode.cn/problems/same-tree/description/ 描述:…...

【Wordpress网站制作】切换语言的问题
前言 自学笔记,解决问题为主,欢迎补充。 本文重点:如何将页面语言从默认的【英语】修改成【中文】。 问题描述 安装完wordpress,在【Setting】→【General】的语言中,选项只有英语。无法切换成中文 方法1: 在 wp-c…...

第19节 Node.js Express 框架
Express 是一个为Node.js设计的web开发框架,它基于nodejs平台。 Express 简介 Express是一个简洁而灵活的node.js Web应用框架, 提供了一系列强大特性帮助你创建各种Web应用,和丰富的HTTP工具。 使用Express可以快速地搭建一个完整功能的网站。 Expre…...
java_网络服务相关_gateway_nacos_feign区别联系
1. spring-cloud-starter-gateway 作用:作为微服务架构的网关,统一入口,处理所有外部请求。 核心能力: 路由转发(基于路径、服务名等)过滤器(鉴权、限流、日志、Header 处理)支持负…...
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以?
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以? 在 Golang 的面试中,map 类型的使用是一个常见的考点,其中对 key 类型的合法性 是一道常被提及的基础却很容易被忽视的问题。本文将带你深入理解 Golang 中…...

【JavaEE】-- HTTP
1. HTTP是什么? HTTP(全称为"超文本传输协议")是一种应用非常广泛的应用层协议,HTTP是基于TCP协议的一种应用层协议。 应用层协议:是计算机网络协议栈中最高层的协议,它定义了运行在不同主机上…...
Admin.Net中的消息通信SignalR解释
定义集线器接口 IOnlineUserHub public interface IOnlineUserHub {/// 在线用户列表Task OnlineUserList(OnlineUserList context);/// 强制下线Task ForceOffline(object context);/// 发布站内消息Task PublicNotice(SysNotice context);/// 接收消息Task ReceiveMessage(…...
测试markdown--肇兴
day1: 1、去程:7:04 --11:32高铁 高铁右转上售票大厅2楼,穿过候车厅下一楼,上大巴车 ¥10/人 **2、到达:**12点多到达寨子,买门票,美团/抖音:¥78人 3、中饭&a…...

【Java_EE】Spring MVC
目录 Spring Web MVC 编辑注解 RestController RequestMapping RequestParam RequestParam RequestBody PathVariable RequestPart 参数传递 注意事项 编辑参数重命名 RequestParam 编辑编辑传递集合 RequestParam 传递JSON数据 编辑RequestBody …...

算法笔记2
1.字符串拼接最好用StringBuilder,不用String 2.创建List<>类型的数组并创建内存 List arr[] new ArrayList[26]; Arrays.setAll(arr, i -> new ArrayList<>()); 3.去掉首尾空格...
JAVA后端开发——多租户
数据隔离是多租户系统中的核心概念,确保一个租户(在这个系统中可能是一个公司或一个独立的客户)的数据对其他租户是不可见的。在 RuoYi 框架(您当前项目所使用的基础框架)中,这通常是通过在数据表中增加一个…...

Netty从入门到进阶(二)
二、Netty入门 1. 概述 1.1 Netty是什么 Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers & clients. Netty是一个异步的、基于事件驱动的网络应用框架,用于…...