哈夫曼编码的应用

news2024/12/22 20:24:45

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

文章目录

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

要求

  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;
		else
			p = p->rchild;
		if (p->data)
		{
			ofs << p->data;
			p = root;
		}
	}
	ifs.close();
	ofs.close();
}

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

与原文完全一致。

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

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

相关文章

公域流量如何引流到私域流量?

公域流量和私域流量是数字营销中常用的两种流量类型。公域流量指的是通过搜索引擎、社交媒体等公共平台获取的流量&#xff0c;而私域流量则是指企业自身拥有的用户群体和数据。那么&#xff0c;如何将公域流量引流到私域流量呢&#xff1f;下面我将为您详细解答。 1、提供有价…

开源社区社群兴趣搭子圈子,系统开发新论坛交流兴趣爱好圈子论坛可搭建本地同城社群圈子社区圈子同城找搭子圈子,包含小程序+公众号H5+安卓苹果app,源码交付!

优势 1、长时间的陪玩APP源码开发经验&#xff0c;始终坚持从客户的实际需求出发 2、提供安全的陪玩系统源码开发解决方案 3、需求定制不走弯路&#xff0c;源码交付&#xff0c;可二开 4、追求精细化服务&#xff0c;力求做好每一个陪玩系统源码开发环节搭建流程支持 PC 端…

多步预测系列 | LSTM、CNN、Transformer、TCN、串行、并行模型集合

● 环境框架&#xff1a;python 3.9 pytorch 1.8 及其以上版本均可运行 ● 使用对象&#xff1a;论文需求、毕业设计需求者 ● 代码保证&#xff1a;代码注释详细、即拿即可跑通。 往期精彩内容&#xff1a; 时序预测&#xff1a;LSTM、ARIMA、Holt-Winters、SARIMA模型的分…

MySQL 进阶使用【函数、索引、视图、存储过程、存储函数、触发器】

前言 做数仓开发离不开 SQL &#xff0c;写了很多 HQL 回头再看 MySQL 才发现&#xff0c;很多东西并不是 HQL 所独创的&#xff0c;而是几乎都来自于关系型数据库通用的 SQL&#xff1b;想到以后需要每天和数仓打交道&#xff0c;那么不管是 MySQL 还是 Oracle &#xff0c;都…

部署YUM仓库及 NFS共享服务

YUM仓库服务 部署YUM软件仓库 使用YUM工具管理软件包 一、YUM概述 1.YUM (Yellow dog Updater Modified) 基于RPM包构建的软件更新机制可以自动解决依赖关系所有软件包由集中的YUM软件仓库提供 2. 准备安装源3-1 2.1 软件仓库的提供方式 FTP服务:ftp://..HTTP服务:htt…

线性回归学习笔记

学习了王天一博士的机器学习40讲&#xff0c;做个小总结&#xff1a; 1、机器学习中&#xff0c;回归问题隐含了输入变量和输出变量均可连续取值的前提。 2、单变量线性回归&#xff0c;所有样本到直线的欧氏距离之和最小&#xff0c;即均方误差最小化。 3、最小二乘法的几何意…

OceanBase集群如何进行OCP的替换

有OceanBase社区版的用户提出替换 OCP 管控平台的需求。举例来说&#xff0c;之前的OCP平台采用单节点&#xff0c;然而随着OceanBase集群的陆续上线和数量的不断增多&#xff0c;担心单节点的OCP可能面临故障风险&#xff0c;而丧失对OceanBase集群的管控能力。另此外&#xf…

Leetcode - 周赛397

目录 一&#xff0c;3146. 两个字符串的排列差 二&#xff0c;3147. 从魔法师身上吸取的最大能量 三&#xff0c;3148. 矩阵中的最大得分 四&#xff0c;3149. 找出分数最低的排列 一&#xff0c;3146. 两个字符串的排列差 本题就是求同一个字符在两个字符串中的下标之差的…

网页版Figma汉化

最近学习Figma&#xff0c;简单介绍一下网页版Figma的汉化方法 1.打开网址&#xff1a;Figma软件汉化-Figma中文版下载-Figma中文社区 2.下载汉化插件离线包 解压汉化包 3.点开谷歌的管理扩展程序 4.点击加载已解压的扩展程序&#xff0c;选择刚刚解压的包 这样就安装好了汉化…

stm32ADC注入通道使用笔记(以STM32F407 为例)

