目录

前言

一、实验目的

二、实验要求

三、设计思想

1 编码

1.1 生成Haffman编码

(1)统计字符频率

(2)构造Haffman树

1.2 文本编码

2 译码

2.1 读入译码信息

2.2 文本译码

2.3 测试结果

四、源码


前言:

        这是我在CSDN的第一篇文章,第一次不知道怎么写,所以直接把实验报告搬上来了。但是目前这篇文章的阅读量很高,并且有帮到了好几位小伙伴,同时也得到了他们对我创作内容的认可,我受宠若惊,所以决定更新一下这篇文章,以更好地帮助大家解决问题。

我猜测许多小伙伴也是要写报告,所以我并不打算修改文章的整体框架,依旧以一份报告的格式来呈现,仅做小部分的改动:将图片型代码转为可以直接复制的代码块;修改部分措辞,便于理解;增加更多解释性语言,尽量把整个流程阐述地更加清楚。

由于当初使用的是富文本编辑器来写的文章,所以无法使用Markdown的相关语法,排版方面可能有些许粗糙,还请见谅。

  我希望达到的目的是,仅仅通过这一篇博客,你便可以完全理解使用Haffman编码实现文本压缩中生成编码、压缩并写入二进制文件、读取文件并解码的整体流程,并能够轻易地在我的框架指导下写出自己的程序,不会出现修改时无从下手的情况。

目前有好几位小伙伴反馈了一些问题,汇总如下:

程序没有输出怎么办?

  1. 程序仅支持ASCII码字符,即英文文档。如果出现控制台没有任何输出,可以先去排查下文章中是否有中文的,。、?【】();等字符。可以在文章末尾源码部分下载我上传的测试文本,如果程序正常的话,那应该就是你源文本有中文字符了(目前有好几位小伙伴都是因为这个原因)。
  2. 如果控制台输出 “Failed to open files!” ,则需要检查一下在main函数开始是否修改了文件路径。相对路径注意将源文本与程序放在同目录下;绝对路径注意是否含有中文,某些编译器可能会出问题。同时,仅需创建"original_text.txt"源文本即可,压缩后的文本与解码后生成的文本会自动生成在同目录下。

压缩后的文件如何打开?

        压缩后生成的"compressed_text.txt"文件,虽然后缀是txt文本文档,但其实是二进制文件,无法通过普通的文本编辑器打开。大家可以自行去网上查询有关的二进制文本编辑器来打开(推荐以十六进制显示)。

我使用的是VScode中的Hex Editor编辑器,十分方便。

如果有其他问题,或者说上述问题的其他情况,可以随时评论区、私信我交流,一起解决问题。

同时我的主页还有更有优质文章,希望可以帮到你,感谢大家的点赞、收藏和关注~

以下为正文部分:


一、实验目的

哈夫曼编码是一种以哈夫曼树(最优二叉树,带权路径长度最小的二叉树)为基础变长编码方法。其基本思想是:将使用次数多的代码转换成长度较短的编码,而使用次数少的采用较长的编码,并且保持编码的唯一可解性。在计算机信息处理中,经常应用于数据压缩。是一种一致性编码法(又称"熵编码法"),用于数据的无损压缩。

要求实现一个完整的哈夫曼编码与译码系统。


二、实验要求

  1. 从文件中读入任意一篇英文文本文件,分别统计英文文本文件中各字符(包括标点符号和空格)的使用频率;
  2. 根据已统计的字符使用频率构造哈夫曼编码树,并给出每个字符的哈夫曼编码(字符集的哈夫曼编码表);
  3. 将文本文件利用哈夫曼树进行编码,存储成二进制压缩文件(哈夫曼编码文件);
  4. 计算哈夫曼编码文件的压缩率
  5. 将哈夫曼编码文件译码为文本文件,并与原文件进行比较。

三、设计思想

程序主要分为编码与译码两部分,总体思路如下:

图3.1 程序总体思路

相关存储结构及函数定义如下:

表3.1 函数定义

函数声明

作用

typedef struct HaffmanNode HaffmanNode, * HaffmanTree;

字符Haffman结点的存储结构

typedef struct Dic Dic;

存放字符及其编码结果的字典

typedef struct HeapStruct HeapStruct, * Heap;

堆的结构

HaffmanTree createHaffmanNode();

生成Haffman树结点

HaffmanTree createHaffmanTree();

构造Haffman树

Heap createHeap(int maxsize);

创建容量为maxsize的堆

void traverseHeap(Heap H);

堆的遍历

void insertMinHeap(Heap H, HaffmanTree data);

最小堆的插入

HaffmanTree deleteMinHeap(Heap H);

最小堆的删除

int countChFrequency(FILE* fp);

统计字符出现频率

void genHaffmanCode(HaffmanTree root);

生成Haffman编码

void encoded(FILE* source, FILE* output);

编码,生成二进制文件

void decoded(FILE* source, FILE* output);

解码,生成文本文件

1 编码

1.1 生成Haffman编码

