数据结构实验二(赫夫曼编码及应用)

参考的大佬的文章改了一些,但是程序还是有bug,经常会乱码,可能在将C++程序换成C程序时某些判断的阈值设置的有些问题,导致乱码的存在,不过我测试的这两篇中文和英文是调好的,目前不会乱码,应付一下实验报告就足够了

因为整个程序是以\n作为换行符,所以对程序中生成和使用的各类文件有硬性要求,必须是Unix (LF)格式的,不然必乱码

数据结构实验二(赫夫曼编码及应用)_第1张图片

实验目的

掌握赫夫曼树和赫夫曼编码的基本思想和算法的程序实现

实验环境及基础知识

实验环境:

Windows10操作系统、C99标准、CLion 2023.1.3

基础知识:

  1. 赫夫曼树和赫夫曼编码基本思想和构建方法;

  2. 线性表数据的排序、插入、删除、修改、查找;

  3. C语言文件的建立、读取、写入方法;

  4. C语言位运算相关方法等;

  5. C语言程序设计方法;

实验内容及要求

实验内容:

实现文件中数据的加解密与压缩:将硬盘上的一个文本文件进行加密,比较加密文件和原始文件的大小差别;对加密文件进行解密,比较原始文件和解码文件的内容是否一致。

实验要求:

  1. 提取原始文件中的数据(包括中文、英文或其他字符),根据数据出现的频率为权重,构建Huffman编码表;

  2. 根据Huffman编码表对原始文件进行加密,得到加密文件并保存到硬盘上;

  3. 将加密文件进行解密,得到解码文件并保存点硬盘上;

  4. 比对原始文件和解码文件的一致性,得出是否一致的结论。

    参考类型定义://双亲孩子表示法

typedef struct {
	unsigned int weight;
	unsigned int parent, lchild, rchild;
} HTNode, \*HuffmanTree;   //动态分配数组存储赫夫曼树

typedef char \* \* HuffmanCode; //动态分配数组存储赫夫曼编码表

需求分析及功能描述

需求分析:

1.用户界面需求:

用户需要一个用户界面来操作程序,包括选择输入文件、执行加密和解密操作以及查看结果。界面应该清晰明了,提供必要的输入和输出字段,以便用户能够轻松理解和操作程序。

2.输入文件处理需求:

用户需要能够选择一个文本文件作为输入,该文件包含要进行加密和解密的数据。程序需要能够读取并处理各种字符(包括中文、英文或其他字符)。

3.赫夫曼编码需求:

程序需要根据输入文件中字符的频率构建赫夫曼树。需要能够通过赫夫曼树生成赫夫曼编码表,用于加密和解密过程中的字符替换。

4.文件加密需求:

程序需要将输入文件中的字符根据赫夫曼编码表进行加密,将字符替换为对应的赫夫曼编码。加密后的数据应保存到硬盘上的一个文件中,供解密使用。

5.文件解密需求:

程序需要能够读取加密文件,并根据赫夫曼编码表将赫夫曼编码还原为原始字符。解密后的数据应保存到硬盘上的一个文件中,供比对原始文件和解码文件的内容是否一致。

6.文件内容比对需求:

程序需要能够比较原始文件和解码文件的内容,以确定解码是否正确。比对结果应该能够清楚地指示原始文件和解码文件的一致性。

7.文件保存需求

加密文件和解码文件应该保存到硬盘上,以便用户进一步查看和分析。用户应能够选择保存文件的路径和文件名。

8.错误处理需求:

程序需要能够处理各种可能的错误情况,如无效的输入文件、文件读取错误、编码表生成错误等。需要向用户提供相应的错误提示,并在可能的情况下进行恢复或修复。

功能描述

1.用户界面功能:

提供选择输入文件的功能,用户可以通过界面选择一个文本文件作为输入。显示加密和解密操作的按钮,用户可以点击执行相应的操作。提供结果显示区域,用于显示加密文件、解码文件以及比对结果。

2.输入文件处理功能:

读取用户选择的输入文件,并将文件内容存储在内存中供后续处理。处理输入文件中的各种字符,包括中文、英文或其他字符。

3.赫夫曼编码功能:

统计输入文件中字符的频率,并根据频率构建赫夫曼树。根据赫夫曼树生成赫夫曼编码表,将每个字符映射到对应的赫夫曼编码。

4.文件加密功能:

遍历输入文件的每个字符,根据赫夫曼编码表将字符替换为对应的赫夫曼编码。将加密后的数据写入一个新的文件,用于保存加密结果。

5.文件解密功能:

读取用户选择的加密文件,根据赫夫曼编码表将赫夫曼编码还原为原始字符。将解密后的数据写入一个新的文件,用于保存解码结果。

6.文件内容比对功能:

比较原始文件和解码文件的内容,逐个字符进行比对。在比对过程中记录不一致的字符,并统计一致性的比例。在界面上显示比对结果,指示原始文件和解码文件的一致性。

7.文件保存功能

提供保存加密文件和解码文件的功能,用户可以选择保存的路径和文件名。将加密文件和解码文件保存到指定的位置。

8.错误处理功能:

检测并处理各种可能的错误情况,如无效的输入文件、文件读取错误、编码表生成错误等。在界面上显示相应的错误提示信息。

系统总体设计

界面显示

在控制台显示系统的操作界面,提供用户友好的交互方式。用户可以看到文件处理系统的标题,并提示选择功能编号。用户需要输入对应的功能编号来选择要执行的操作。系统还提供了一个退出选项,用户可以选择退出系统。用户可以根据界面的提示进行输入,并通过按下回车键确认选择。

主要模块

文件读写模块:负责读取和写入文件数据。

数据处理模块:包括将字节数据转换为16进制数据、统计16进制数据出现次数、创建哈夫曼树、生成哈夫曼编码、创建搜索表等功能。