ADC_JDR1 存放的是第一次转换的数据 ADC_JDR2 存放的是第二次转换的数据 ADC_JDR3 存放的是第三次转换的数据 ADC_JDR4 存放的是第四次转换的数据 1.当 JL 0&#xff08;定序器中有 1 次注入转换&#xff09;时&#xff0c;ADC 将仅转换 JSQ4[4:0] 通道。值存入ADC_JDR1中…

dfs记忆化搜索,动态规划

动态规划概念&#xff1a; 给定一个问题&#xff0c;将其拆成一个个子问题&#xff0c;直到子问题可以直接解决。然后把子问题的答案保存起来&#xff0c;以减少重复计算。再根据子问题的答案反推&#xff0c;得出原问题解。 821 运行时间长的原因&#xff1a; 重复大量计算…

IT革新狂潮:引领未来的技术趋势

方向一&#xff1a;技术革新与行业应用 当前现状&#xff1a; 量子计算&#xff1a;量子计算的研究正在加速&#xff0c;尽管目前仍处于初级阶段&#xff0c;但其在药物研发、加密技术和材料科学等领域的应用潜力已被广泛认可。 虚拟现实&#xff08;VR&#xff09;与增强现实…

算法学习笔记(5.0)-基于比较的高效排序算法-归并排序

##时间复杂度O(nlogn) 目录 ##时间复杂度O(nlogn) ##递归实现归并排序 ##原理 ##图例 ##代码实现 ##非递归实现归并排序 ##释 #代码实现 ##递归实现归并排序 ##原理 是一种基于分治策略的基础排序算法。 1.划分阶段&#xff1a;通过不断递归地将数组从中点处分开&…

python之pyQt5实例:树莓派+MPU6050采集数据

1、安装必要的软件包&#xff1a; sudo apt-get update sudo apt-get install python3-smbus python3-dev i2c-tools sudo apt-get install python3-smbus 2、确认I2C接口已经启用&#xff1a; 运行 sudo raspi-config 命令打开Raspberry Pi配置工具。 在菜单中选择 "…

ThreadLocal,一次到位

一、定义 ThreadLocal是线程私有变量&#xff0c;用于保存每个线程的私有数据。 那么什么情况下需要进行线程隔离 二、源码分析 public class ThreadLocalTest01 {ThreadLocal<Integer> t new ThreadLocal<>();public void test() {t.set(1);Integer integer…

如果你还不了解双亲委派模型,来看看这篇吧

文章首发于【Java天堂】&#xff0c;跟随我探索Java进阶之路&#xff01; 类与类加载器 类是由它的类加载器加载进虚拟机中的&#xff0c;在同一个Java虚拟机中&#xff0c;对于同一个Class文件&#xff0c;如果采用不同的类加载器&#xff0c;得到的是不相等的类&#xff0c;…

k8s二进制部署--多master、负载均衡、高可用

目录 1、环境准备 1.1 服务器配置 1.2 master02 节点部署 2、负载均衡部署 2.1 下载nginx 2.2 修改nginx配置文件 2.3 启动nginx 2.3.1 检查配置文件语法 2.3.2 启动nginx服务&#xff0c;查看已监听6443端口 3. 部署keepalived服务(nginx主机&#xff0c;以nginx01为…

十一.吊打面试官系列-JVM优化-深入JVM类加载机制

前言 从本篇文章开始我们来探讨JVM相关的知识&#xff0c;内容附带JVM的启动&#xff0c;JVM内存模型&#xff0c;JVM垃圾回收机制&#xff0c;JVM参数调优等&#xff0c;跟着文章一步一步走相信你对JVM会有一个不一样的认识&#xff0c;如果觉得文章对你有所帮助请给个好评吧…

基于Java+SpringBoot+Mybaties-plus+Vue+elememt 驾校管理系统 设计与实现

一.项目介绍 系统角色&#xff1a;管理员、驾校教练、学员 管理员&#xff1a; 个人中心&#xff1a;修改密码以及个人信息修改 学员管理&#xff1a;维护学员信息&#xff0c;维护学员成绩信息 驾校教练管理&#xff1a;驾校教练信息的维护 驾校车辆管理&…

水离子雾化壁炉与会所房间的氛围搭配

水离子雾化壁炉在会所房间的氛围搭配可以为房间增添舒适、温馨和现代感&#xff0c;以下是一些建议&#xff1a; 主题定位&#xff1a; 根据会所房间的主题和定位选择合适的水离子雾化壁炉款式和设计风格。可以是现代简约、欧式古典或是豪华奢华&#xff0c;确保与房间整体风格…