首页 > 编程语言 >数据结构与算法分析实验7 构造哈夫曼树和生成哈夫曼编码

数据结构与算法分析实验7 构造哈夫曼树和生成哈夫曼编码

时间:2024-07-14 11:55:18浏览次数:15  
标签:编码 code 哈夫曼 ++ treeinfo 数据结构 Buff

文章目录

1.上机名称

构造哈夫曼树和生成哈夫曼编码

2.上机要求

(1) 输入一段文本,统计文本中每个字符出现的频率
(2) 对每个字符构造哈夫曼树,输出对应的哈夫曼编码
(3) 输出文本对应的编码
(4) 对编码进行译码,输出译码后的文本,并判断是否和输入文本一致
注:对实验内容进行了扩充,要求:
(5) 从文本读取数据,输出字符数量和对应编码,将英文字符文件编码输出到另一个文件
(6) 将编码出来的文件译码回原文件

3.上机环境

visual studio 2022 Windows11 家庭版 64位操作系统

4.程序清单(写明运行结果及结果分析)

4.1 程序清单

4.1.1 head.h 头文件内容如下:

#pragma once
#include<iostream>
#include<string>
using namespace std;
#define Buff_Size 1024					//定义从文件一次读取的字符串大小
#define Buff_size 64
#define Tree_Size 64
#define InputFile_Name "test1.txt"		//输入的原始文件名,可更改
#define OutputCode_Name "OutCode.txt"	//输出编码的文件名,可更改
#define OutputFile_Name "OutText.txt"	//通过编码文件输出的文件名,可更改

typedef struct Tnode {	//哈夫曼树的节点
	int count;				//用次数代替频率
	char data;				//存放的字符内容
	struct Tnode* parent;	//双亲
	struct Tnode* lchild;	//左子树
	struct Tnode* rchild;	//右子树
	string code;			//保存的编码
}Node, * pNode;

typedef struct CodeRecorder {	//编码表
	char data;
	string code;
}Cd, * pCd;

class hfmtree {
public:
	hfmtree();					//构造对象
	~hfmtree();					//析构函数
	void Analyse();				//统计字符
	void maketree();			//生成哈夫曼树
	void encode();				//内部生成编码
	pCd outcode();				//导出编码表
	void outputcode(pCd cd);	//通过给定的编码表,输出编码后的文件
	void outputfile(pCd cd);	//将编码文件转为字符文件输出
private:
	//所有树的节点,原本只需要26个英文字母和6个符号,
	//包括 , . : ? 引号(作为一类了)空格的存储空间,
	//但需要预留空节点去作为连接节点
	//粗略估计需要64个节点
	Node treeinfo[Tree_Size];

	//显示目前已用的空间,便于资源调度:
	int usedlen;

	//经过处理得到的哈夫曼树的根
	Node root;

	//交换数据的缓冲区,一大一小:
	char Buff[Buff_Size];
	char buff[Buff_size];
};

4.1.2 head.cpp 实现文件内容如下:

#include "head.h"

hfmtree::hfmtree(){//创建
	for (int i = 0; i < Tree_Size; i++) {
		treeinfo[i].count = 0;
		treeinfo[i].data = '#';
		treeinfo[i].lchild = nullptr;
		treeinfo[i].rchild = nullptr;
		treeinfo[i].parent = nullptr;
		treeinfo[i].code = "";	//空串
	}
	for (int i = 0; i < 26; i++) {
		treeinfo[i].data = i + 'a';
	}
	treeinfo[26].data = ' ';	//空格
	treeinfo[27].data = ',';	//逗号
	treeinfo[28].data = '.';	//句号
	treeinfo[29].data = '?';	//问号
	treeinfo[30].data = '!';	//感叹
	treeinfo[31].data = '\"';	//引号
	usedlen = 32;
}

hfmtree::~hfmtree(){
}

