主要目的是从文件中读取文本,经过哈夫曼算法编码成为01字符串,之后再将字符串解码。
强调的是不限制文章的大小,理论上【注】可以将一篇非常大的英文文章进行编码解码(当时做设计的时候从百度的一部分例子都是用的数组,限制文章的字数,所以我有了这个想法),同样也可以对较小的文章进行编码解码,而且不占用多余的空间。
(【注】:这里的理论上是指程序运行后文章内容不可变,且硬件支持)
先贴出要求:
哈夫曼编码的一个实例应用,实现英文字符的编码与解码。即针对一篇不少于100个单词的英文文章,统计文章中每个字符的出现概率(包括标点符号,区分大小写),根据分析结果对文章中每一个字符进行哈夫曼编码,并将编码原则存储于一个独立的文本文件中。然后,根据这个编码原则,将英文文章转换为01串存储于另一个文本文件中。最后,编写一个解码程序,还原文本文件中的01串为原英文文章。
应用哈夫曼算法实现如下基本功能:计算英文文章中每个字符的出现概率;计算英文文章中出现字符的哈夫曼编码;存储编码原则于txt文件中;英文文章转换为01串并存储;对01串进行解码转换为原英文文章。
要求系统运行正常、功能完整;数据结构使用得当,算法有较高的效率;代码规范、可读性高,结构清晰;具备一定的健壮性、可靠性和可维护性。
程序中部分算法和代码直接引用自《严蔚敏,吴伟民.数据结构(C语言版).北京:清华大学出版社,2011》。
程序中用的指针动态申请内存空间,储存要编码的英文文章,哈夫曼树等;用二级指针储存编码后的01字符串
下面直接上代码
huffman.h
#include
#include
#include
#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;
//在HT[1..i-1]中选择两个parent为0且weight最小的,序号为s1,s2
void _Select(HuffmansTree HT, int i, int& s1, int& s2);
//读取文件,并将文件中内容存入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);
huffman.cpp
#include "huffman.h"
//在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++)
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].weights2)
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 ((ch = fgetc(fp)) != EOF){
if (i + 1 == FileArraySize){
FileArray = (char*)realloc(FileArray, FileArraySize * 2 *sizeof(char));
FileArraySize *= 2;
}
FileArray[i] = ch;
i++;
}
FileArraySize = i;
fclose(fp); //完成读取以后将文件关闭
}
//求每个ASCII码对应字符的权值,并将权值存入weigh数组中
void Weight(char* FileArray, int weight[ASCIISize]){
for (int i = 0,m; i < FileArraySize; i++){
m = FileArray[i] - 0;
weight[m]++;
}
}
//根据每个字母的权值,创建哈夫曼树,求出哈夫曼编码
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;
//初始化
m = 2 * ASCIISize - 1;
HT = (HuffmansTree)malloc((m + 1)*sizeof(HTNode)); //0号单元未用
for (p = HT + 1, i = 1; i <= ASCIISize; ++i, ++p) //生成n个叶子结点 NOTE:课本有误
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]中选择两个parent为0且weight最小的,序号为s1,s2
_Select(HT, i - 1, s1, s2);
//用s1和s2作为左右子树和并成一棵树i
HT[s1].parent = HT[s2].parent = i;
HT[i].leftChild = s1; HT[i].rightChild = s2;
HT[i].weight = HT[s1].weight + HT[s2].weight;
}
//求哈夫曼编码
HC = (HuffmanCode)malloc((ASCIISize + 1)*sizeof(char *));
cd = (char *)malloc(ASCIISize*sizeof(char)); //临时工作空间
for (i = 1; i <= ASCIISize; ++i){
start = ASCIISize - 1;
cd[start] = '\0';
for (c = i, f = HT[i].parent; f != 0; c = f, f = HT[f].parent)
if (HT[f].leftChild == c)
cd[--start] = '0';
else
cd[--start] = '1';
HC[i] = (char*)malloc((ASCIISize - start + 1)*sizeof(char));
strcpy(HC[i], &cd[start]);
}
free(cd);
}
//给据已得到的每个字符的哈夫曼编码对整篇文章进行编码,结果存入数组中
void BuildFileCode(char** &FileCode, int &FileCodeSize){
FileCode = (char**)malloc(FileCodeSize *sizeof(char *));
int n = 0,m = 0,i,j;
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];
}
FileCode[n] = "###";
FileCodeSize = n;
for (int i = 0; i < --n; i++)
printf("%s", FileCode[i]);
}
//根据哈夫曼树进行哈夫曼解码,结果存入数组返回
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 = HT[p].leftChild;
}
else if (fcs == '1'){ //如果当前字符为 1 ,则当前节点为原节点的右子
p = HT[p].rightChild;
}
if (HT[p].flag == 1){ //如果当前节点既没有左子,又没有右子,则将对应的字符存入编码数组
_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("F:\\read.txt", "r", FileArray, FileArraySize);
Weight(FileArray, weight);
CreateHaffTree(weight,HT, HC);
BuildFileCode(FileCode, FileCodeSize);
DecodeHaffTree(HT, FileCode, _FileArray_, _FileArraySize_);
WriteFile("F:\\write.txt", "a", _FileArray_);
getchar();
}
程序中只做了对解码后文章的储存,没有做对编码字符串和哈夫曼树的储存
程序中各种命名混乱加不规则,各位就将就着看看吧