压缩模块:根据生成的哈夫曼编码和搜索表,将源文件数据进行压缩,并写入压缩文件。

解压模块:读取压缩文件中的数据,根据搜索表进行解压,并写入解压文件。

文件比较模块:比较源文件和解压文件的内容是否相同。

系统执行流程

用户选择功能编号,根据用户输入执行相应的功能。

1.压缩文件功能

用户输入要压缩的文件路径。

打开源文件和计数文件,将字节数据转换为16进制数据并统计出现次数,将统计结果写入计数文件。

创建哈夫曼树和哈夫曼编码。

创建搜索表,并将哈夫曼编码写入哈夫曼编码文件。

读取源文件数据,根据搜索表将数据进行压缩,并将压缩后的数据写入压缩文件。

执行解压文件功能。

执行文件比较功能。

2.解压文件功能

用户输入要解压的文件路径。

读取压缩文件中的数据和哈夫曼编码文件,创建搜索表。

读取压缩文件数据,根据搜索表进行解压,并将解压后的数据写入解压文件。

执行文件比较功能。

3.文件比较功能

读取源文件和解压文件的内容,并逐个比较字符。如果存在不同的字符,输出不相同位置的行号和列号。如果两个文件内容完全相同,输出相同提示。

流程图分析

数据结构实验二(赫夫曼编码及应用)_第2张图片

数据结构实验二(赫夫曼编码及应用)_第3张图片

数据结构实验二(赫夫曼编码及应用)_第4张图片

系统详细设计

主函数设计

1.显示用户操作界面,根据用户输入的功能编号,调用对应的功能函数。

2.将用户输入功能编号,选择调用对应功能的代码放到一个while死循环中,只要用户不输入退出系统的代码,就可以一直执行这个系统。

数据结构实验二(赫夫曼编码及应用)_第5张图片

主要功能函数设计

1.字符频次统计函数void ReadByte_WriteHex()

以二进制只读方式读取SourceFile源文件文件,并将文件中的每个字节转换为十六进制表示,并统计每个十六进制值的出现次数,然后将结果写入CountFile文件中。

数据结构实验二(赫夫曼编码及应用)_第6张图片

2.创建赫夫曼树函数HTree CreateHTree(int *N)

基于给定的频次数据,创建一个哈夫曼树。具体功能的详细描述如下:

提取非零权值:根据全局变量 HexDataTimes中存储的频次数据,遍历所有可能的十六进制值(0x00 到0xFF),将非零权值的频次和对应的十六进制值保存在 weight 和 weight_Hex数组中。这样做是为了构建哈夫曼树时只考虑非零频次的节点。

确定叶子节点个数:将非零权值的个数保存在变量 n 中,并通过指针参数 N 返回该值。

计算哈夫曼树节点总数:根据叶子节点个数 n,计算哈夫曼树的总节点数m。每个叶子节点对应一个非零权值。

分配哈夫曼树内存空间:使用 malloc 函数分配 m+1 个 HTNode结构体的内存空间,保存哈夫曼树的节点。

初始化叶子节点:将前 n个节点作为叶子节点,并将对应的权值、父节点、左子节点和右子节点初始化为适当的值。

初始化非叶子节点:对于第 n+1 到第 m个节点,将它们的权值、父节点、左子节点和右子节点初始化为适当的值。

构建哈夫曼树:通过循环从 n+1 到 m的节点,选择两个权值最小的节点作为它们的父节点,并更新父节点的权值为两个子节点的权值之和。这个过程持续到只剩下一个根节点,即HT[m]。

返回哈夫曼树:将构建好的哈夫曼树 HT 返回。

数据结构实验二(赫夫曼编码及应用)_第7张图片

3.创建赫夫曼编码函数HuffmanCode HuffmanCoding(HTree HT, int n)

根据给定的哈夫曼树,生成对应的哈夫曼编码。具体功能的详细描述如下:

分配内存空间:使用 malloc 函数分配 n+1个指针大小的内存空间,用于存储每个字符的哈夫曼编码。同时,使用 malloc 函数分配 n个字符大小的内存空间,作为临时存储每个字符编码的数组 Each_Code。

设置编码结束符:将 Each_Code 数组的最后一个元素设置为 ‘\0’,作为编码的结束符。