对一棵具有n个叶子结点的Haffman树,将树中的每个左分支赋0,右分支赋1,则从根到每个叶子的通路上各个分支的赋值分别构成一个二进制串,该二进制串就称为哈夫曼编码。哈夫曼编码是最优前缀编码,能使各种报文对应的二进制串的平均长度最短。

因此,为生成Haffman编码,首先要对文本中每个字符出现的频率进行统计,并利用堆的结构构造Haffman树,并生成编码。

Haffman树结点的结构定义如下。

/* 字符Haffman结点 */
typedef struct HaffmanNode
{char character;             // 字符long count;                 // 出现频数char code[MAXBIT];          // 编码struct HaffmanNode *lchild; // 左孩子struct HaffmanNode *rchild; // 右孩子struct HaffmanNode *parent; // 父结点
} HaffmanNode, *HaffmanTree;

由于ASCII码值有128个字符,因此创建一个大小为128的Haffman结点指针数组,其下标分别对应各个字符的ASCII码值,并创建全局变量dic作为字典。

/* 存放字符及其编码结果的结构体 */
typedef struct Dic
{HaffmanTree charNode[128]; // 字符结点数组,起到字典的作用(只适用于ASCII码表)
} Dic;

(1)统计字符频率

使用countChFrequency函数进行统计,具体流程如下:

图3.2 统计字符流程题

(2)构造Haffman树

最小堆的根节点为所有元素中最小值,操作简单,时间复杂度低,用于构造Haffman树十分合适,因此使用最小堆的存储结构来构造。(没有学过堆的小伙伴可以采用别的方案,这里不是重点)

构造哈夫曼树的算法步骤如下:

① 预处理操作:由所有在文本中出现过的字符节点生成最小堆,并以各字符出现的频率(count)作为其权重。

② 依次从最小堆里取出两个结点(孩子结点),并生成一个新的父结点,将其插入最小堆中。其中父结点的count值为两孩子的count值求和。

③ 重复操作②,直至最小堆中只有一个结点为止。

④ 取出最小堆中结点,即为Haffman树的根节点root。

(3)生成Haffman编码

下面对各个字符进行编码,编码原则是:从树的根节点开始,进入左子树则编码加0,进入右子树则编码加1,就可以得到对应字符的二进制编码。

图3.3 生成Haffman编码示例

具体编程实现采用从叶子结点逆向遍历至根结点的方法:

即对于每一个叶子结点来说,如果它是父结点的左儿子,则将'0'存入临时字符数组中,如果是右儿子,则存入'1',如此循环直至到达根节点。

最后将临时字符数组逆置,便得到字符的编码,将其存入对应结点的code字符数组中。

结果示例(部分):

图3.4 Haffman编码结果部分示例

1.2 文本编码

接下来便是将source文件里的文本按照编码规则以二进制的形式写入到output文件中。

采取以下策略:

将存储内容分为三部分:

1. 文件基本信息

2. 正文

3. 字典(Haffman编码表)

结构如图所示,其中每一个小方格代表一个字节。

图3.5 二进制文本存储结构

(1)写入操作

三部分内容写入方式大同小异,以正文内容写入为例。

首先需要一个暂存编码的字符数组tempCode。读取正文内容,不断地将字符对应的编码追加到tempCode的末尾。当发现tempCode的长度大于8位时,将进行写入(逐字节写入)操作。

声明一个char类型字符ch(大小为1个字节),首先将其置为0,即将二进制码置为0000 0000。从tempCode的首字符开始读取8位,如果是'1',则将ch左移一位,然后+1;如果是'0',则仅将ch左移一位。然后将ch写入output文件中即可,同时将tempCode中的内容向前移动8位,如此循环。

当文本读取完毕,发现tempCode中还有不足8位的编码,则在后面补0至8位,然后写入。

这里用到了位操作,估计有部分小伙伴不太理解,这里稍加解释,并不严谨,仅用于更好地辅助理解。(理解的小伙伴可以直接看代码)

我们采用的方案是逐字节写入,首先我们便需要获得一个1字节大小的“容器”,其实在计算机的眼里,没有什么int型、char型,不过是不同的连续大小的空间而已,int型就是4个字节,char型就是1个字节。

不知道怎么往文本中写二进制编码?但我们会写入char类型的字符呀。此处我们声明一个char类型的字符ch = 0,其对应的编码就是0000 0000。此时我们将这个char字符写入文本时,其实就是写入了8个为0的比特位。

那么现在我们就有办法写入0、1比特位了,如何将ch修改为我们想要的形式呢?这里就要用到左移加1的操作了。

ch << 1代表将整个字节的二进制位向左移动1位,溢出的舍弃,右边补0;ch +1 中加的是二进制下的1。这样,我们便可以在二进制串中写入0和1,也就可以构造我们的Haffman编码了。

即将ch“改造”成我们想要的二进制形式,再按照普通的文本写入方式即可。

