课程要求:
本项目为哈夫曼编码的一个实例应用,实现英文字符的编码与解码。即针对一篇不少于100个单词的英文文章,统计文章中每个字符的出现概率(包括标点符号,区分大小写),根据分析结果对文章中每一个字符进行哈夫曼编码,并将编码原则存储于一个独立的文本文件中。然后,根据这个编码原则,将英文文章转换为01串存储于另一个文本文件中。最后,编写一个解码程序,还原文本文件中的01串为原英文文章。
应用哈夫曼算法实现如下基本功能:计算英文文章中每个字符的出现概率;计算英文文章中出现字符的哈夫曼编码;存储编码原则于txt文件中;英文文章转换为01串并存储;对01串进行解码转换为原英文文章。
要求系统运行正常、功能完整;数据结构使用得当,算法有较高的效率;代码规范、可读性高,结构清晰;具备一定的健壮性、可靠性和可维护性。
设计流程:
读取文件:通过ReadFile函数,从指定路径的文件中读取内容,并将内容存入FileArray数组中。
权值计算:通过Weight函数,统计FileArray数组中每个字符的出现次数,并将结果存入weight数组中。
创建哈夫曼树和编码:通过CreateHaffTree函数,根据权值数组weight创建哈夫曼树,并生成对应的哈夫曼编码,存储在HT和HC数组中。
文件编码:通过BuildFileCode函数,将FileArray数组中的每个字符根据哈夫曼编码转换为对应的编码串,并存储在FileCode数组中。
文件解码:通过DecodeHaffTree函数,根据哈夫曼树HT和FileCode数组中的编码串,将编码串解码为原始字符序列,并存储在_FileArray_数组中。
文件写入:通过WriteFile函数,将_FileArray_数组中的内容写入指定路径的文件中。
代码如下:
结构体:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define ASCIISize 127
//储存哈夫曼树
typedef struct HTNode {
int flag; //叶子节点的标志位,0 代表不是叶子节点,1 代表是叶子节点
int parent; //当前节点的父节点
int leftChild; //当前节点的左子
int rightChild; //当前节点的右子
int weight; //当前节点的权值
char ch; //当前节点代表的字母
}*HuffmansTree;
子函数:
//储存哈夫曼编码
typedef char** HuffmanCode;
//储存读取的文件
char* FileArray;
//储存读取的文件的数组长度
int FileArraySize = 200;
//储存每个ASCII码相应的权值
int weight[ASCIISize];
//储存哈夫曼树
HuffmansTree HT;
//储存哈夫曼编码
HuffmanCode HC;
//储存文章的哈夫曼编码
char** FileCode;
//储存文章的哈夫曼编码
int FileCodeSize = 20;
//储存解码后的文件
char* _FileArray_;
//储存解码后的文件的数组长度
int _FileArraySize_ = 200;
//读取文件,将文件内容存入FileArray数组中
void ReadFile(char path[], char way[], char*& FileArray, int& FileArraySize);
//求每个ASCII码对应字符的权值,将权值存入weigh数组中
void Weight(char* FileArray, int weight[ASCIISize]);
//根据权值,创建哈夫曼树,求哈夫曼编码
void CreateHaffTree(int weight[ASCIISize], HuffmansTree& HT, HuffmanCode& HC);
//在HT[1..i-1]中选择两个parent为0且weight最小的,序号为s1,s2
void _Select(HuffmansTree HT, int i, int& s1, int& s2);
代码主体:
//在HT[1..i]中选择两个parent为0且weight最小的,序号为s1,s2
void _Select(HuffmansTree HT, int i, int& s1, int& s2) {
int j;
int w1, w2, s;
//选出最小 s1
for (j = 1; j <= i; j++) //找到第一个具有根节点为 0 的节点
if (HT[j].parent == 0) break;
s1 = j;
w1 = HT[j].weight;
for (j++; j <= i; j++)
if (HT[j].parent == 0 && HT[j].weight < w1)
s1 = j, w1 = HT[j].weight;
//选出次小 s2
for (j = 1; j <= i; j++)
if (HT[j].parent == 0 && j != s1) break;
s2 = j;
w2 = HT[j].weight;
for (j++; j <= i; j++)
if (HT[j].parent == 0 && j != s1 && HT[j].weight < w2)
s2 = j, w2 = HT[j].weight;
//保持s1<s2
if (s1 > s2)
s = s1, s1 = s2, s2 = s;
}
//读取文件,存入FileArray 数组中
void ReadFile(char path[], char way[], char*& FileArray, int& FileArraySize) {
//path文件位置,way文件打开方式(r,w,a...),文件中读取的数据放入FileArray数组中
FILE* fp;
int i = 0;
//根据路径和打开方式打开指定的文件,判断操作是否成功
if ((fp = fopen(path, way)) == NULL) {
printf("can't open the file");
}
FileArray = (char*)malloc(FileArraySize * sizeof(char));
char ch;
while ( EOF != (ch = fgetc(fp)) ) { //改了一下!!@@
//如果条件满足,表示 FileArray数组不足以容纳更多的字符,需要进行扩展
// 函数重新分配 FileArray 的内存空间,将其大小扩大为原来的两倍。重新分配空间,防止文章字数超了本来设定的200
if (i + 1 == FileArraySize) //当 i 达到 FileArraySize - 1 时,表示数组 FileArray 即将填满,再读取一个字符就超出了数组的边界。
{
FileArray = (char*)realloc(FileArray, FileArraySize * 2 * sizeof(char));
FileArraySize *= 2;
}
FileArray[i] = ch;
i++;
}
FileArraySize = i;
fclose(fp); //完成读取以后将文件关闭
}
//将FileArray 数组中的字符-->ASCLL码值-->统计次数-->weight数组里的权值
//在C/C++ 中,字符是以 ASCII 码的形式存储的。ASCII码(表示字符的标准编码系统),每个字符都有一个对应的 ASCII 码值。
void Weight(char* FileArray, int weight[ASCIISize]) {
for (int i = 0, m; i < FileArraySize; i++)
{
//FileArray[i]表示字符值。通过将其减去字符 '0'(ASCII码为 48),可以将字符的数值形式转换为对应的 ASCII 码值。
m = FileArray[i] - 0; 这里为啥不是 m = FileArray[i] - "0";
weight[m]++;
}
}
//根据权值,创建哈夫曼树HT[ch = 对应的ASCLL码],求哈夫曼编码HC[]
void CreateHaffTree(int weight[ASCIISize], HuffmansTree& HT, HuffmanCode& HC) {
// int _Select(HuffmansTree, int, int&, int&);
int i, m, s1, s2, c, f, start;
HuffmansTree p;
char* cd;
//初始化
//字符数=ASCLL码数
m = 2 * ASCIISize - 1;
HT = (HuffmansTree)malloc((m + 1) * sizeof(HTNode)); //0号单元未用,所以有 2 * ASCLLSize(m+1)
for (p = HT + 1, i = 1; i <= ASCIISize; ++i, ++p) //生成n个叶子结点 (flag = 1, ch = 字符对应的ASCLL码)
p->weight = weight[i], p->flag = 1, p->parent = p->leftChild = p->rightChild = 0, p->ch = i;
for (; i <= m; ++i, ++p)
p->flag = p->weight = p->parent = p->leftChild = p->rightChild = 0, p->ch = -1;
//构造哈夫曼树
for (i = ASCIISize + 1; i <= m; ++i) {
//在HT[1..i-1(ASCIISize)]中选择两个parent为0且weight最小的,序号为s1,s2
_Select(HT, i - 1, s1, s2); //_Select函数这里用i-1原因是: 挑选时的范围是动态变化的,生成一个新根,挑选范围加一,所以(i-1)不能替换为ASCLLSize
//i 从 ASCLLSize + 1 开始
//改变s1, s2的parent 和 新根 i 的LC,RC,Weight
HT[s1].parent = HT[s2].parent = i;
HT[i].leftChild = s1; HT[i].rightChild = s2;
HT[i].weight = HT[s1].weight + HT[s2].weight;
}
//求哈夫曼编码 //(for循环求n个编码,while循环回溯直到f != 0)
HC = (HuffmanCode)malloc((ASCIISize + 1) * sizeof(char*)); //分配n个字符编码的头指针矢量
cd = (char*)malloc(ASCIISize * sizeof(char)); //临时空间,分配临时放编码的 动态数组空间
for (i = 1; i <= ASCIISize; ++i){ //有ASCLLSize个字符,依次求哈夫曼编码
start = ASCIISize - 1;
cd[start] = '\0';
c = i, f = HT[c].parent; //f是结点c的根
while (f != 0){ //从叶子结点往上回溯,直到根为0
if ( c == HT[f].leftChild ) //若结点c是f的左孩子,则生成代码0
cd[--start] = '0';
else
cd[--start] = '1';
c = f, f = HT[f].parent; //继续向上回溯
}
HC[i] = (char*)malloc((ASCIISize - start + 1) * sizeof(char)); // 为第i 个字符串编码分配空间,HC数组后面的链数组
strcpy(HC[i], &cd[start]); //将求得的编码从临时空间cd[],复制到HC[]的链数组中
}
free(cd); //释放临时空间
}
//对文章编码,存入FileCode (二维字符)数组中
void BuildFileCode(char**& FileCode, int& FileCodeSize) {
FileCode = (char**)malloc(FileCodeSize * sizeof(char*)); // FileCode[行]->完整编码, FileCode[行][列]->编码特定字符
int n = 0, m = 0, i, j;
//遍历整个 FileArray 数组,其中存储了待编码的字符
for (i = 0; i < FileArraySize; i++, n++) {
m = FileArray[i] - 0;
if (n >= FileCodeSize - 1) { //若超出空间,重新分配
FileCode = (char**)realloc(FileCode, FileCodeSize * 2 * sizeof(char*));
FileCodeSize *= 2;
}
FileCode[n] = HC[m]; //将在HC 数组中的哈夫曼编码赋值给 FileCode 数组的相应位置
}
FileCode[n] = "###"; //将结束标记 "###" 存在数组的最后
FileCodeSize = n;
for (int i = 0; i < --n; i++)
printf("%s", FileCode[i]);
}
//(根据哈夫曼树)解码,存入_FileArray_ 数组
void DecodeHaffTree(HuffmansTree HT, char** FileCode, char*& _FileArray_, int& _FileArraySize_) {
int root, p, j = 0;
p = root = 2 * ASCIISize - 1; //设置遍历开始,根节点
printf("解码后的字符串为:\n");
_FileArray_ = (char*)malloc(FileCodeSize * sizeof(char));
for (int i = 0; FileCode[i] != "###"; i++) {
int m = strlen(FileCode[i]);
for (int k = 0; k < m; k++) {
char fcs = *(FileCode[i]++);
if (fcs == '0') { //为0, p = p的左孩子
p = HT[p].leftChild;
}
else if (fcs == '1') { //为1, p = p的右孩子
p = HT[p].rightChild;
}
if (HT[p].flag == 1) { //叶子节点,将对应的ASCLL码存储到 _FileArray_ 数组中
_FileArray_[j++] = HT[p].ch;
printf("%c", HT[p].ch);
p = root; //重新回到根节点,继续解码
}
}
}
}
//储存文件,将array数组中的元素存入文件中,array包括FileCode和_FileArray
void WriteFile(char path[], char way[], char* array) {
FILE* fp;
fp = fopen(path, way);
fputs(array, fp);
fclose(fp);
}
int main(void) {
ReadFile("D:\\read4.txt", "r", FileArray, FileArraySize);
Weight(FileArray, weight);
CreateHaffTree(weight, HT, HC);
BuildFileCode(FileCode, FileCodeSize);
DecodeHaffTree(HT, FileCode, _FileArray_, _FileArraySize_);
WriteFile("D:\\write4.txt", "a", _FileArray_);
getchar();
}