生成哈夫曼编码:通过遍历每个字符的节点,从叶子节点向上遍历到根节点,确定每个字符的哈夫曼编码。从当前节点开始,向上追溯父节点,根据左子节点和右子节点的关系,确定当前节点的编码位('0’或 ‘1’),并将编码保存在 Each_Code 数组中。

存储哈夫曼编码:为每个字符分配内存空间,根据 Each_Code数组中的编码内容,将编码复制到 HC[i] 数组中。HC[i] 存储了第 i个字符的哈夫曼编码。

释放内存空间:释放临时存储编码的 Each_Code 数组的内存空间。

返回哈夫曼编码:将生成好的哈夫曼编码 HC 返回。

数据结构实验二(赫夫曼编码及应用)_第8张图片

4.压缩文件函数void Compress(HuffmanCode SearchList)

根据给定的编码查询表 SearchList,对源文件进行编码压缩。具体功能的详细描述如下:

打开源文件和压缩文件:使用 fopen函数以二进制只读方式打开源文件,以二进制写入方式打开压缩文件。

逐行读取源文件数据:通过循环从源文件中读取 Line 个字符,存储到 OriginCode数组中。每次循环后,OriginCode 存储了一行数据。

进行编码拼接:调用 _01StrCat 函数,将 OriginCode 数组中的字符根据编码查询表SearchList 进行编码拼接,结果存储在 Str01 数组中。Str01是一个用于存储编码的字符串数组。

编码写入压缩文件:调用 TransWrite 函数,将 Str01中的编码写入压缩文件。该函数将编码转换为比特流,并将比特流写入压缩文件。

关闭源文件:处理完成后,关闭源文件。

写入编码查询表:调用 WriteHuffmanCode 函数,将编码查询表 SearchList写入压缩文件对应的编码文件HuffmanCodeFile中。同时,将无法凑足8位比特位的编码以字符串形式写入文件末尾。

数据结构实验二(赫夫曼编码及应用)_第9张图片

5.解压文件函数void DeCompress()

这段代码的功能是进行解压缩操作,将压缩文件恢复为原始文件。具体功能的详细描述如下:

打开编码查询表文件和解压文件:使用 fopen函数以二进制只读方式打开编码查询表文件,以二进制写入方式打开解压文件。解压文件在打开时会被清空。

读取编码查询表:从编码查询表文件中逐行读取数据,将每行数据存储到 SearchList数组中。同时,读取编码查询表文件末尾的不足8位字符串,存储到 surplus 数组中。

处理编码查询表中的 (null):遍历 SearchList 数组,如果某个元素的值为"(null)",则将其截断,即将第一个字符设置为 ‘\0’。

打开压缩文件:使用 fopen 函数以二进制只读方式打开压缩文件。

解压缩:循环读取压缩文件中的数据,将每行数据转换为比特编码形式,并将其连接到Str01 字符串中。

将 Str01 写入解压文件:调用 TransWrite2 函数,将 Str01中的比特编码解析为原始字符,并将字符写入解压文件中。

关闭压缩文件:处理完成后,关闭压缩文件。

凑足8位编码并写入解压文件:将不足8位的编码字符串连接到 Str01中,凑足8位,并将该编码对应的字符写入解压文件中。

写入换行符:在解压文件末尾写入换行符。

数据结构实验二(赫夫曼编码及应用)_第10张图片

6.文件比较函数int Compare(void)

比较两个文件的内容是否相同,默认比较源文件和解压文件的内容。具体功能的详细描述如下:

打开源文件和解压文件:使用 fopen 函数以只读方式打开源文件和解压文件。

逐字符比较两个文件的内容:通过循环从源文件和解压文件中逐个读取字符进行比较。循环条件是源文件和解压文件都没有到达文件末尾。

判断换行符:如果当前字符是换行符’\n’,则表示当前行结束,将对应文件的行号加一,列号重置为1。

比较字符是否相同:如果源文件和解压文件的当前字符不相同,则输出第一次不相同的位置信息(行号和列号),并跳出循环。

判断文件内容是否完全相同:如果一个文件已经到达文件末尾而另一个文件还有字符未读取,则说明其中一个文件的内容被包含在另一个文件中。根据情况输出相应的提示信息。

关闭文件:处理完成后,关闭源文件和解压文件。

返回比较结果:根据比较的结果,返回一个整数值。如果两个文件完全相同,返回1;如果两个文件不相同,返回0。

数据结构实验二(赫夫曼编码及应用)_第11张图片

辅助函数设计

数据结构实验二(赫夫曼编码及应用)_第12张图片

功能展示

控制台页面展示

数据结构实验二(赫夫曼编码及应用)_第13张图片

各功能模块展示

数据结构实验二(赫夫曼编码及应用)_第14张图片

数据结构实验二(赫夫曼编码及应用)_第15张图片

数据结构实验二(赫夫曼编码及应用)_第16张图片

中间产生文件展示

数据结构实验二(赫夫曼编码及应用)_第17张图片

数据结构实验二(赫夫曼编码及应用)_第18张图片

数据结构实验二(赫夫曼编码及应用)_第19张图片

压缩效果展示

中文文件

数据结构实验二(赫夫曼编码及应用)_第20张图片

数据结构实验二(赫夫曼编码及应用)_第21张图片

英文文件

数据结构实验二(赫夫曼编码及应用)_第22张图片

数据结构实验二(赫夫曼编码及应用)_第23张图片

实验总结

在实验中,我们首先定义了赫夫曼树(HuffmanTree)和赫夫曼编码表(HuffmanCode)的数据结构。赫夫曼树用于构建字符频率的最优二叉树,而赫夫曼编码表用于存储每个字符对应的赫夫曼编码。这些数据结构为后续的加密、解密和比对操作提供了基础支持。

首先,我们实现了文件读写模块,用于读取和写入文件数据。通过使用标准库函数,我们能够打开源文件和目标文件,并进行数据的读取和写入操作。这使得我们能够在程序中处理文件数据。

然后,我们实现了数据处理模块,其中包括将字节数据转换为16进制数据、统计16进制数据出现次数、创建赫夫曼树、生成赫夫曼编码、创建搜索表等功能。这些功能通过遍历文件数据、统计频率和构建赫夫曼树来实现。通过赫夫曼编码表,我们能够将字符替换为对应的赫夫曼编码,实现了文件的加密和解密功能。

在压缩模块中,我们使用赫夫曼编码表对源文件进行压缩。遍历源文件中的每个字符,并通过搜索表将其转换为对应的赫夫曼编码。这样,我们可以将字符替换为更短的二进制编码,从而实现文件数据的压缩。压缩后的数据保存在目标文件中。

在解压模块中,我们读取压缩文件和赫夫曼编码表,根据搜索表进行解压。通过将赫夫曼编码转换为对应的字符,我们能够将压缩文件解压为原始文件数据,并将其保存在解压文件中。

最后,我们实现了文件内容比对模块。通过逐个字符比较原始文件和解压文件的内容,我们能够确定解码是否正确。在比对过程中,我们记录不一致的字符,并统计一致性的比例。通过输出比对结果,我们能够清楚地指示原始文件和解压文件的一致性。

在实验中,我们还考虑了错误处理的需求。通过使用条件判断和错误提示,我们能够检测并处理各种可能的错误情况,如无效的输入文件、文件读取错误和编码表生成错误等。这些错误处理机制使得程序具备了一定的鲁棒性,能够应对各种异常情况。

通过本次实验,我们深入了解了赫夫曼编码的原理和应用,并通过代码的实现加深了对赫夫曼编码的理解。我们掌握了文件加解密和压缩的基本操作,并学会了使用赫夫曼编码算法进行数据的压缩和解压缩。这些技能对于实际应用中的文件数据处理具有重要意义,提高了文件数据的安全性和处理效率。

总之,通过本次实验,我们成功实现了文件中数据的加解密与压缩功能,并对赫夫曼编码算法有了更深入的理解。实验过程中,我们不仅学习了赫夫曼编码的原理和实现方法,还掌握了文件读写、数据处理和错误处理等编程技巧。这些技能的掌握为我们今后在文件数据处理和安全传输领域的工作奠定了基础,为我们的学习和职业发展带来了积极的影响。

附录

源码

源码

#include 
#include 
#include 

#define Line 200 //每行最大字符数

typedef struct HTNode {
    int weight;//权值
    int lchild, rchild, parent;//左右孩子及双亲结点在数组中的位置下标
} HTNode, *HTree;//哈夫曼树

typedef char **HuffmanCode;//哈夫曼编码

//各个文件夹的路径
char *SourceFile = "../Source.txt";
char *CountFile = "../Count.txt";
char *HuffmanCodeFile = "../HuffmanCodeFile.txt";
char *ZipFile = "../Zip.txt";
char *UnZipFile = "../Unzip.txt";

int HexDataTimes[256] = {0};//存储16进制数据出现次数


void TransToHex(char ByteData[], int HexData[], int bytenum);//将ByteData中的数据转换成16进制数据存储到HexData中
void AddTimes(int HexDataTimes[], int HexData[]);//将HexData中的数据出现次数存储到HexDataTimes中
void ReadByte_WriteHex();//读取文件,将字节数据转换成16进制数据,存储到HexDataTimes[0~0xFF]中
void SelectMin(HTree HT, const int n, int *min1, int *min2);//选择最小的两个权值
HTree CreateHTree(int *N);//创建哈夫曼树
HuffmanCode HuffmanCoding(HTree HT, int n);//创建哈夫曼编码
HuffmanCode CreateSearchList(HuffmanCode HC);//创建搜索表
void Compress(HuffmanCode SearchList);//压缩文件
void WriteHuffmanCode(HuffmanCode SearchList, char *Str01);//将Str01中的01转换成16进制写入文件
void TransWrite(char *Str01, int _01StrLen);//将Str01中的01转换成16进制写入文件
void _01StrCat(char *Str01, char *OriginCode, int strlen, HuffmanCode SearchList);//将OriginCode中的字符转换成Str01中的01
void Set_bit(char *a, int bit, int _01);//设置bit位为_01
void BitToStr(char a, char *Each_Str);//将a转换成字符串存储到Each_Str中
void TransWrite2(char *Str01, char SearchList[0xFF + 1][200], int bytenum);//将Str01中的01转换成16进制写入文件
void DeCompress();//解压文件
void TransWrite2(char *Str01, char SearchList[0xFF + 1][200], int bytenum);//将Str01中的01转换成16进制写入文件
void BitToStr(char a, char *Each_Str);//将字符a转换成字符串存储到Each_Str中
int Compare(void);//文件内容比较,默认为源文件和解压文件比较
int Check_File(char *filename);//检查文件是否存在

int main() {
    //使用之前务必有一个Source.txt文件,其他文件没有会自动创建

    char f[256];
    char x[256];
    char s[256];
    int length, N;
    int n;
    HTree HT = NULL;
    HuffmanCode HC = NULL;
    HuffmanCode SearchList = NULL;
    //界面设置
    printf("***********欢迎使用哈夫曼编码压缩文件系统***********\n");
    printf("*-------------------请选择功能---------------------*\n");
    printf("* 1.压缩文件                                       *\n");
    printf("* 2.解压文件                                       *\n");
    printf("* 3.文件对比                                       *\n");
    printf("* 0.退出系统                                       *\n");
    printf("****************************************************\n");
    printf("请输入功能编号:");

    while (1) {
        scanf("%d", &n);
        printf("%d\n", n);
        while (1) {
            //选择功能1,压缩文件
            if(n == 1){
                printf("请输入要压缩的文件路径:");
                scanf("%s", f);
                printf("%s\n", f);
                if (Check_File(f)) {
                    //路径正确,文件存在,开始压缩
                    printf("开始压缩\n");
                    //初始化
                    HT = NULL;
                    HC = NULL;
                    SearchList = NULL;
                    SourceFile = f;
                    length=  N = 0;
                    for (int i = 0; i < 256; ++i)
                        HexDataTimes[i] = 0;

                    ReadByte_WriteHex();
                    HT = CreateHTree(&N);//创建哈夫曼树
                    HC = HuffmanCoding(HT, N);//创建哈夫曼编码
                    SearchList = CreateSearchList(HC);
                    Compress(SearchList);//压缩文件
                    DeCompress();//解压文件
                    Compare();//文件内容比较

                    printf("压缩成功,请输入其他命令\n");
                    printf("请输入功能编号:");
                } else {
                    //路径错误,文件不存在,重新输入
                    continue;
                }
            }
            //选择功能2,解压文件
            else if(n == 2){
                printf("请输入要解压的文件路径:");
                scanf("%s", x);
                printf("%s\n", x);

                if (Check_File(x)) {
                    //路径正确,文件存在,开始解压
                    printf("开始解压\n");
                    if (Check_File(HuffmanCodeFile) == 0) {
                        printf("解压失败,缺少对应码表,请进入功能1录入源文件信息\n");
                        printf("请输入功能编号:");
                        break;
                    } else {
                        ZipFile = x;

                        DeCompress();//解压文件
                        Compare();//文件内容比较
                    }
                    printf("解压完成,请输入其他命令\n");
                    printf("请输入功能编号:");
                } else {
                    //路径错误,文件不存在,重新输入
                    continue;
                }
            }
            //选择功能3,文件内容比较
            else if(n == 3)
            {
                printf("请输入要对比的两个文件的路径:\n");
                printf("文件1:");
                scanf("%s", f);
                if (Check_File(f)) {
                    SourceFile = f;
                    printf("\n文件2:");
                    scanf("%s", s);
                    if (Check_File(s)) {
                        UnZipFile = s;
                        Compare();//文件内容比较
                        printf("对比完成,请输入其他命令\n");
                        printf("请输入功能编号:");
                    } else {
                        //路径错误,文件不存在,重新输入
                        printf("\n");
                        continue;
                    }
                } else {
                    //路径错误,文件不存在,重新输入
                    printf("\n");
                    continue;
                }
            }
            //选择功能0,退出系统
            else if (n == 0)
            {
                exit(0);
            }
            //输入错误的功能编号,重新输入
            else{
                printf("输入错误,请重新输入\n");
                printf("请输入功能编号:");
            }
            break;
        }
    }
    return 0;
}

/**
 * @brief 检查文件是否存在
 * @param filename  文件路径
 * @return  0:文件不存在 1:文件存在
 */
int Check_File(char *filename) {
    FILE *fp;
    if ((fp = fopen(filename, "r")) == NULL) {
        printf("文件不存在,请输入正确的文件路径\n");
        return 0;
    } else {
        return 1;
    }
}

/*功能1*/
/**
 * @brief 读取文件,将字节数据转换成16进制数据,存储到HexDataTimes[0~0xFF]中
 */
void ReadByte_WriteHex() {
    int i, bytenum;
    FILE *file1 = fopen(SourceFile, "rb");// 以二进制只读方式打开源文件
    FILE *file2 = fopen(CountFile, "wb");// 以二进制写入方式打开计数文件

    if (file1 != NULL && file2 != NULL) {//确保文件打开成功
        //打开文件
        char ByteData[Line + 1] = {0};//存储原始字节数据
        int HexData[Line + 1] = {0};//以16进制存储字节值的
        for (; feof(file1) == 0;) {
            memset(HexData, -1, sizeof(HexData) / sizeof(int));//每次都重置为-1
            memset(ByteData, 0, sizeof(ByteData) / sizeof(char));//重置数组为0
            bytenum = fread(ByteData, sizeof(char), Line + 1, file1);//返回值为实际读取的字节数
            TransToHex(&ByteData[0], &HexData[0], bytenum);//将ByteData转换成16进制数存储
            AddTimes(&HexDataTimes[0], &HexData[0]);//将16进制字节值的出现次数存储到HexDataTime[0~255]全局变量中
        }
        for (i = 0; i <= 0xFF; ++i) {
            fprintf(file2, "%02X==%010d\n", i, HexDataTimes[i]);//以特定格式将频次数据写入文件
        }
        //关闭文件
        fclose(file1);
        fclose(file2);
    }
}

/**
 * @brief 将ByteData中的数据转换成16进制数据存储到HexData中
 * @param ByteData 存储字节数据
 * @param HexData  存储16进制数据
 * @param bytenum  字节数据的个数
 */
void TransToHex(char ByteData[], int HexData[], int bytenum) {
    for (int i = 0; i < bytenum; ++i) {
        HexData[i] = ByteData[i] & 0b11111111;
    }
}

/**
 * @brief 将HexData中的数据出现次数存储到HexDataTimes中
 * @param hexDataTimes 存储16进制数据出现次数
 * @param HexData    存储16进制数据
 */
void AddTimes(int hexDataTimes[], int HexData[]) {
    for (int i = 0; HexData[i] != -1 && i <= Line; i++)
        HexDataTimes[HexData[i]]++;//相同字符出现次数加1
}

/*功能2*/
/**
 * @brief       创建哈夫曼树
 * @param   N   哈夫曼树的叶子节点个数
 * @return      哈夫曼树
 */
HTree CreateHTree(int *N) {
    int i, n, m;
    int min1, min2;
    HTree HT;
    int weight[0xFF + 1], weight_Hex[0xFF + 1];
    for (i = 0, n = 0; i < 256; i++) {
        if (HexDataTimes[i] != 0) {
            weight[n] = HexDataTimes[i];//把非0权值提取出来
            weight_Hex[n] = i;        //保留对应非0权值的映射值
            ++n;
        }
    }
    *N = n;
    m = 2 * n - 1;//weight[0~n-1]存放了所有非0权值
    HT = (HTree) malloc((m + 1) * sizeof(HTNode));//分配编码空间舍去0单元不用
    for (i = 1; i <= n; i++) {
        HT[i].weight = weight[i];
        HT[i].parent = 0;
        HT[i].lchild = 0;
        HT[i].rchild = 0;
    }
    for (i = n + 1; i <= m; i++) {
        HT[i].weight = 0;
        HT[i].parent = 0;
        HT[i].lchild = 0;
        HT[i].rchild = 0;
    }
    for (i = n + 1; i <= m; ++i) {
        SelectMin(HT, i - 1, &min1, &min2);
        HT[min1].parent = i;
        HT[min2].parent = i;
        HT[i].lchild = min1;
        HT[i].rchild = min2;
        HT[i].weight = HT[min1].weight + HT[min2].weight;
    }
    return HT;
}

/**
 * @brief 获取哈夫曼编码
 * @param HT  哈夫曼树
 * @param n   哈夫曼树的叶子节点个数
 * @return    哈夫曼编码
 */
HuffmanCode HCHuffmanCoding(HTree HT, int n) {
    int start, parent, current;
    HuffmanCode HC;
    HC = (HuffmanCode) malloc((n + 1) * sizeof(char *));
    char *Each_Code = (char *) malloc(n * sizeof(char));
    Each_Code[n - 1] = '\0';

    for (int i = 1; i <= n; ++i) {
        start = n - 1;//从start开始时指向最后,即编码结束符位置
        current = i;
        parent = HT[i].parent;
        while (parent != 0) {
            --start;
            if (HT[parent].lchild == current)
                Each_Code[start] = '0';
            else
                Each_Code[start] = '1';
            current = parent;
            parent = HT[parent].parent;
        }
        HC[i] = (char *) malloc((n - start) * sizeof(char));
        strcpy(HC[i], &Each_Code[start]);
    }
    free(Each_Code);
    return HC;
}

/**
 * @brief 选择最小的两个权值
 * @param HT    哈夫曼树
 * @param n     哈夫曼树的叶子节点个数
 * @param min1  最小权值的下标
 * @param min2  次小权值的下标
 */
void SelectMin(HTree HT, const int n, int *min1, int *min2) {
    int i;
    int min_value1 = INT_MAX;//INT_MAX是C语言中定义的最大整数
    int min_value2 = INT_MAX;

    //找出最小权值
    for (i = 1; i <= n; i++) {
        if (HT[i].parent == 0 && HT[i].weight < min_value1) {//HT[i].parent==0是为了防止节点被重复选取
            min_value1 = HT[i].weight;//min_value1是最小权值
            *min1 = i;//min1是最小权值的下标
        }
    }
    //找出次小权值
    for (i = 1; i <= n; i++) {
        if (HT[i].parent == 0 && HT[i].weight < min_value2 && i != (*min1)) {//i!=(*min1)是为了防止min1和min2指向同一个节点
            min_value2 = HT[i].weight;//min_value2是次小权值
            *min2 = i;//min2是次小权值的下标
        }
    }
}

/*功能3 压缩*/

/**
 * @brief 根据编码查询表SearchList[0~0xFF]进行编码
 * @param SearchList  搜索表
 */
void Compress(HuffmanCode SearchList) {
    FILE *pfile = fopen(SourceFile, "rb");
    FILE *pfile1 = fopen(ZipFile, "wb");
    fclose(pfile1);
    char OriginCode[Line + 10] = {0}, Str01[20 * Line] = {0};

    for (; feof(pfile) == 0;) {
        memset(OriginCode, 0, strlen(OriginCode));
        fread(OriginCode, sizeof(char), Line, pfile);
        _01StrCat(&Str01[0], &OriginCode[0], strlen(OriginCode), SearchList);
        TransWrite(&Str01[0], strlen(Str01));//写入BitCode
    }
    fclose(pfile);
    WriteHuffmanCode(SearchList, Str01);
    //将编码查询表写入另一文件,同时将无法凑足8bit位的
    //编码以字符串形式写到该文件末尾
}

/**
 * @brief 创建搜索表
 * @param HC  哈夫曼编码  SearchList中有NULL,而HC只有[0]单元为空
 * @return  搜索表
 */
HuffmanCode CreateSearchList(HuffmanCode HC) {
    int i, j;
    HuffmanCode SearchList;

    SearchList = (HuffmanCode) calloc(0xFF + 1, sizeof(char *));
    for (i = 0; i <= 0xFF; ++i)
        SearchList[i] = NULL;
    for (i = 0, j = 1; i <= 0xFF; ++i) {
        if (HexDataTimes[i] != 0) {
            SearchList[i] = HC[j];
            ++j;
        }
    }
    return SearchList;
}

/**
 * @brief 将读取的字节值对应的哈夫曼编码逐个连接到Str01
 * @param Str01  二进制字符串
 * @param OriginCode  原始数据
 * @param strlen  原始数据长度
 * @param SearchList  搜索表
 */
void _01StrCat(char *Str01, char *OriginCode, int strlen, HuffmanCode SearchList) {
    int i;
    for (i = 0; i < strlen; ++i) {
        strcat(Str01, SearchList[OriginCode[i] & 0b11111111]);//将读取的字节值对应的哈夫曼编码逐个连接到_01Str
    }
}

/*按位运算设定bit位,稍大数值用强制类型转换(char),否则部分编译器报错数值溢出*/
/**
 * @brief 设置bit位
 * @param a    字符串
 * @param bit  bit位
 * @param _01  0或1
 */
void Set_bit(char *a, int bit, int _01) {
    char bit_to_1[9] = {0, 0b1, 0b10, 0b100, 0b1000, 0b10000,
                        0b100000, 0b1000000, (char) 0b10000000};
    char bit_to_0[9] = {0, (char) 0b11111110, (char) 0b11111101, (char) 0b11111011, (char) 0b11110111,
                        (char) 0b11101111, (char) 0b11011111, (char) 0b10111111, (char) 0b01111111};
    if (_01 == 0) {
        *a &= bit_to_0[bit]; //表示要将bit位变为0,也即是将第bit位&0,其他位&1即可
    } else if (_01 == 1) {
        *a |= bit_to_1[bit];//表示要将bit位变为1,也即时将第bit位|1,其他位|0即可
    }
}

/**
 * @brief 将Str01(0101010000111……)字符数字数值存储到BitCode[]元素的bit位中
 * @param Str01      二进制字符串
 * @param _01StrLen  二进制字符串长度
 */
void TransWrite(char *Str01, int _01StrLen) {
    int i, last, bytenum;
    char BitCode[_01StrLen / 8 + 10];
    char temp[10];
    last = _01StrLen % 8;//将不足八位的截断
    _01StrLen -= last;
    for (i = 7, bytenum = 0; i < _01StrLen; i += 8) {
        Set_bit(&BitCode[bytenum], 1, Str01[i] - 48);
        Set_bit(&BitCode[bytenum], 2, Str01[i - 1] - 48);
        Set_bit(&BitCode[bytenum], 3, Str01[i - 2] - 48);
        Set_bit(&BitCode[bytenum], 4, Str01[i - 3] - 48);
        Set_bit(&BitCode[bytenum], 5, Str01[i - 4] - 48);
        Set_bit(&BitCode[bytenum], 6, Str01[i - 5] - 48);
        Set_bit(&BitCode[bytenum], 7, Str01[i - 6] - 48);
        Set_bit(&BitCode[bytenum], 8, Str01[i - 7] - 48);
        bytenum++;
    }
    BitCode[bytenum] = '\0';
    strcpy(temp, &Str01[_01StrLen]);
    strcpy(Str01, temp);//将截断未被写入的子字符串恢复到_01Str开头

    FILE *pfile = fopen(ZipFile, "ab");
    if (pfile != NULL) {
        fwrite(BitCode, sizeof(char), bytenum, pfile);
        fclose(pfile);
    } else {
        printf("文件打开失败!\n");
        fclose(pfile);
    }

}

/**
 * @brief 将哈夫曼编码表写到文件fHuffmanCode,格式为每行一个编码
 * @param SearchList  搜索表
 * @param Str01     二进制字符串
 */
void WriteHuffmanCode(HuffmanCode SearchList, char *Str01) {
    int i;
    FILE *pfile = fopen(HuffmanCodeFile, "wb");
    if (pfile != NULL) {
        for (i = 0; i <= 0xFF; ++i)
            fprintf(pfile, "%s\n", SearchList[i]);
        fprintf(pfile, "%s", Str01);//不足8bit位的,写到文件尾部
        fclose(pfile);
    } else {
        printf("文件打开失败!\n");
    }

}

/*功能四 解压*/
/**
 * @brief 解压文件
 */
void DeCompress() {
    int i;
    char surplus[12] = {0};//读取编码查询表文件末尾的不足8位字符串
    char SearchList[0xFF + 1][200];//这里以数组形式使用编码表
    FILE *pfile = fopen(HuffmanCodeFile, "rb");
    FILE *pfile1 = fopen(UnZipFile, "wb");//清空
    fclose(pfile1);
    if (pfile != NULL) {
        for (i = 0; i <= 0xFF; ++i) {
            fscanf(pfile, "%s", SearchList[i]);//读取编码查询表
        }
        fgets(surplus, 10, pfile);//最后一行就是不足8位的字符串,读取它
        fclose(pfile);
    }
    //编码查询表写入时有的为(null),将其截断
    for (i = 0; i <= 0xFF; ++i)
        if (strcmp(SearchList[i], "(null)") == 0)
            SearchList[i][0] = '\0';

    pfile = fopen(ZipFile, "rb");
    char BitCode[Line + 10], Str01[8 * Line + 10] = {0}, EachStr[50];
    int bytenum;

    for (; feof(pfile) == 0;) {
        memset(BitCode, 0, strlen(BitCode));
        bytenum = fread(BitCode, sizeof(char), Line, pfile);
        //返回值为读取了的字节数
        for (i = 0; i < bytenum; ++i) {
            //BitCode转为字符串
            BitToStr(BitCode[i], EachStr);
            //将每个逆转得到的字符串连接到_01Str
            strcat(Str01, EachStr);
        }
        //将_01Str写入到解压文件
        TransWrite2(Str01, SearchList, bytenum);
    }
    fclose(pfile);
    //下面的代码作用:将不足8位的编码字符串连接到_01Str中,
    //按理将正好凑足8位。并将该(也是最后一个)字节写入解压文件
    strcat(Str01, surplus);

    pfile = fopen(UnZipFile, "ab");
    for (i = 0; i <= 0xFF; ++i) {
        if (SearchList[i][0] != '\0')
            if (strcmp(Str01, SearchList[i]) == 0) {
                char OneCode = i;
                printf("%c", OneCode);
                fwrite(&OneCode, sizeof(char), 1, pfile);
                break;
            }
    }
    fwrite("\n", sizeof(char), 1, pfile);
    fclose(pfile);
}

/**
 * 将_01Str中的01转换成16进制写入文件
 * @param a  Str01 01字符串
 * @param Each_Str  OriginCode 字符串
 */
void BitToStr(char a, char *Each_Str) {
    int bit[8] = {0b00000001, 0b00000010, 0b00000100,
                  0b00001000, 0b00010000, 0b00100000, 0b01000000, 0b10000000};
    Each_Str[0] = ((a & bit[7]) >> 7) + 48;
    Each_Str[1] = ((a & bit[6]) >> 6) + 48;
    Each_Str[2] = ((a & bit[5]) >> 5) + 48;
    Each_Str[3] = ((a & bit[4]) >> 4) + 48;
    Each_Str[4] = ((a & bit[3]) >> 3) + 48;
    Each_Str[5] = ((a & bit[2]) >> 2) + 48;
    Each_Str[6] = ((a & bit[1]) >> 1) + 48;
    Each_Str[7] = ((a & bit[0]) >> 0) + 48;
    Each_Str[8] = '\0';
}

/**
 * 将Str01中的01转换成16进制写入文件
 * @param Str01  Str01 01字符串
 * @param SearchList  OriginCode 字符串
 * @param bytenum  OriginCode 字符串长度
 */
void TransWrite2(char *Str01, char SearchList[0xFF + 1][200], int bytenum) {
    FILE *pfile = fopen(UnZipFile, "ab");

    int i = 0, times = 0, j, Bitnum = bytenum * 8, len = 0;
    char *p = NULL;
    //char temp[100];
    char OneCode;

    //循环遍历_01Str
    while (i <= Bitnum) {
        for (j = 0; j <= 0xFF; ++j)//遍历编码查询表
        {
            //如果编码串不为空
            if (SearchList[j][0] != '\0')
                //比较 编码串长度个字符
                if (strncmp(&Str01[i], SearchList[j], strlen(SearchList[j])) == 0) {        //如果_01Str[i]开始匹配成功,
                    len += strlen(SearchList[j]);//统计总共写入了的_01Str中的字符数
                    OneCode = j;//j的值即为该编码串对应的原字节值
                    //把字节值写入解压文件,完成一个字节的解压
                    printf("%c", OneCode);
                    fwrite(&OneCode, sizeof(char), 1, pfile);

                    times++;
                    i += strlen(SearchList[j]);
                    //执行到这里说明已经解压了编码串长度个字符,i要随之变动
                    break;
                }
        }
        if (j == 0xFF + 1)//说明遍历了编码查询表也没有匹配成功,继续从下一个字符匹配
            ++i;//所以加1
    }
    strcpy(Str01, &Str01[len]);//将未解压的子字符串安排到开头,下一次继续
    fclose(pfile);
}

/**
 * 文件内容比较,默认为源文件和解压文件比较
 * @return  1:两个文件完全相同 0:两个文件不相同
 */
int Compare(void) {
    FILE *fp, *fp1;
    int array1 = 1, row1 = 1, array2 = 1, row2 = 1;
    char ch1, ch2;
    //test与test1都是与当前程序在同一目录下的文件
    fp = fopen(SourceFile, "r");
    fp1 = fopen(UnZipFile, "r");
    while (!feof(fp) || !feof(fp1)) {
        ch1 = fgetc(fp);
        ch2 = fgetc(fp1);
        if (ch1 == '\n') {
            row1++;
            array1 = 1;
        }
        if (ch2 == '\n') {
            row2++;
            array2 = 1;
        }
        if (ch1 != ch2) {
            if (ch1 == EOF || ch2 == EOF) break;
            printf("第一次不相同的位置:\n");
            printf("test\t行号:%d 列号:%d\n", row1, array1);
            printf("test1\t行号:%d 列号:%d\n", row2, array2);
            printf("-----两个文件不相同!-----\n");
            break;
        } else {
            array1++;
            array2++;
        }
    }
    if (ch1 == EOF && ch2 != EOF) {
        printf("\n-----源文件文件内容被包含在解压文件中-----\n");
        fclose(fp);
        fclose(fp1);
        return 0;
    }

    if (ch2 == EOF && ch1 != EOF) {
        printf("\n-----解压文件内容被包含在源文件中-----\n");
        fclose(fp);
        fclose(fp1);
        return 0;
    }

    if (ch1 == EOF && ch2 == EOF) {
        printf("\n-----两个文件完全相同!-----\n");
        fclose(fp);
        fclose(fp1);
        return 1;
    }
    return 0;
}

测试文件

This README file is copied into the directory for GCC-only header files
when fixincludes is run by the makefile for GCC.
Many of the files in this directory were automatically edited from the
standard system header files by the fixincludes process.

They are system-specific, and will not work on any other kind of system.  They
are also not part of GCC.  The reason we have to do this is because
GCC requires ANSI C headers and many vendors supply ANSI-incompatible
headers.

Because this is an automated process, sometimes headers get "fixed"
that do not, strictly speaking, need a fix.  As long as nothing is broken
by the process, it is just an unfortunate collateral inconvenience.
We would like to rectify it, if it is not "too inconvenient".
 
我说道,“爸爸,你走吧。”他望车外看了看说:“我买几个橘子去。你就在此地,不要走动。”我看那边月台的'栅栏外有几个卖东西的等着顾客。
走到那边月台,须穿过铁道,须跳下去又爬上去。父亲是一个胖子,走过去自然要费事些。我本来要去的,他不肯,只好让他去。
我看见他戴着黑布小帽,穿着黑布大马褂,深青布棉袍,蹒跚地走到铁道边,慢慢探身下去,尚不大难。可是他穿过铁道,要爬上那边月台,
就不容易了。他用两手攀着上面,两脚再向上缩;他肥胖的身子向左微倾,显出努力的样子。这时我看见他的背影,我的泪很快地流下来了。
我赶紧拭干了泪。怕他看见,也怕别人看见。我再向外看时,他已抱了朱红的橘子往回走了。过铁道时,他先将橘子散放在地上,自己慢慢爬下,再抱起橘子走。
到这边时,我赶紧去搀他。他和我走到车上,将橘子一股脑儿放在我的皮大衣上。于是扑扑衣上的泥土,心里很轻松似的。
过一会说:“我走了,到那边来信!”我望着他走出去。他走了几步,回过头看见我,说:“进去吧,里边没人。”
等他的背影混入来来往往的人里,再找不着了,我便进来坐下,我的眼泪又来了。
近几年来,父亲和我都是东奔西走,家中光景是一日不如一日。他少年出外谋生,独力支持,做了许多大事。哪知老境却如此颓唐!
他触目伤怀,自然情不能自已。情郁于中,自然要发之于外;家庭琐屑便往往触他之怒。他待我渐渐不同往日。但最近两年的不见,
他终于忘却我的不好,只是惦记着我,惦记着我的儿子。我北来后,他写了一信给我,信中说道:“我身体平安,惟膀子疼痛厉害,
诸多不便,大约大去之期不远矣。”我读到此处,在晶莹的泪光中,又看见那肥胖的、青布棉袍黑布马褂的背影。
唉!我不知何时再能与他相见!

你可能感兴趣的:(笔记,数据结构,开发语言,期末复习,c语言)