void hfmtree::Analyse(){
	FILE* fp;
	fopen_s(&fp, InputFile_Name, "rb");	
	fseek(fp, 0, SEEK_SET);
	int i = 0;
	while (!feof(fp)) {
		memset(Buff, 0, Buff_Size);
		if (fread(&Buff[0], 1, Buff_Size, fp) == 0) exit(10);
		while (i < Buff_Size)
		{
			if ('a' <= Buff[i] && Buff[i] <= 'z') {
				++treeinfo[Buff[i] - 'a'].count;
			}
			else if ('A' <= Buff[i] && Buff[i] <= 'Z') {
				++treeinfo[Buff[i] - 'A'].count;
			}
			else {
				switch (Buff[i]){
				case ' ':	++treeinfo[26].count; break;
				case ',':	++treeinfo[27].count; break;
				case '.':	++treeinfo[28].count; break;
				case '?':	++treeinfo[29].count; break;
				case '!':	++treeinfo[30].count; break;
				case '\'':	 			  
				case '\"':	++treeinfo[31].count; break;
				default: break;
				}
			}
			i++;
		}
		i = 0;
	}
	fclose(fp);
	for (int i = 0; i < usedlen;i++) {
		cout <<treeinfo[i].data << ":" << treeinfo[i].count<<"  ";
		
	}
	//system("pause");
}

void hfmtree::maketree(){
	int cnt_frst_num;
	while (1) {
		//计算森林里树的个数,如果只剩一棵树,说明构造完成,退出循环:

		cnt_frst_num = 0;//代表树的数量 count forest's number
		for (int i = 0; i < usedlen; i++) { 
			if (treeinfo[i].parent == nullptr) {	//没有双亲的就是树根
				cnt_frst_num++;						//有一个树根就有一棵树
			}
		}
		if (cnt_frst_num == 1) break;				//如果只有一颗树退出while

		//在有多棵树的情况下:
		//准备一个新节点:
		pNode nodenew = &treeinfo[usedlen];
		//找到第一个有效节点,将它作为基准值:
		int first_id = 0;
		for (int i = 0; i < Tree_Size; i++) {
			if (treeinfo[i].parent == nullptr && treeinfo[i].data != '#') {
				first_id = i;
				break;
			}
		}
		for (int i = 0; i < Tree_Size; i++) {
			if (treeinfo[i].parent == nullptr && treeinfo[i].data != '#') {
				first_id = treeinfo[first_id].count <= treeinfo[i].count ? first_id : i;
			}
		}
		pNode node1 = &treeinfo[first_id];//拿到count最小的有效节点

		//下面拿到count第二小的有效节点
		int second_id = 0;
		for (int i = 0; i < Tree_Size; i++) {
			if (treeinfo[i].parent == nullptr && treeinfo[i].data != '#' && i != first_id) {
				second_id = i;
				break;
			}
		}
		for (int i = 0; i < Tree_Size; i++) {
			if (treeinfo[i].parent == nullptr && treeinfo[i].data != '#' && i != first_id) {
				second_id = treeinfo[second_id].count <= treeinfo[i].count ? second_id : i;
			}
		}
		pNode node2 = &treeinfo[second_id];//拿到count第二小的有效节点

		//下面将两个节点连接到双亲节点上
		node1->parent = nodenew;
		node2->parent = nodenew;
		nodenew->lchild = node1;
		nodenew->rchild = node2;
		//使nodenew在下次循环中有效
		nodenew->data = '^';
		nodenew->count = node1->count + node2->count;
		usedlen++;
	}
	cout <<"Total char:"<< treeinfo[usedlen - 1].count << endl;  //在以上操作中,treeinfo[usedlen - 1]为哈夫曼树树根
}

void hfmtree::encode() {
	for (int i = 0; i <= usedlen; i++) {
		for (int j = 0; j <= usedlen; j++) {
			if (treeinfo[j].lchild != nullptr)treeinfo[j].lchild->code = treeinfo[j].code + "0";
			if (treeinfo[j].rchild != nullptr)treeinfo[j].rchild->code = treeinfo[j].code + "1";
		}
	}
}

pCd hfmtree::outcode(){
	pCd code = new Cd[32];
	for (int i = 0; i < 32; i++) {
		code[i].data = treeinfo[i].data;
		code[i].code = treeinfo[i].code;
	}
	return code;
}