此部分核心代码如下:

    // 写入文本char ch = fgetc(source);while (ch != EOF){strcat(tempCode, dic.charNode[ch]->code); // 将字符的编码追加给tempCode暂存lenTempCode = strlen(tempCode);ch = 0;                  //二进制为00000000,作为写入文本的二进制编码的临时容器while (lenTempCode >= 8) // 暂存编码位数大于8时,执行写入操作{for (i = 0; i < 8; i++) // 八位二进制数写入文件一次{if (tempCode[i] == '1') // ch的二进制数左移并加1{ch = ch << 1;ch = ch + 1;}elsech = ch << 1; // ch的二进制数左移}fwrite(&ch, sizeof(char), 1, output);lenCompressedFile++;strcpy(tempCode, tempCode + 8); // temoCode的值重新覆盖lenTempCode = strlen(tempCode);}ch = fgetc(source);}if (lenTempCode > 0) // 若最后不满8位,则补0{ch = 0;strcat(tempCode, "00000000");for (i = 0; i < 8; i++){if (tempCode[i] == '1') // ch的二进制数左移并加1{ch = ch << 1;ch = ch + 1;}elsech = ch << 1; // ch的二进制数左移}fwrite(&ch, sizeof(char), 1, output); // 将最后一个字符写入lenCompressedFile++;}

同理,将文本基本信息以及字典也写入output文件中(原理相同,代码详细,不多赘述),最终得到二进制文本,实现了压缩的目的,示例如下。

图3.6 二进制文本示例

2 译码

想要知道如何译码,就要知道是如何进行编码的,此时我们刚才在文件中存储的字典便派上了用场,就可以像人查字典一样,将二进制序列逐个与字典匹配,进行“翻译”。

2.1 读入译码信息

首先需要构造字典,将字符、对应Haffman编码存入。

字典结构如下图所示:

图3.7 字典结构

操作如下:

首先读入文本开头12个字节的3个文件基本信息,依据信息定位至字典存储位置,进行读取。对于每一个字符结点来说,首先读入1个字节,值就是其ASCII码值。然后读取其编码长度,并在编码中读取对应位数,然后转换为字符数组并存入对应结点即可。

核心代码如下:

    // 读取相关文本信息int textLen, binLen, types, biFileLen, codeLen, byteNum;fseek(source, 0, SEEK_END);biFileLen = ftell(source); // 二进制文件总大小fseek(source, 0, SEEK_SET);fread(&textLen, sizeof(int), 1, source); // 源文本大小fread(&binLen, sizeof(int), 1, source);  // 压缩后的文件大小(不含字典)fread(&types, sizeof(int), 1, source);   // 字符种类数// 读取字典并转换成二进制码(char数组)fseek(source, binLen, SEEK_SET);for (i = 0; i < types; i++){fread(&dic_decode.charNode[i]->character, 1, 1, source); // 读取字符(ASCII码)fread(&ch, 1, 1, source);dic_decode.charNode[i]->count = (long)ch;  // 读取字符编码长度 strlen(code)if (dic_decode.charNode[i]->count % 8 > 0) // 当前字符的编码占了几个字节byteNum = dic_decode.charNode[i]->count / 8 + 1;elsebyteNum = dic_decode.charNode[i]->count / 8;for (j = 0; j < byteNum; j++){fread(&ch, 1, 1, source); // 此步读取的是单个字节对应的ASCII码temp = (int)ch;_itoa(temp, tempCode, 2); // 将ch的值转为二进制字符串存入tempCodetemp = strlen(tempCode);for (k = 8; k > temp; k--){strcat(dic_decode.charNode[i]->code, "0"); //位数不足,执行补零操作}strcat(dic_decode.charNode[i]->code, tempCode);}dic_decode.charNode[i]->code[dic_decode.charNode[i]->count] = 0; // 放上/0}

如此,便根据文本内存储的基本信息与字典,还原了该文本Haffman编码。

2.2 文本译码

由于Haffman编码不等长,所以编码的匹配策略对匹配效率影响很大。首先对字典进行按照编码长度升序排序,在之后遍历时便可以以最小代价匹配出正确字符。

采用如下的匹配策略:

首先找出各字符编码中的最长编码数maxLen,声明字符数组codeToBeDecoded存放待译码编码。逐字节将二进制文件中的内容读取到codeToBeDecoded数组中(其中需要进行十进制转二进制字符串,并补0操作)。当发现codeToBeDecoded数组的长度大于maxLen时,将其编码与字典各字符的编码进行逐个内存匹配。匹配成功后,将译码得到的字符写入output文件,并将codeToBeDecoded中编码向前覆盖,如此循环。

当写入的字符数与原文本字符数相同时,则译码完成,此时再往后读取便是字典的信息。

如此,我们便得到了译码后的结果。匹配过程的流程图如下:

图3.8 译码匹配流程图

同时,将Haffman编码读入至字典中后,我们可以按照其长度对其进行升序排序,这样在译码查找的时候就可以从小到大依次匹配,方便处理、提高效率。

