哈夫曼编码的应用

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

文章目录

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

要求

  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文件…

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…

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…

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

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

谷歌全力反击 OpenAI:Google I/O 2024 揭晓 AI 新篇章,一场激动人心的技术盛宴

&#x1f680; 谷歌全力反击 OpenAI&#xff1a;Google I/O 2024 揭晓 AI 新篇章&#xff0c;一场激动人心的技术盛宴&#xff01; 在这个人工智能的全新时代&#xff0c;只有谷歌能让你眼前一亮&#xff01;来自全球瞩目的 Google I/O 2024 开发者大会&#xff0c;谷歌用一场…

Vue 之 后台管理系统的权限路由的管理

目录 前言实现理解三者的概念以及之间的关联账号&#xff08;用户&#xff09;角色菜单 用户权限授权相关概念实现代码实现登录跳转路由&#xff0c;路由守卫中进行权限验证按钮权限封装指令&#xff1a;调用&#xff08;其中一个页面参考&#xff09; 思路&#xff0c;操作流程…

数学:矩阵范数的定义、常见的矩阵范数

1 算子范数【从属范数】 1.1 1-算子范数【列和范数】 &#xff1a;即对A的每列的绝对值求和再求其中的最大值 1.2 ∞-算子范数【行和范数】即对 A 的每行的绝对值求和再求其中的最大值 1.3 2-算子范数【谱范数】 学过奇异值分解就知道谱范数是最大奇异值/ 二次型的最大特…

大数据Spark教程从入门到精通第四篇:Spark快速上手

一&#xff1a;Spark快速上手 1&#xff1a;创建Maven项目 idea安装scala_idea scala插件-CSDN博客 代表了我们安装scala的maven环境已经准备好了&#xff0c;代码可以正常跑了

Git使用(4):分支管理

一、新建分支 首先选择Git -> Branches... 然后选择 New Branch&#xff0c;输入新分支名称&#xff0c;例如dev。 可以看到右下角显示已经切换到新建的dev分支了。 push到远程仓库&#xff0c;可以看到新添加的分支。 二、切换分支与合并分支 为了演示合并分支&#xff0c…

【MySQL数据库开发设计规范】之SQL使用规范

欢迎点开这篇文章&#xff0c;自我介绍一下哈&#xff0c;本人姑苏老陈 &#xff0c;是一名JAVA开发老兵。 本文收录于 《MySQL数据库开发设计规范》专栏中&#xff0c;该专栏主要分享一些关于MySQL数据库开发设计相关的技术规范文章&#xff0c;定期更新&#xff0c;欢迎关注&…

Shell之高效文本处理命令

目录 一、排序命令—sort 基本语法 常用选项 二、去重命令—uniq 基本语法 常用选项 三、替换命令—tr 基本语法&#xff1a; 常用选项 四、裁剪命令—cut 基本语法&#xff1a; 常用选项 字符串分片 五、拆分命令—split 基本语法&#xff1a; 六、 文件…

NVM安装及VUE创建项目的N种方式

VUE 参考官网&#xff1a;https://cli.vuejs.org/zh/guide/ 目录 NVM安装 1.卸载node.js 2.安装nvm ​编辑​ 3.配置 4.使用nvm安装node.js 5.nvm常用命令 创建VUE项目 1.使用vue init 创建vue2&#xff08;不推荐&#xff09; 2.使用vue create创建vue2和3&#xff…

TINA 使用教程

常用功能 分析-电气规则检查&#xff1a;短路&#xff0c;断路等分析- 直流分析 交流分析 瞬态分析 视图-分离曲线 由于输出的容性负载导致的振荡 增加5欧电阻后OK 横扫参数 添加横扫曲线的电阻&#xff0c;选择R3&#xff1a;8K-20K PWL和WAV文件的支持 示例一&#xff1a;…