void hfmtree::outputcode(pCd cd){
	string Buffstring;
	FILE* ifp,*ofp;
	fopen_s(&ifp, InputFile_Name, "rb");
	fopen_s(&ofp, OutputCode_Name, "w");
	while (!feof(ifp)) {
		memset(buff, 0, Buff_size);
		Buffstring.clear();
		fread(buff, 1, Buff_size, ifp);
		for (auto v : buff) {
			if ('a' <= v && v <= 'z') {
				Buffstring += cd[v - 'a'].code; 
			}
			else if ('A' <= v && v <= 'Z') {
				Buffstring += cd[v - 'A'].code; 
			}
			else switch (v) {
			case ' ':	Buffstring += cd[26].code;  break;
			case ',':	Buffstring += cd[27].code;  break;
			case '.':	Buffstring += cd[28].code;  break;
			case '?':	Buffstring += cd[29].code;  break;
			case '!':	Buffstring += cd[30].code;  break;
			case '\'':							  
			case '\"':	Buffstring += cd[31].code;  break;
			default:   break;
			}
		}
		//cout << Buffstring << endl;
		fwrite(Buffstring.c_str(), 1, Buffstring.length(), ofp);
		//fputc('\n', ofp);
	}
	fclose(ifp);
	fclose(ofp);
}

void hfmtree::outputfile(pCd cd){
	string Buffstring = "";
	string cmpstring = "";
	FILE* ifp, * ofp;
	fopen_s(&ifp, OutputCode_Name, "r");
	fopen_s(&ofp, OutputFile_Name, "w"); 
	while (!feof(ifp)) {
		Buffstring.clear();
		memset(Buff, 0, Buff_Size);
		fread(Buff, 1, Buff_Size, ifp);
		//cout << "read:" << Buff << endl;
		for (int i = 0; i < Buff_Size; i++) {
			cmpstring += Buff[i];
			for (int j = 0; j < 32;j++) {
				if (cmpstring.compare(cd[j].code) == 0) {
					//cout << "compare:" << cmpstring << endl;
					Buffstring += cd[j].data;
					cmpstring.clear();
				}
			}
		}
		//cout << "write:"<< Buffstring << endl;
		fwrite(Buffstring.c_str(),Buffstring.length(), 1 , ofp);
	}
	fclose(ifp);
	fclose(ofp);
}

4.1.3 源文件main.cpp 内容如下:

#include"head.h"
int main() {
	hfmtree tree;
	tree.Analyse();
	tree.maketree();
	tree.encode();
	pCd cd = tree.outcode();
	//print code_recorder:
	for (int i = 0; i < 32; i++) {
		cout << cd[i].data << ':' << cd[i].code << endl;
	}
	tree.outputcode(cd);
	tree.outputfile(cd);
} 

4.2 程序运行结果

注:在程序目录下放了一本英文小说test1.txt,作为数据的输入,正文开头内容如下:
在这里插入图片描述选中所有文本,发现有28万左右的字:
在这里插入图片描述
启动程序运行,命令行提示字符统计数(仅统计设计好的字符)以及对应编码信息,发现空格和元音字母在英语文本中出现的频率最高,在哈夫曼编码中对应字符编码也最短,j q z出现的频率最低,在哈夫曼编码中对应字符编码也最长,符合实验正确现象:
在这里插入图片描述
打开目录,发现在程序目录下生成了另外两个文件,即用编码输出的文件和译码回去的文件,点击打开,进行对照,发现编码能够唯一确定字符,而文本由于没有设置处理格式、数字和特殊字符,导致只能将纯英文内容进行输出,部分不支持处理的字符丢失导致字数减少,只剩27万左右:
在这里插入图片描述

5.上机体会

哈夫曼树和哈夫曼编码是数据结构中非常重要的概念和算法,通过实验来加深对它们的理解是非常有意义的。以下是我在进行构造哈夫曼树和生成哈夫曼编码实验后得到的一些心得:

  1. 理解哈夫曼树和哈夫曼编码的原理和应用场景非常重要。哈夫曼树是一种最优二叉树,可以用于压缩数据,减少存储空间。哈夫曼编码是基于哈夫曼树的一种编码方式,可以用于数据的传输和存储,减少带宽和存储空间的占用,在本次实验中,较之Unicode编码,压缩率就可以达到25.7%。
  2. 在实验中,需要掌握哈夫曼树的构造算法和哈夫曼编码的生成算法。哈夫曼树的构造算法包括选择最小权值的两个节点、合并节点、更新权值等步骤,需要注意每一步的细节和实现方式。哈夫曼编码的生成算法需要根据哈夫曼树的结构,对每个节点进行编码,得到一个二进制编码字符串,本次实验中没有用到容器进行自下而上的栈式实现编码,而是通过空间换时间,利用函数递归完成对每个节点的编码。
  3. 通过实验,可以深入理解哈夫曼树和哈夫曼编码的性能和优缺点。哈夫曼树和哈夫曼编码可以有效地减少数据的存储空间和传输带宽,但也存在一些局限性,例如对于非等概率分布的数据,压缩效果可能不佳。因此,在实际应用中需要根据具体情况选择合适的压缩算法和编码方式。
  4. 最后,通过实验还可以提高自己的编程能力和数据结构的应用能力。实验需要编写复杂的算法和代码,需要运用数据结构的知识和技能,同时也需要具备良好的编程习惯和调试能力。通过实验,可以不断提高自己的编程水平和解决问题的能力。