我的代码中使用的是冒泡排序,其时间复杂度为O(n^2),大家可以根据自己需要进行优化。

相关的核心代码如下:

int maxLen = strlen(dic_decode.charNode[types - 1]->code); // 最长编码数fseek(source, 12, SEEK_SET);                               // 指向正文开头tempCode[0] = 0;while (1){while (strlen(codeToBeDecoded) < maxLen) // 确保一定找到一个字符{fread(&ch, 1, 1, source);temp = (int)ch;_itoa(temp, tempCode, 2); // 将ch的值转为二进制字符串存入tempCodetemp = strlen(tempCode);for (k = 8; k > temp; k--) // 转十进制再转二进制会丢掉0,进行补0操作strcat(codeToBeDecoded, "0");strcat(codeToBeDecoded, tempCode);}// Haffman码与字符的匹配for (i = 0; i < types; i++){if (memcmp(dic_decode.charNode[i]->code, codeToBeDecoded, dic_decode.charNode[i]->count) == 0)break;}strcpy(codeToBeDecoded, codeToBeDecoded + dic_decode.charNode[i]->count); // 将已经解译的编码覆盖ch = dic_decode.charNode[i]->character;fwrite(&ch, 1, 1, output); //写入解码的文件writeCount++;if (writeCount == textLen) // 限定写入只写到正文内容,再往后存储的就是字典break;}

此处匹配方案使用的是memcmp函数,进行内存匹配。

2.3 测试结果

以”瓦尔登湖(英文版).txt”为例,运行结果如下:

图4.1 测试结果

对于一篇59w个字符的小说,压缩为55.65%,压缩耗时0.064秒,译码耗时0.069秒。


四、源码

在此给出整个程序的源代码,同时我也将.c文件与文章中”瓦尔登湖(英文版).txt”一同打包上传到CSDN资源板块,感兴趣的小伙伴可以直接下载下来直接使用。

资源地址:

Haffman编码实现文本压缩的源码及文本示例-C文档类资源-CSDN文库https://download.csdn.net/download/m15253053181/87166548程序全部源代码:

