-
实验目的
熟悉掌握Huffman树的构造方法及Huffman编码的应用,了解Huffman树在通信、编码领域的应用过程。
-
实验内容
(1)输入一段100—200字的英文短文,存入一文件a中。
(2)写函数统计短文出现的字母个数n及每个字母的出现次数
(3)写函数以字母出现次数作权值,建Haffman树(n个叶子),给出每个字母的Haffman编码。
(4)用每个字母编码对原短文进行编码,码文存入文件b中。
(5)用Haffman树对文件b中码文进行译码,结果存入文件c中,比较a,c是否一致,以检验编码、译码的正确性。
-
问题描述
(说明你选做的题目及要求)
输入一段100—200字的英文短文,存入一文件a中。写函数统计短文出现的字母个数n及每个字母的出现次数写函数以字母出现次数作权值,建Haffman树(n个叶子),给出每个字母的Haffman编码。用每个字母编码对原短文进行编码,码文存入文件b中。用Haffman树对文件b中码文进行译码,结果存入文件c中,比较a,c是否一致,以检验编码、译码的正确性。
-
数据结构定义
(说明你算法中用到的数据结构、数据类型的定义)
定义哈夫曼树对于每个节点而言,既然需要双亲的信息,又需要知道孩子结点的信息。,每一个结点用结构体定义,里面包含此结点的权值weight,父结点parent,左孩子lchild,右孩子rchild。
Typedef struct{
Unsigned int weight;
Unsigned int parent ,lchild,rchild;
};HTNode, *Huffmantree;
Typedef char * *HuffmanCode;
-
算法思想及算法设计
(先文字说明算法的思想,然后给出类C语言算法)
由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个结点为此哈夫曼树的根结点。
void Creat_HT(Node * HT, int n, int m) {
int i; 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;}
}
对n个字符进行编码。采用逆序编码的方法,即从叶子结点出发,寻找根结点。例如第i个叶子结点中存储着它的双亲信息,访问第i个叶子节点的双亲p(注:双亲其实就是教材中的父节点,它是一个节点,而不是两个),判断此叶子结点是它双亲结点的左孩子还是右孩子,若为左孩子,则编码为0,右孩子编码为1。下一步继续往上访问,直到访问到根结点结束。
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; start; 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;}
读取文章,生成此文章的Huffman编码。每读到一个字符,先判断此字符的编码在数组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);}
读取编码,进行译码过程。从b.txt文件中读取编码,每次读取一个字符(此字符为0或1),并把此字符存放在一个临时数组中。判断当前数组中的字符是否为有效的Huffman编码,若是,则翻译此编码,并把翻译得到的字符写到文件c.txt中,清空临时数组,继续读取b.txt中的下一个字符。若不是有效编码,则继续读取b.txt中的下一个字符。
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);
}
-
实验代码
(即C语言程序)
#include <stdlib.h>
#include<string.h>
#include <stdio.h>
#include <malloc.h>
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");
// return 0;
}
}
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编码并译码
}
-
算法测试结果
(说明测试数据,粘贴实验结果图)
-
分析与总结
(1)算法复杂度分析及优、缺点分析
(说明你编写算法的复杂度,算法的优点和缺点有哪些)
文章中只能含有52个大小写字母,逗号,空格,句号这55种常用字符。其余的符号太多,未写进程序。文章不能太长,因为我默认所有字符的权值不超过2000,Huffman编码长度不超过9位
1.将所有结点放入集合 K。若集合 K 中剩余结点大于 2 个,则取出其中权值最小的两个结点,构造他们同时为某个新节点的左右儿子,该新节点是他们共同的双亲结点,设定它的权值为其两个儿子结点的权值和。并将该父亲结点放入集合 K。重复步骤。若集合 K 中仅剩余一个结点,该结点即为构造出的哈夫曼树数的根结点, 所有构造得到的中间结点(即哈夫曼树上非叶子结点)的权值和即为该哈夫曼树的带权路径和。为了方便快捷高效率的求得集合 K 中权值最小的两个元素,我们需要使用堆数据结构。它可以以 O(logn)的复杂度取得 n 个元素中的最小元素。
(2)实验总结
(说明你怎么解决实验中遇到的问题,有什么收获)
为了避免a.txt文件加错位置而引发断点,更新后的程序中添加了判断文件是否成功打开的代码,若文件打开失败,则输出提示信息,并退出程序的执行。熟悉掌握Huffman树的构造方法及Huffman编码的应用,了解Huffman树在通信、编码领域的应用过程。同时回顾前边的对于文件的操作和使用,巩固了知识