哈夫曼编码的应用

数据结构与算法课的一个简单实验,记录一下,以供参考。

文章目录

      • 要求
      • 测试样例
      • 统计字母出现次数
      • 建立哈夫曼树
      • 对字符编码
      • 对原文进行编码
      • 译码

要求

  1. 输入一段100—200字的英文短文,存入一文件a中。
  2. 统计短文出现的字母个数n及每个字母的出现次数。
  3. 以字母出现次数作权值,建Huffman树(n个叶子),给出每个字母的Huffman编码。
  4. 用每个字母编码对原短文进行编码,码文存入文件b中。
  5. 用Huffman树对b中码文进行译码,结果存入文件c中,比较a,c是否一致,以检验编码、译码的正确性。

测试样例

节选自J·阿尔弗瑞德·普鲁弗洛克的情歌,存放在文件a.txt中:

Let us go then, you and I,
When the evening is spread out against the sky
Like a patient etherized upon a table;
Let us go, through certain half-deserted streets,
The muttering retreats
Of restless nights in one-night cheap hotels
And sawdust restaurants with oyster-shells:
Streets that follow like a tedious argument
Of insidious intent
To lead you to an overwhelming question ...
Oh, do not ask, “What is it?”
Let us go and make our visit.

这段诗是游戏赛博朋克2077节制结局奥特对v说的,印象深刻,找到英文原版拿来用了。

统计字母出现次数

这块用哈希表来存比较简单,字母为key,出现的次数为valve,借助stl容器unordered_map实现,从文件中一个字符一个字符读,然后将其存到哈希表中。

代码如下:

void analysis()
{ifstream ifs("a.txt", ios::in);char c;while (ifs.get(c))m[c]++;ifs.close();
}

测试代码如下:

for (auto& kv : m)cout << kv.first << '>' << kv.second << '\t';
cout << endl;

结果如下:
img

第一行最后的换行是因为回车符。原文正好12行,换了11次行。

建立哈夫曼树

采用链式存储,先给出节点的定义:

struct treeNode
{char data;int weight;treeNode* parent;treeNode* lchild;treeNode* rchild;treeNode(char d = 0, int w = 0, treeNode* p = nullptr, treeNode* l = nullptr, treeNode* r = nullptr): data(d), weight(w), parent(p), lchild(l), rchild(r){}  
};
vector<treeNode*> v;

在给出字符的哈夫曼编码时采取的从叶子到根的方式,所以需要保存父节点指针,同时需要记录所有叶子节点,借助stl容器vector保存。

建树的过程很简单,先建立n个叶子结点,然后创建n-1个非叶子节点,每次从节点堆里取出两个权值最低的节点作为新节点,然后加入节点堆重复这个过程,一共循环n-1次。

每次都需要取权值最小的节点,考虑使用小根堆,借助stl容器priority_queue,由于优先队列中存放的是自定义类型指针,需要自定义比较方式,用仿函数实现:

struct treeNodeCompare  
{  bool operator()(treeNode* lhs, treeNode* rhs)  {  return lhs->weight > rhs->weight;}  
};

所以建树的代码就好写了:

void createTree()
{priority_queue<treeNode*, vector<treeNode*>, treeNodeCompare> q;treeNode *e;for (auto& kv : m){e = new treeNode(kv.first, kv.second);q.push(e);v.push_back(e);}treeNode *l, *r, *p;for (int i = 0; i < m.size() - 1; i++){l = q.top();q.pop();r = q.top();q.pop();p = new treeNode(0, l->weight + r->weight, nullptr, l, r);l->parent = p;r->parent = p;q.push(p);}
}

对字符编码

对字符进行编码,这里采取从下至上的方式,在左分支给0,右分支给1。

所以代码思路就很简单,遍历先前保存的叶子结点集合,一直向上找parent,如果是parent的left就+0,如果是parent的right就+1,直到走到根节点,这样得到的序列是逆过来的,可以选择转置,这个无所谓。

得到字符的编码之后还需要保存一下,后面对短文进行编码和译码的时候都要用。

用哈希表存的话需要存两份,一份字符为key编码为value,对文章编码的时候用;一份编码为key字符为value,译码的时候用,这份其实也可以不要,后续给出不用这个的译码方式。

代码如下:

unordered_map<char, string> ch_hf;
unordered_map<string, char> hf_ch;
void createHfCode()
{for (auto e : v)  {  string tmp;  treeNode* p = e;  while (p->parent){  if (p->parent->lchild == p)  tmp += '0';  else  tmp += '1';  p = p->parent;  }  reverse(tmp.begin(), tmp.end());  ch_hf[e->data] = tmp;hf_ch[tmp] = e->data;}
}

也可以使用先序遍历,遍历到根节点就存储,然后回溯,这样就不需要父节点了,但是需要保存一下根节点:

void dfs(treeNode* p, string& s)
{if (p->lchild == nullptr && p->rchild == nullptr){hf_ch[s] = p->data;ch_hf[p->data] = s;return;}if (p->lchild){s += '0';dfs(p->lchild, s);s.pop_back();}if (p->rchild){s += '1';dfs(p->rchild, s);s.pop_back();}
}void createHfCode()
{string s;dfs(root, s);
}