/*
* 程序:使用Haffman编码进行英文文档的编码、压缩、译码全过程
* 作者:友人帐_
* 文章地址:https://blog.csdn.net/m15253053181/article/details/127457700?spm=1001.2014.3001.5501
* 个人主页:https://blog.csdn.net/m15253053181?type=blog
* 希望可以帮助到大家,也欢迎进入我的主页查看更多优质文章~
*/#define _CRT_SECURE_NO_WARNINGS
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>#define MAXSIZE 100
#define MAXBIT 256/* 字符Haffman结点 */
typedef struct HaffmanNode
{char character;             // 字符long count;                 // 出现频数char code[MAXBIT];          // 编码struct HaffmanNode *lchild; // 左孩子struct HaffmanNode *rchild; // 右孩子struct HaffmanNode *parent; // 父亲
} HaffmanNode, *HaffmanTree;/* 存放字符及其编码结果的结构体 */
typedef struct Dic
{HaffmanTree charNode[128]; // 字符结点数组,起到字典的作用(只适用于ASCII码表)
} Dic;/* 堆的结构 */
typedef struct HeapStruct
{HaffmanTree *elem; // 存放堆元素(Haffman结点)的数组int size;          // 当前元素个数int capacity;      // 存储容量
} HeapStruct, *Heap;HaffmanTree createHaffmanNode();              // 生成Haffman树结点
HaffmanTree createHaffmanTree();              // 构造Haffman树
Heap createHeap(int maxsize);                 // 创建容量为maxsize的堆
void traverseHeap(Heap H);                    // 堆的遍历
void insertMinHeap(Heap H, HaffmanTree data); // 最小堆的插入
HaffmanTree deleteMinHeap(Heap H);            // 最小堆的删除int countChFrequency(FILE *fp);           // 统计字符出现频率
void genHaffmanCode(HaffmanTree root);    // 生成Haffman编码
void encoded(FILE *source, FILE *output); // 编码
void decoded(FILE *source, FILE *output); // 解码Dic dic; // 字典,全局变量,用于编码压缩int main()
{float begintime, endtime;// 编码FILE *originalText = fopen("./original_text.txt", "rb");     // 源文件FILE *compressedText = fopen("./compressed_text.txt", "wb"); // 压缩后的二进制文件if (!(originalText && compressedText))printf("Failed to open files!\n ");else{begintime = clock(); //计时开始encoded(originalText, compressedText);endtime = clock(); //计时结束printf("Program time consuming: %fs\n\n", (endtime - begintime) / ((double)CLOCKS_PER_SEC));}fclose(originalText);fclose(compressedText);// 解码FILE *binText = fopen("./compressed_text.txt", "rb");  // 压缩后的二进制文件FILE *decodedText = fopen("./decoded_text.txt", "wb"); // 解码后的文件if (!(binText && decodedText))printf("Failed to open files!\n");else{begintime = clock(); //计时开始decoded(binText, decodedText);endtime = clock(); //计时结束printf("Program time consuming: %fs\n\n", (endtime - begintime) / ((double)CLOCKS_PER_SEC));}fclose(binText);fclose(decodedText);system("pause");return 0;
}/*---------------------------------------- 编码 ----------------------------------------*/
void encoded(FILE *source, FILE *output)
{int i, j;int types = 0;int textLength = countChFrequency(source); // 计算字符频率HaffmanTree root = createHaffmanTree();    // 由字符频率构造Haffman树genHaffmanCode(root);                      // 编码// 输出编码结果printf("   *------------> Huffman Code Table <-----------*\n");printf("    ____________________________________________\n      charcater |  frequency  |     code\n");printf("    ____________|_____________|_________________\n");for (i = 0; i < 128; i++)if (dic.charNode[i]->count > 0){if (dic.charNode[i]->character == 10) // 换行符printf("       \\n\t|%8ld     |   %s\n", dic.charNode[i]->count, dic.charNode[i]->code);else if (dic.charNode[i]->character == 0) // 结束符printf("       \\0\t|%8ld     |   %s\n", dic.charNode[i]->count, dic.charNode[i]->code);else if (dic.charNode[i]->character == 9) // 制表符printf("       \\t\t|%8ld     |   %s\n", dic.charNode[i]->count, dic.charNode[i]->code);else if (dic.charNode[i]->character == 13) // 回车printf("       \\r\t|%8ld     |   %s\n", dic.charNode[i]->count, dic.charNode[i]->code);else if (dic.charNode[i]->character == 32) // 空格printf("     space      |%8ld     |   %s\n", dic.charNode[i]->count, dic.charNode[i]->code);elseprintf("       %c\t|%8ld     |   %s\n", dic.charNode[i]->character, dic.charNode[i]->count, dic.charNode[i]->code);types++;}printf("   _____________|_____________|_________________\n\n");// 压缩文件准备操作fseek(source, 0, SEEK_SET);  // 找到源文件开头fseek(output, 12, SEEK_SET); // 找到输出文件的12个字节后的位置,前12字节存相关信息int lenCompressedFile = 12;  // 压缩文件的长度,单位字节char tempCode[MAXBIT] = {0}; // 临时存放字符编码int lenTempCode = 0;         // 暂存的字符编码长度// 写入文本char ch = fgetc(source);while (ch != EOF){strcat(tempCode, dic.charNode[ch]->code); // 将字符的编码追加给tempCode暂存lenTempCode = strlen(tempCode);ch = 0;                  //二进制为00000000,作为写入文本的二进制编码的临时容器while (lenTempCode >= 8) // 暂存编码位数大于8时,执行写入操作{for (i = 0; i < 8; i++) // 八位二进制数写入文件一次{if (tempCode[i] == '1') // ch的二进制数左移并加1{ch = ch << 1;ch = ch + 1;}elsech = ch << 1; // ch的二进制数左移}fwrite(&ch, sizeof(char), 1, output);lenCompressedFile++;strcpy(tempCode, tempCode + 8); // temoCode的值重新覆盖lenTempCode = strlen(tempCode);}ch = fgetc(source);}if (lenTempCode > 0) // 若最后不满8位,则补0{ch = 0;strcat(tempCode, "00000000");for (i = 0; i < 8; i++){if (tempCode[i] == '1') // ch的二进制数左移并加1{ch = ch << 1;ch = ch + 1;}elsech = ch << 1; // ch的二进制数左移}fwrite(&ch, sizeof(char), 1, output); // 将最后一个字符写入lenCompressedFile++;}// 写入源文件大小、压缩后文件大小和字符种类数(bytes)fseek(output, 0, SEEK_SET);                         // 输出文件指针指向开头,写入参数fwrite(&textLength, sizeof(int), 1, output);        // 写入源文件大小fwrite(&lenCompressedFile, sizeof(int), 1, output); // 写入压缩后文件大小(包括12字节的文本信息及正文,不包括字典)fwrite(&types, sizeof(int), 1, output);             // 写入字符种类数// 写入字典fseek(output, lenCompressedFile, SEEK_SET); // 找到output的末尾HaffmanTree tempNode;char codeLenBit;for (i = 0; i < 128; i++){if (dic.charNode[i]->count > 0){tempNode = dic.charNode[i];lenTempCode = strlen(tempNode->code);fwrite(&(tempNode->character), 1, 1, output); // 写入字符ASCII码lenCompressedFile++;codeLenBit = lenTempCode;fwrite(&codeLenBit, 1, 1, output); // 写入字符编码的长度-1个字节就够了lenCompressedFile++;while (lenTempCode % 8 != 0) // 当编码不够整数字节时,补0{strcat(tempNode->code, "0");lenTempCode = strlen(tempNode->code);}while (tempNode->code[0] != 0){ch = 0;for (j = 0; j < 8; j++){if (tempNode->code[j] == '1'){ch = ch << 1;ch += 1;}elsech = ch << 1;}strcpy(tempNode->code, tempNode->code + 8);fwrite(&ch, 1, 1, output); // 将所得的编码信息写入文件lenCompressedFile++;}}}printf("\n>>> Original File Information\n");printf("- filename: original_text.txt\n- file length: %ld bytes\n", textLength);printf("\n>>> Compressed File Information\n");printf("- filename: decoded_text.txt\n- file length: %ld bytes\n", lenCompressedFile);printf("\nThe compress has finished! Compression ratio: %.2f%%\n", (float)lenCompressedFile / (float)textLength * 100);
}/*---------------------------------------- 解码 ----------------------------------------*/
void decoded(FILE *source, FILE *output)
{int i, j, k;int temp;unsigned char ch;int writeCount = 0;                 // 写入字符数的统计char tempCode[MAXBIT] = {0};        // 临时存放字符编码char codeToBeDecoded[MAXBIT] = {0}; // 存放待译码的二进制序列(1个char)Dic dic_decode;                     // 解码用字典// 对dic进行初始化for (i = 0; i < 128; i++){dic_decode.charNode[i] = (HaffmanTree)malloc(sizeof(HaffmanNode)); // 申请空间dic_decode.charNode[i]->character = 0;                             // 此时下标无含义dic_decode.charNode[i]->count = 0;                                 // 此时用于存放编码长度 strlen(code)dic_decode.charNode[i]->lchild = NULL;dic_decode.charNode[i]->rchild = NULL;dic_decode.charNode[i]->parent = NULL;dic_decode.charNode[i]->code[0] = 0;}// 读取相关文本信息int textLen, binLen, types, biFileLen, codeLen, byteNum;fseek(source, 0, SEEK_END);biFileLen = ftell(source); // 二进制文件总大小fseek(source, 0, SEEK_SET);fread(&textLen, sizeof(int), 1, source); // 源文本大小fread(&binLen, sizeof(int), 1, source);  // 压缩后的文件大小(不含字典)fread(&types, sizeof(int), 1, source);   // 字符种类数// 读取字典并转换成二进制码(char数组)fseek(source, binLen, SEEK_SET);for (i = 0; i < types; i++){fread(&dic_decode.charNode[i]->character, 1, 1, source); // 读取字符(ASCII码)fread(&ch, 1, 1, source);dic_decode.charNode[i]->count = (long)ch;  // 读取字符编码长度 strlen(code)if (dic_decode.charNode[i]->count % 8 > 0) // 当前字符的编码占了几个字节byteNum = dic_decode.charNode[i]->count / 8 + 1;elsebyteNum = dic_decode.charNode[i]->count / 8;for (j = 0; j < byteNum; j++){fread(&ch, 1, 1, source); // 此步读取的是单个字节对应的ASCII码temp = (int)ch;_itoa(temp, tempCode, 2); // 将ch的值转为二进制字符串存入tempCodetemp = strlen(tempCode);for (k = 8; k > temp; k--){strcat(dic_decode.charNode[i]->code, "0"); //位数不足,执行补零操作}strcat(dic_decode.charNode[i]->code, tempCode);}dic_decode.charNode[i]->code[dic_decode.charNode[i]->count] = 0; // 放上/0}// 按Haffman码长度从小到大排序,便于译码时查找 - 冒泡HaffmanTree tmp;for (i = 0; i < types; i++){for (j = 0; j < types - i - 1; j++){if (strlen(dic_decode.charNode[j]->code) > strlen(dic_decode.charNode[j + 1]->code)){tmp = dic_decode.charNode[j];dic_decode.charNode[j] = dic_decode.charNode[j + 1];dic_decode.charNode[j + 1] = tmp;}}}int maxLen = strlen(dic_decode.charNode[types - 1]->code); // 最长编码数fseek(source, 12, SEEK_SET);                               // 指向正文开头tempCode[0] = 0;while (1){while (strlen(codeToBeDecoded) < maxLen) // 确保一定找到一个字符{fread(&ch, 1, 1, source);temp = (int)ch;_itoa(temp, tempCode, 2); // 将ch的值转为二进制字符串存入tempCodetemp = strlen(tempCode);for (k = 8; k > temp; k--) // 转十进制再转二进制会丢掉0,进行补0操作strcat(codeToBeDecoded, "0");strcat(codeToBeDecoded, tempCode);}// Haffman码与字符的匹配for (i = 0; i < types; i++){if (memcmp(dic_decode.charNode[i]->code, codeToBeDecoded, dic_decode.charNode[i]->count) == 0)break;}strcpy(codeToBeDecoded, codeToBeDecoded + dic_decode.charNode[i]->count); // 将已经解译的编码覆盖ch = dic_decode.charNode[i]->character;fwrite(&ch, 1, 1, output); //写入解码的文件writeCount++;if (writeCount == textLen) // 限定写入只写到正文内容,再往后存储的就是字典break;}printf("The decoded has finish!\n");
}/*---------------------------------------- 编码所需操作 ----------------------------------------*/
/* 统计字符频率 */
int countChFrequency(FILE *fp)
{int i;int length = 0; // 统计文本的长度// 对dic进行初始化for (i = 0; i < 128; i++){dic.charNode[i] = (HaffmanTree)malloc(sizeof(HaffmanNode)); // 申请空间dic.charNode[i]->character = i;                             // 将下标与ASCII码对应dic.charNode[i]->count = 0;dic.charNode[i]->lchild = NULL;dic.charNode[i]->rchild = NULL;dic.charNode[i]->parent = NULL;}// 对字符进行统计char ch = fgetc(fp);while (ch != EOF){dic.charNode[(int)ch]->count++;ch = fgetc(fp);length++;}return length;
}/* 生成Haffman编码 */
void genHaffmanCode(HaffmanTree root)
{int i, j;int count = 0;char tempCode[256]; // 临时存放字符编码HaffmanTree pMove = NULL;// 生成Haffman编码for (i = 0; i < 128; i++){if (dic.charNode[i]->count > 0) // 对于出现过的字符,即Haffman树的叶子结点{pMove = dic.charNode[i];// 从叶子逆序到根,将编码逆序存放在tempCode中while (pMove->parent){if (pMove->parent->lchild == pMove)tempCode[count] = '0'; // 左子树为0elsetempCode[count] = '1'; // 右子树为1count++;pMove = pMove->parent;}// 将tempCode编码逆序存放在字符结点中for (j = 0; j < count; j++)dic.charNode[i]->code[j] = tempCode[count - j - 1];dic.charNode[i]->code[j] = '\0';count = 0;}}
}/* 生成Haffman树结点 */
HaffmanTree createHaffmanNode()
{HaffmanTree node = (HaffmanTree)malloc(sizeof(HaffmanNode));node->character = 0;node->count = 0;node->lchild = NULL;node->rchild = NULL;node->parent = NULL;return node;
}/* 构造Haffman树 */
HaffmanTree createHaffmanTree()
{int i;// 由字典构造最小堆Heap H = createHeap(MAXSIZE);for (i = 0; i < 128; i++){if (dic.charNode[i]->count > 0){insertMinHeap(H, dic.charNode[i]);}}// 构造Haffman树while (H->size > 1){// 创建新结点,值为两最小结点的和HaffmanTree newNode = createHaffmanNode();HaffmanTree left = deleteMinHeap(H);HaffmanTree right = deleteMinHeap(H);newNode->count = left->count + right->count;newNode->lchild = left;newNode->rchild = right;left->parent = newNode;right->parent = newNode;// 将新结点插入堆中insertMinHeap(H, newNode);}HaffmanTree root = deleteMinHeap(H);return root;
}/* 创建容量为maxsize的堆 */
Heap createHeap(int maxsize)
{Heap H = (Heap)malloc(sizeof(struct HeapStruct));H->elem = (HaffmanTree *)malloc(sizeof(HaffmanTree) * (maxsize + 1)); // 从下标为1存放堆元素H->size = 0;H->capacity = maxsize;return H;
}/* 堆的遍历 */
void traverseHeap(Heap H)
{int i;if (H->size == 0){printf("Heap is empty!\n");return;}for (i = 1; i <= H->size; i++){printf("%d ", H->elem[i]->count);}printf("\n");
}/* 最小堆的插入 */
void insertMinHeap(Heap H, HaffmanTree data)
{int i;if (H->size == H->capacity) // 堆满{printf("Min heap is full!\n");return;}i = H->size + 1; // i指向插入元素后堆中最后一个元素的位置H->size++;while (1){if (i <= 1) // 如果堆为空,直接退出去,插入元素break;if (!(H->elem[i / 2]->count > data->count)) // 已经找到位置break;H->elem[i] = H->elem[i / 2]; // 如果插入位置的父结点大于其值,则将其插入位置与父结点交换i /= 2;}H->elem[i] = data; // 将data插入
}/* 最小堆的删除 */
HaffmanTree deleteMinHeap(Heap H)
{int parent, child;HaffmanTree min, temp;if (H->size == 0){printf("Heap is empty!\n");return NULL;}min = H->elem[1]; // 取出最小值-根节点// 将堆最后一个元素作为树根,然后调整树的结构temp = H->elem[H->size]; // 存放堆的最后一个元素H->size--;for (parent = 1; parent * 2 <= H->size; parent = child){child = parent * 2; // 先指向左子结点// child指向左右子结点的较小者if ((child != H->size) && (H->elem[child]->count > H->elem[child + 1]->count))child++;if (temp->count <= H->elem[child]->count) //找到位置了break;else // 将temp移动到下一层H->elem[parent] = H->elem[child];}H->elem[parent] = temp;return min;
}