标签:编码,code,哈夫曼,++,treeinfo,数据结构,Buff
From: https://blog.csdn.net/2301_81290340/article/details/140414687

相关文章

  • 旷野之间19 - Nvidia 首席执行官建议不要学习编码
    50年前出现的许多技术都遵循了两种轨迹之一:它们要么发展以跟上现代的步伐,要么消失得无影无踪。一个例子是1938年推出的第一台可编程机械计算机。由于内存限制,它的操作能力有限,而且重量很重,很难想象今天在我们的家中或工作场所放置这样的设备。确实,有许多技术远见者对计算......
  • 可持久化数据结构
    P4735转化到区间求\(\text{xor}\x\)后的最大值,用Trie。那么需要知道区间是否有在Trie树某个子树内的节点,用可持久Trie,或者离线扫右端点并记录左端点时间戳即可。第二个做法可以不离线,同样使用可持久Trie,但是求区间时不使用减法,而是只使用插入前\(r\)个数的Trie,通过......
  • 数据结构第25节 深度优先搜索
    深度优先搜索(Depth-FirstSearch,简称DFS)是一种用于遍历或搜索树或图的算法。DFS从根节点开始,尽可能深的搜索树的分支,当节点v的所在边都己被探寻过,搜索将回溯到上一个节点w,然后探索w的其他未搜索过的子节点。DFS有两种常用的方式:递归方式和非递归方式(通常使用栈来实......
  • 哈夫曼编码
    目的、效果:不出现歧义的情况下编码最短方式:从信息的概率出发,最小概率的两两合并,合并之后的值和剩下的信息再次概率最小匹配和两两合并,最后得到一个二叉树,左路径为0,右路径为1,从根节点到叶子节点的路径编码就是该信息的编码。参考链接1参考链接2 ......
  • 【数据结构与算法】详解二叉树下:实践篇————通过链式结构深入理解并实现二叉树
          ......
  • 数据结构题目:几种典型排序算法的实现
    1、实验目的实现几种典型的排序算法2、实验具体要求分别利用直接插入/折半插入/希尔/冒泡/快速/简单选择排序算法实现将待排序序列{26,6,18,8,36,66,56,76,99}由小到大排序,并输出结果。3、实验设计思路(编程语言、模块划分及函数功能描述等)模块划分及函数功能描述:主函数模......
  • 数据结构,(动态)顺序表,C语言实现
    ——如果代码存在问题,请务必评论告诉我,感激不尽(#^.^#)——动态和静态的顺序表差别主要在于开辟内存的方式,动态顺序表中的数据所在内存是通过malloc函数实现的,这也意味着,动态顺序表可以更改存储数据的内存大小,其他的话基本没什么差别1.数据类型定义 structElemType想要建......
  • 数据结构与算法学习day4之单向链表
    1.单向链表的的定义链表是有序的列表,这是它在内存中的存储,如下图所示:链表是以节点的形式存储的,是链式存储每个节点都包含两个部分,一个是data域,一个是next域指向下一个节点每个节点不一定是连续存储链表分为带头节点和不带头节点2.单向链表的实现思路(1)添加添加节点的......
  • 数据结构(队列的实现)
    目录队列队列的定义队列的实现创建队列的结构队列的初始化进行插入数据删除数据计算个数 判断是否为空返回队列头部数据返回队列尾部数据队列队列的定义队列是:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出FIFO(First......
  • 第八篇:Python集合:高效的无序集数据结构
    1.集合的定义Python中的集合(set)是一种高度优化的无序且不重复的数据结构。它在概念上类似于数学中的集合,能够存储多个不同的元素。集合的这种特性使其成为处理唯一性和成员资格检查的理想选择。在Python中,我们可以通过两种主要方式定义集合:a)使用花括号{}:set1={1,......