用下面的代码进行这部分的测试:

for (auto& kv : ch_hf)printf("%c->%-15s", kv.first, kv.second.c_str());
cout << endl;

结果如下:
image-20240515223825233

第三行突然换行还是因为换行符。

对原文进行编码

这个就很简单了,还是一个一个字符读取,直接向文件b中写入字符对应的编码。

哈希表还是很方便的,代码如下:

void code()
{ifstream ifs("a.txt", ios::in);ofstream ofs("b.txt", ios::out);char c;while (ifs.get(c))ofs << ch_hf[c];ifs.close();ofs.close();
}

编译运行得到文件b,一长串01序列,全部都在一行不好展示,用下面代码进行测试:

ifstream ifs2("b.txt", ios::in);
while (ifs2.get(c))cout << c;
cout << endl;
ifs2.close();

结果如下:
image-20240515223801747

译码

第一种方法,使用多保存的那个哈希表。

还是一个字符一个字符地从文件b读取,读一个就加到临时字符串中,然后从哈希表中查询一下是否有以当前字符串为key的kv对,如果有就将其value值写入到文件c中。代码如下:

void encode()
{ifstream ifs("b.txt", ios::in);ofstream ofs("c.txt", ios::out);string tmp;char c;while (ifs.get(c)){tmp += c;auto it = hf_ch.find(tmp);if (it != hf_ch.end()){ofs << it->second;tmp.clear();}}ifs.close();ofs.close();
}

第二种方法,不使用哈希表,直接从树中查询,需要保存根节点。

还是一个字符一个字符读取,如果读到0就走到左子树,如果读到1就走到右子树。当走到叶子节点说明已经读完了一个字符的完整哈夫曼编码,可以进行译码,将叶子节点存的字符写入到文件c中。代码如下:

void encode2()
{ifstream ifs("b.txt", ios::in);ofstream ofs("c.txt", ios::out);char c;treeNode* p = root;while (ifs.get(c)){if (c == '0')p = p->lchild;elsep = p->rchild;if (p->data){ofs << p->data;p = root;}}ifs.close();ofs.close();
}

编译运行得到c.txt:
image-20240515223738291

与原文完全一致。

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

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

相关文章

终于搞懂Linux 设备树中的#address-cells,#size-cells 和reg 属性

目录 一、前置知识 1. 处理器平台2. reg 属性的基本格式3. reg 属性的作用 reg 用法 二、#address-cells 和 #size-cells 属性 1. 示例1 2. 示例23. 示例3 一、前置知识 要理解#address-cells和#size-cell 这两个属性&#xff0c;就要先了解 reg属性。 1. 处理器平台 下…

VS2022如何添加现有项

以 想在队列里&#xff0c;使用堆栈的.c&#xff0c;.h文件 为例 目录 1.复制堆栈的.c&#xff0c;.h文件 ​编辑 2.打开队列所在项目的文件夹 3.粘贴堆栈的.c&#xff0c;.h文件 4.在头文件和源文件添加相应的堆栈的.c&#xff0c;.h文件 1.复制堆栈的.c&#xff0c;.h文件…

QML进阶(十七) ECMAScript 语法介绍

文章目录 基本语法变量基本类型类型转换对象函数和循环打印输出内置对象来自QML的基本类型ECMAScript语言的标准是由Netscape、Sun、微软、Borland等公司基于JavaScript和JScript定义出来的脚本语言标准。可以为不同种类的浏览器环境提供核心的脚本编程能力。在QML中我们通过EC…

HCIP【VLAN综合实验】

目录 一、实验拓扑图&#xff1a; 二、实验要求&#xff1a; 三、实验思路&#xff1a; 四、实验步骤&#xff1a; 1、在交换机SW1,SW2,SW3配置VLAN和各个接口对应类型的配置 2、在路由器上面配置DHCP服务 一、实验拓扑图&#xff1a; 二、实验要求&#xff1a; 1、PC1 …

STK12 RPO模块学习(3)

一、Maintain NMC RPO Sequence Maintain Natural Motion Circumnavigation RPO序列在目标星和追踪星经历不同的力的情况下保持NMC。通常这种差异是由于阻力和太阳光压造成的。这些是主要不同力当执行接近任务的时候&#xff0c;因为重力和相对三体摄动力非常小当相对距离在10…

【Docker使用技巧】

Docker使用 命令 //docker 进入容器内部 退出 exit docker exec -it 容器ID /bin/bash //docker 查看容器日志 退出 Ctrlc docker logs [OPTIONS] 容器ID或名称 [OPTIONS] -f : 跟踪日志输出 -t : 显示时间戳 --tail :仅列出最新N条容器日志 --since&#xff1a;显示某个日…

从零开始精通RTSP之传输AAC音频流

概述 AAC&#xff0c;英文全称为Advanced Audio Coding&#xff0c;是一种高效的有损音频压缩格式&#xff0c;由MPEG-4标准定义。相比传统的MP3&#xff0c;AAC在相同比特率下能提供更好的音质&#xff0c;尤其在低比特率场景下优势明显。AAC支持多种采样率、声道数和编码工具…