CSDN统计整篇文章共18906字符,码字不易,如果这篇文章对你有用的话,麻烦留下一个大大的赞吧~非常感谢!

Haffman编码实现文本压缩-C语言-万字长文,绝对详细相关推荐

  1. Haskll Lesson:Huffman编码实现文本压缩

    微信公众号:牛顿一号 欢迎关注我,一起学习,一起进步! 一.Huffman编码理论 学习资料 https://web.stanford.edu/class/archive/cs/cs106b/cs10 ...

  2. c语言哈夫曼压缩文本,哈夫曼文本压缩C语言实现.doc

    /*文件中有些参数定义的比较大,主要是为了适应较大文件的压缩*/ #include #include #include #include//用以删除多余的中间文件 #define M 10000000 ...

  3. 最全整理 | 万字长文综述目标检测领域,您要的,都在这里!

    点击上方"3D视觉工坊",选择"星标" 干货第一时间送达 在人体姿态估计前面的工作,一般还需要进行目标检测以提高最后姿态估计的准确度.那么这一次呢,站长就来跟大 ...

  4. huffman编码压缩c语言,用Huffman编码对文件进行压缩的C语言实现

    本文介绍了采用Huffman编码对ASCII码文件进行压缩的基本原理,并用C语言程序实现了这个压缩过程.文中给出了比较完整的C语言程序代码,可以直接用于调试实验. 福 建电 脑 21 0 2年第 1期 ...

  5. c语言文件压缩怎么求压缩比,文件压缩C语言程序设计报告.doc

    文件压缩C语言程序设计报告 课程设计目的 1. 巩固和加深学生对C语言课程的基本知识的理解和掌握 2. 掌握C语言编程和程序调试的基本技能 3. 利用C语言进行基本的软件设计 4. 掌握书写程序设计说 ...

  6. 史诗级计算机字符编码知识分享,万字长文,一文即懂!

    本文由阿里技术团队詹向阳(骁飏)分享,原题"一文读懂字符编码",有修订和改动. 一.引言 说起计算机字符编码,让我想起了科幻巨作<三体-黑暗深林>人类遇到外星文明魔戒的 ...

  7. 万字长文讲解编码知识,看这文就够了!| 原力计划

    作者 | Aledsan 责编 | 王晓曼 出品 | CSDN博客 基础概念 1.字符 字符指类字形单位或符号,包括字母.数字.运算符号.标点符号和其他符号,以及一些功能性符号.一般来说我们称某个字符 ...

  8. 万字长文讲解编码知识,看这文就够了!

    作者 | Aledsan 责编 | 王晓曼 出品 | CSDN博客 基础概念 1.字符 字符指类字形单位或符号,包括字母.数字.运算符号.标点符号和其他符号,以及一些功能性符号.一般来说我们称某个字符 ...

  9. Haffman编码(算法导论)

    上次算法导论课讲到了Haffman树,笔者惊叹于Haffman编码的压缩效果,故想自己亲自动手尝试写一个极简的Haffman压缩程序. 首先,我们来了解一下什么是Haffman编码 Haffman编码 ...

