1.输入一段100~200字的英文短文,存入一文件a中。
2.写函数统计短文出现的字母个数n及每个字母出现的次数。
3.写函数以字母出现的次数作权值,建Haffman树(n个叶子),给出每个字母的Huffman编码。
4.用每个字母编码对原短文进行编码,码文存入文件b中。
5.用Huffman树对b中码文进行译码,结果存入文件c中,比较a,c是否一致,以检验编码、译码的正确性。
●从实验要求中可以看到,此程序需要涉及到文件的读和写操作。读操作用fgetc函数实现,写操作用fputc函数实现。
●在程序中定义一个字符数组,此数组用来存储文章中所有可能出现的字符。再定义一个整形数组,此数组用来存储每个字符在文章中出现的次数。字符数组的下标与整形数组的下标一一对应。比如说字符数组character[0] == ‘a’,字符’a’在数组中的下标为0,那么每当从文章中读出的字符是’a’时,相应的整型数组第零个元char_freque[0]自加,如果当文件读到末尾时,发现文章中一共有5个字符’a’,那么char_freque[0]的值就应该是5。
●统计总共出现过多少个不同的字符n(因为文章比较短,可能会有个别字母未在文章中出现),统计过程就是对数组char_freque遍历,当发现数组元素的值不为0时,就让n自加(n的初值为0)。
●每一个结点用结构体定义,里面包含此结点的权值weight,父结点parent,左孩子lchild,右孩子rchild。
●可以证明,对于有n个叶子结点的Huffman树,一共需要m = (2 * n - 1)个结点。动态分配m+1个结点类型的存储空间,首地址赋值给节点类型的指针HT,之所以不分配m个,是因为数组下标为0的结点不使用。再动态分配n + 1个字符型数据的存储空间(同理。第0号元素不使用),首地址给字符型指针ture_form,用来存储在文章中实际出现过的字符。
●初始化树的每一个结点。数组下标为1到n的这n个节点用来表示叶子结点,他们的权值为n个字符出现的次数。也就是说把数组char_freque中n个不为0的元素分别赋值给前n个节点的weight。除这n个节点之外的其余m - n个节点的weight都为0,所有节点的parent,lchild,rchild都为0。在此函数中,同时初始化ture_form数组。初始化原则如下:
若第i个叶子结点的weight是由数组char_freque中的第j个元素来初始化的,则说明第i个节点对应的字符为character[j],那么就把character[j]赋值给ture_form[i]。比如说HT[5].weight = char_freque[7],下一条语句应该执行ture_form[5] = character[7]。
●由m个结点创建哈夫曼树。创建的过程实际上是把n个叶子结点的parent赋上值,其余m-n个结点的lchild、rchild和parent(根结点除外,根结点parent为0)赋上值。假设现在正处理第i个节点(i从n+1开始),那么需要从前i - 1个节点中找parent为0
(parent为0说明此结点为一棵树的根,它可以作为其它结点的孩子结点)且权值最小的两个结点。这两个结点分别作为第i个结点的左右孩子,第i结点则为这两个结点的双亲。当i的值为m之后,整个树就建立起来了,而且第m个结点为此哈夫曼树的根结点。
●对n个字符进行编码。采用逆序编码的方法,即从叶子结点出发,寻找根结点。例如第i个叶子结点中存储着它的双亲信息,访问第i个叶子节点的双亲p(注:双亲其实就是教材中的父节点,它是一个节点,而不是两个),判断此叶子结点是它双亲结点的左孩子还是右孩子,若为左孩子,则编码为0,右孩子编码为1。下一步继续往上访问,直到访问到根结点结束。这n个字符的编码用一个二维数组(实际上是二重指针进行了内存的分配)TC(Huffman Code的首字母缩写)存储。
●读取文章,生成此文章的Huffman编码。每读到一个字符,先判断此字符的编码在数组HC中位置,再把编码写入到文件b.txt中。
●读取编码,进行译码过程。从b.txt文件中读取编码,每次读取一个字符(此字符为0或1),并把此字符存放在一个临时数组中。判断当前数组中的字符是否为有效的Huffman编码,若是,则翻译此编码,并把翻译得到的字符写到文件c.txt中,清空临时数组,继续读取b.txt中的下一个字符。若不是有效编码,则继续读取b.txt中的下一个字符。
首先在自己创建的工程目录下新建一个名为“a”的文本文档,里面输入一段文章,我输入的文章内容如下:
Alipay is very popular in China and it helps people to pay online everywhere, even for the small business. Online paying promotes the trade deal and it is the future way to pay bills. With the development of none paper money trade, the nonstaff supermarket finally runs. It is the special supermarket which opens with none staff and customers can buy what they want and then pay online. People must be wondering what if customers steal stuffs. Then their credit will be lower, which means they are not welcomed in the shop next time. It is so great to involve in the credit, so people need to behave themselves. Credit is in need of in todays situation. The sharing industry is developing so fast that it can go much further with everybodys protection.
文件位置如图所示
运行程序之后,可以看到多出了两个文件,分别为b和c,查看c文件的内容是否与a文件的内容相同。
●文章中只能含有52个大小写字母,逗号,空格,句号这55种常用字符。其余的符号太多,我没写进程序中。
●文章不能太长,因为我默认所有字符的权值不超过2000,Huffman编码长度不超过9位。
●VS2017编译器可以成功运行此代码,其余编译器未测试。
●有些电脑会自动在文件的末尾添加一个换行符,因此会出现非法字符的问题。本次更新解决了此问题,只需把文件结束的标志从EOF改为’\n’即可。
●为了避免a.txt文件加错位置而引发断点,更新后的程序中添加了判断文件是否成功打开的代码,若文件打开失败,则输出提示信息,并退出程序的执行。
●判断是否在文件末尾自动添加了换行符的方法。
打开a.txt文件,将光标移至文件的末尾,如图所示。
此时按键盘上的右方向键,若光标的位置能移动到下一行,则说明文件末尾有一个换行符,此时只需按Backspace键即可删除此换行符,删除之后别忘了保存一下。
重要:如果文件末尾有换行符,一定要删去
// 赫夫曼编码.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include "pch.h"
#include
#include
#include
#include
struct Node {
int weight; // 权值
int parent, lchild, rchild;
};
//统计每个字符出现的次数
void Statis_char(int char_freque[]) {
FILE * fp;
fp = fopen("a.txt", "r");
char c;
while ((c = fgetc(fp)) != EOF ) {
if (c >= 97)
char_freque[c - 97]++;
else if (c >= 65)
char_freque[c - 39]++;
else if (c == ',')
char_freque[52]++;
else if (c == ' ')
char_freque[53]++;
else if (c == '.')
char_freque[54]++;
else //有的编译器可能无法使用exit函数,把此else语句注释掉就可以了
{
printf("非法字符\n");
exit(EOVERFLOW);
}
}
fclose(fp);
}
//统计总共有多少种不同的字符
int Statis_n(int char_freque[]) {
int n = 0;
for (int i = 0; i < 55; i++)
if (char_freque[i] != 0)
n++;
return n;
}
//对现在的m棵树进行初始化
void Init_tree(Node HT[], char ture_form[], int char_freque[], char character[], int m) {
int i, j;
for (i = 0, j = 1; i < 55; i++)
if (char_freque[i] != 0) {
HT[j] = { char_freque[i],0,0,0 };
ture_form[j] = character[i]; //第j个叶子结点对应的字符为ture_form[j]
j++;
}
for (; j <= m; j++)
HT[j] = { 0,0,0,0 };
}
//从前k个结点中选取两个parent为0且weight最小的结点
void Select(Node * HT, int k, int * i, int * j) {
int min1, min2;
min1 = min2 = 2000; //假设任何一个字符的权值都不超过2000.
for (int t = 1; t <= k; t++) {
if (HT[t].parent == 0) {
if (HT[t].weight < min1) {
min1 = HT[t].weight;
*i = t;
}
else if (HT[t].weight < min2) {
min2 = HT[t].weight;
*j = t;
}
}
}
}
//建立赫夫曼树
void Creat_HT(Node * HT, int n, int m) {
int i;
int s1, s2;
for (i = n + 1; i <= m; i++) {
Select(HT, i - 1, &s1, &s2);
HT[i].lchild = s1;
HT[i].rchild = s2;
HT[i].weight = HT[s1].weight + HT[s2].weight;
HT[s1].parent = HT[s2].parent = i;
}
}
//对每一个字符进行编码,逆序编码
char * * Huf_code(Node * HT, int n) {
char * cd = (char *)malloc(n * sizeof(char));
char * * HC = (char * *)malloc((n + 1) * sizeof(char *)); //此处注意分配的char *类型的存储空间,而不是char类型
cd[n - 1] = '\0'; //所有字符的编码长度不会超过(n - 1)
int p;
int start;
int c;
for (int i = 1; i <= n; i++) {
c = i;
start = n - 1;
p = HT[i].parent;
while (p != 0) {
if (HT[p].lchild == c)
cd[--start] = '0';
if (HT[p].rchild == c)
cd[--start] = '1';
c = p;
p = HT[c].parent;
}
HC[i] = (char *)malloc((n - start) * sizeof(char));
strcpy(HC[i], &cd[start]);
}
return HC;
}
//读取文章,并生成此文章的赫夫曼编码到文件b.txt中。
void read_creat_code(char * * HC, int n, char * ture_form) {
FILE * fp_r = fopen("a.txt", "r");
FILE * fp_w = fopen("b.txt", "w");
char c = fgetc(fp_r);
int i, j;
while (c != EOF) {
for (i = 1; i <= n; i++) {
if (c == ture_form[i]) //判断当前读入的字符在原码中的位置
break;
}
c = HC[i][0];
for (j = 1; c != '\0'; j++) {
fputc(c, fp_w);
c = HC[i][j];
}
c = fgetc(fp_r);
}
fclose(fp_r);
fclose(fp_w);
}
//判断当前存储在数组code中的编码是否为有效编码
int judge_valid(char ** HC, char code[], int n) {
for (int i = 1; i <= n; i++)
if (strcmp(code, HC[i]) == 0)
return i;
return 0;
}
//译码
void decode(char ** HC, int n, char ture_form[]) {
FILE * fpr, *fpw;
fpr = fopen("b.txt", "r");
fpw = fopen("c.txt", "w");
char code[10] = { 0 }; //默认所有字符的编码长度不超过9,(字符数组最后一个元素为'\0')
for (int i = 0; i < 10; i++) {
if ((code[i] = fgetc(fpr)) == EOF)
break; //已经读到文件的末尾
int state; //若当前编码无效,则state为0,否则为当前编码对应字符在数组ture_form中的位置
state = judge_valid(HC, code, n);
if (state != 0) { //若编码有效,则把对应字符写到文件里,并让下一个读到的赫夫曼码放到code[0]里面
fputc(ture_form[state], fpw);
i = -1;
for (int j = 0; j < 10; j++) //清空此临时数组
code[j] = '\0';
}
}
fclose(fpr);
fclose(fpw);
}
int main()
{
int n; //总共出现了n种不同的字符
int m; //所需总的结点个数
int i; //临时变量
char * ture_form; //文章中出现的字符用此数组存储
char character[55]; //存储所有可能出现的字符
int char_freque[55] = { 0 }; //记录每个字符出现的次数
Node * HT; //结点类型的指针,用来指向Huffman树的m个结点
//初始化文章中所有可能出现的字符
for (i = 0; i <= 25; i++)
character[i] = i + 97;
for (i = 26; i <= 51; i++)
character[i] = i + 39;
character[52] = ',';
character[53] = ' ';
character[54] = '.';
Statis_char(char_freque); //统计每个字符出现的次数
n = Statis_n(char_freque); //统计总共出现了多少种不同的字符
m = 2 * n - 1; //构建此Huffman树所需的节点个数
HT = (Node *)malloc((m + 1) * sizeof(Node)); //第零个存储空间不用
ture_form = (char *)malloc((n + 1) * sizeof(char)); //共有n种不同的字符,这n种字符用数组ture_form存储,第零个存储空间不用
Init_tree(HT, ture_form, char_freque, character, m); //初始化Huffman树的每个结点
Creat_HT(HT, n, m); //建立Huffman树
char * * HC; //HC[i]指向第i个字符的Huffman编码
HC = (char * *)malloc((n + 1) * sizeof(char *)); //第0个存储空间不用
HC = Huf_code(HT, n); //对n个字符进行编码
read_creat_code(HC, n, ture_form); //读取文章,并对整篇文章进行编码
decode(HC, n, ture_form); //读取Huffman编码并译码
}