MySQL远程连接错误解决:“Host is not allowed to connect to this MySQL server”详解

目录 一、异常错误二、原因三、解决方法 一、异常错误 通过远程客户端访问MySQL服务器时会遇到“Host is not allowed to connect to this MySQL server”的错误提示。 二、原因 MySQL服务器当前配置不允许来自特定主机的连接尝试。 三、解决方法 允许远程主机访问MySQL服…

link.click()时浏览器报错The file at ‘data:image/png;base64,iVBORw

代码如下&#xff1a; const dataURL canvas.toDataURL({format: "png",width: 400,height: 400, });const link document.createElement("a"); link.download new Date().getTime();link.href dataURL; document.body.appendChild(link); link.click…

IT行业的现状与未来发展趋势

方向一&#xff1a;技术革新与行业应用 随着科技的日新月异&#xff0c;IT行业已成为全球经济增长和社会进步的重要引擎。从云计算、大数据、人工智能到物联网、5G通信和区块链&#xff0c;这些前沿技术正以前所未有的速度重塑我们的生活和工作方式。以下是我对IT行业现状及未来…

高压无源探头能测整流桥电压吗?

高压无源探头是用于测量高电压电路中信号的一种工具&#xff0c;它不需要外部电源供电。然而&#xff0c;对于测量整流桥电压&#xff0c;需要考虑几个因素以确定是否可以使用高压无源探头。 首先&#xff0c;让我们了解一下整流桥的基本原理。整流桥是一种电路&#xff0c;用…

MyBatis的一二级缓存区别

MyBatis的一级缓存和二级缓存之间存在以下主要区别&#xff1a; 缓存级别与作用域&#xff1a; 一级缓存&#xff1a;也称为SqlSession级别的缓存&#xff0c;与数据库会话&#xff08;SqlSession对象&#xff09;绑定&#xff0c;并且默认开启。一级缓存的作用域仅限于同一个…

STM32--HC-SR501 热释电人体红外感应模块

实物引脚图&#xff1a; 模块工作特性&#xff1a; 当人进入感应范围之后输出引脚输出高电平&#xff0c;人离开感应范围自动延时输出低电平 热释电效应&#xff1a; 热释电传感器&#xff0c;也称为人体红外传感器&#xff0c;其工作原理基于热释电效应。这种传感器由几个关…

Rust中使用Rocket框架返回html网页,返回一个基于 Handlebars (HBS) 模板的响应

在Rust中使用Rocket框架返回网页&#xff0c;通常涉及创建一个路由&#xff0c;该路由将返回一个HTML页面。Rocket是一个快速、易用且可扩展的Web框架&#xff0c;它允许你以一种简洁的方式定义路由和处理请求。 一、使用Rocket框架返回一个简单的HTML页面&#xff1a; 添加依…

手机怎么下载别人直播间视频

手机下载直播视频&#xff0c;您需要按照以下步骤进行操作&#xff1a; 1. 打开直播平台&#xff0c;获取正在直播的链接&#xff0c;就是直播间的地址&#xff0c;然后粘贴在直接视频解析工具里&#xff0c;就可以同步下载直播视频画面。 2. 获取直播视频解析工具方法&#…

项目管理-案例重点知识(成本管理)

项目管理&#xff1a;每天进步一点点~ 活到老&#xff0c;学到老 ヾ(◍∇◍)&#xff89;&#xff9e; 何时学习都不晚&#xff0c;加油 三、成本管理 案例重点 成本管理 案例重点内容&#xff1a; &#xff08;1&#xff09;成本管理计划内容 &#xff08;2&#xff09;估算…

pcdn边缘云常见sla有哪些?如何避免被白嫖

PCDN&#xff08;Point-to-Point Content Delivery Network&#xff09;边缘云常见的SLA&#xff08;Service Level Agreement&#xff09;规则包括高峰期离线、服务时间、重传延时、限速等。这些规则是为了保证服务质量和用户体验。下面将详细解释这些规则&#xff0c;并提供一…

ANSYS Maxwell16 引导

Maxwell 使用 下载 https://download.csdn.net/download/wangjun_huster/89313272 安装 https://www.52txr.cn/2022/MaxwellInstall.html 入门

渗透相关面试+流量分析

文章目录 简单自我介绍上一个工作的主要内容Hvv的分组和流程你在hvv/攻防演练中取得了哪些成绩&#xff1f; 二、渗透相关面试题基础端口号以及入侵方式OSI七层协议响应状态码都有哪些&#xff1f;**WAF和IPS的区别**盲注是什么&#xff1f;java内存马类型**内存马有几种类型**…

2024最新大厂C++面试真题合集,大厂面试百日冲刺 bay11

CSIG 腾讯后台 new和malloc区别 "new"是C的一个操作符&#xff0c;它在创建对象时会自动调用构造函数。"malloc"是C语言的函数&#xff0c;仅仅分配内存。"new"返回的是对象类型的指针&#xff0c;类型与对象的类型保持一致&#xff0c;无需类…