最新文章

  1. 【附代码】如何在私有链上编写、部署与以太坊进行交互的智能合约
  2. 元素周期表超清pdf_重磅分享||化学II卷5个大题汇编PDF
  3. AbstractListView源码分析9
  4. Android之最简单的遍历某个目录下的所有文件(递归)
  5. Java操作MongoDB之mongodb-driver
  6. 科技论文----论搜索引擎现状及发展趋势
  7. 您需要了解的WordPress漏洞以及如何修复它们
  8. struct结构体初始化3种方法
  9. java和node.js 2018_node.js在2018年能继续火起来吗?我们来看看node.js的待遇情况
  10. html基础标签 1211
  11. Basic Calculator 基本计算器-Leetcode
  12. php中提示Undefined index的解决方法
  13. 江苏大学考研885程序设计 - 填空选择知识点
  14. 如何在Scala中使用条件表达式 .
  15. Error: DPI-1047: Cannot locate a 64-bit Oracle Client library: “问题
  16. Root Cause Analysis of Anomalies of Multitier Services in Public Clouds
  17. 魔方(4)二阶魔方、六阶魔方、七阶魔方
  18. 计算机怎么快捷截图桌面,电脑截屏的快捷键是什么
  19. Web设计网站软件推荐
  20. MySQL多表查询优化

热门文章

  1. IIC总线电阻/电容的计算
  2. 安卓滤镜君LR调色大师v2.2.1
  3. “创客中国大创智”创新中国大创智上海市中小企业创新创业大赛
  4. 【产品经理】工作交接
  5. 教学杂谈-3:严师岀高徒 高徒捧名师
  6. 计算机键盘f1到f12功能键的功能肯作用,电脑键盘上F1到F12功能键的作用分别是什么?...
  7. 2019.11.6早上vue听写
  8. 罗永浩一个坑位卖60万脏钱背后:放下面子赚钱,才是成年人最大的体面
  9. navicat --mysql数据库外键约束及其外键策略
  10. 牛客网Wannafly挑战赛4 C.割草机(模拟)