赫夫曼编码(综合性实验)
1. 需求分析
需求:
设某编码系统共有n个字符,使用频率分别为w1, w2, …, wn,设计一个不等长的编码方案,使得该编码系统的空间效率最好。
基本要求:
一个完整的系统应具有以下功能。
(1) I:初始化(Initialization)。从终端读入字符集大小n,以及n个字符和n个权值,建立赫夫曼树,并将它存于文件hfmTree中。
(2) E:编码(Encoding)。利用已建好的赫夫曼树对文件ToBeTran中的正文进行编码,然后将结果存入文件CodeFile中。
(3) D:译码(Decoding)。利用已建好的赫夫曼树将文件CodeFile中的代码进行译码,结果存入文件TextFile中。
(4) P:打印代码文件(Print)。将文件CodeFile以紧凑格式显示在终端上,每行50个字符。同时将此字符形式的编码文件写入文件CodePrint中。
(5) T:打印赫夫曼树(Tree printing)。将已在内存中的赫夫曼树以直观的方式显示在终端上,同时将此字符形式的赫夫曼树写入文件TreePrint中。
测试数据
由读者依据软件工程的测试技术自己确定。注意测试边界数据。
实现提示
利用赫夫曼编码树求得 佳的编码方案。
(1) 文件 CodeFile 的基类型可以设为字节型。
(2) 用户界面可以设计为“菜单”方式,除显示上述功能符号外,还应显示“Q”(Quit),表示退出运行。请用户键入一个选择功能符。此功能执行完毕后再显示此菜单,直至某次用户选择了“E” 为止。在程序的一次执行过程中,第一次执行 I、D 或 C 命令之后,赫夫曼树已经在内存了,不必再读入。每次执行时不一定执行 I 命令,因为文件 hfmTree 可能早已建好。
2. 概要设计
为了实现程序功能,需要定义树的抽象数据类型。
ADT BinaryTree{
数据对象D:D是具有相同特性的数据元素的集合
数据关系R:
若D = Φ,则R = Φ,称BinaryTree为空二叉树
若D ≠ Φ,则R = {H},H有如下二元关系:
(1)在D中存在唯一的称为根的数据元素root,它在关系H下无前驱;
(2)若D-{root} ≠ Φ,则存在D - {root} = {Dl,Dr},且Dl ∩ Dr = Φ;
(3)若Dl ≠ Φ,则Dl中存在唯一的元素xl,<root,xl>∈H,且存在Dl上的关系Hl⊂H;
若Dr ≠ Φ,则Dr中存在唯一的元素xr,<root,xr>∈H,且存在Dr上的关系Hr⊂H;
H = {<root,xl>,<root,xr>,Hl,Hr};
(4)(Dl,{Hl})是一棵符合本定义的二叉树,称为根的左子树,
(Dr,{Hr})是一棵符合本定义的二叉树,称为根的右子树;
typedef struct{
int weigth; //权值
int parent; //父母
int lchild; //左儿子
int rchild; //右儿子
}HTNode,*HuffmanTree; //哈夫曼树结构
基本操作P:
InitBiTree(&T);
操作结果:构造空的二叉树T。
DestoryBiTree(&T);
初始条件:二叉树T存在。
操作结构:销毁二叉树T。
CreateBiTree(&T,definition);
初始条件:definition给出二叉树T的定义。
操作结果:按definition构造二叉树T。
ClearBiTree(&T);
初始条件:二叉树T存在。
操作结果:将二叉树清为空树。
BiTreeEmpty(T);
初始条件:二叉树T存在。
操作结果:若T为空二叉树,返回TRUE,否则返回FALSE。
BiTreeDepth(T);
初始条件:二叉树T存在。
操作结果:返回T的深度。
Root(T);
初始条件:二叉树T的根存在。
操作结果:返回T的根。
Value(T,e);
初始条件:二叉树T存在,e是T中某个结点。
操作结果:返回e的值。
Assign(T,&e,value);
初始条件:二叉树T存在,e是T中的某个结点。
操作结果:结点e赋值为value。
Parent(T,e);
初始条件:二叉树T存在,e是T中的结点。
操作结果:若e是T的非根结点,则返回它的双亲,否则返回"空"。
LeftChild(T,e);
初始条件:二叉树T存在,e是T中的某个结点。
操作结果:返回e的左孩子。若e无左孩子,则返回"空"。
RightChild(T,e);
初始条件:二叉树T存在,e是T中的某个结点。
操作结果:返回e的右孩子。若e无右孩子,则返回"空"。
LeftSibling(T,e);
初始条件:二叉树T存在,e是T中的某个结点。
操作结果:返回e的左兄弟。若e是T的左孩子或无左兄弟,则返回"空"。
RightSibling(T,e);
初始条件:二叉树T存在,e是T中的某个结点。
操作结果:返回e的右兄弟。若e是T的右孩子或无右兄弟,则返回"空"。
InsertChild(T,p,LR,c);
初始条件:二叉树T存在,p指向T中的某个结点,LR为0或1,非空二叉树c与T不想交且右子树为空。
操作结果:根据LR为0或1,插入c为T中p所指结点的左或右子树。p所指结点的原有左或右子树则成为c的右子树。
DeleteChile(T,p,LR);
初始条件:二叉树T存在,p指向T中的某个结点,LR为0或1.
操作结果:根据LR为0或1,删除T中p所指结点的左或右子树。
PreOrderTraverse(T,visit());
初始条件:二叉树T存在,visit()是对结点操作的应用函数。
操作结果:先序遍历T,对每个结点调用函数visit一次且仅一次。一旦visit失败,则操作失败。
InOrderTraverse(T,visit());
初始条件:二叉树T存在,visit()是对结点操作的应用函数。
操作结果:中序遍历T,对每个结点调用函数visit一次且仅一次。一旦visit失败,则操作失败。
PostOrderTraverse(T,visit());
初始条件:二叉树T存在,visit()是对结点操作的应用函数。
操作结果:后序遍历T,对每个结点调用函数visit一次且仅一次。一旦visit失败,则操作失败。
LevelOrderTraverse(T,visit());
初始条件:二叉树T存在,visit()是对结点操作的应用函数。
操作结果:层序遍历T,对每个结点调用函数visit一次仅且一次。一旦visit失败,则操作失败。
}ADT BinaryTree
3.具体代码
- 哈夫曼树.h:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
//#include"哈夫曼树.h"
typedef struct{
int weigth; //权值
int parent; //父母
int lchild; //左儿子
int rchild; //右儿子
//静态三叉链表 【用数组来表示链接关系】
}HTNode,*HuffmanTree; //哈夫曼树结构
void select(HuffmanTree p,int n,int *a,int *b) //选择权值最小的
{
*a = 0;
*b = 0;//两个指针先初始化
for(int z = 1;z <= n;z++)//遍历
{
if(p[z].parent == 0&&*a == 0)//如果父节点为空,且*a还没赋值
{
*a = z;
continue;
}
if(p[z].parent == 0)
{
*b = z;
break;
}
}
if(p[*a].weigth >= p[*b].weigth)//a的权值大于等于b
{
int i = *a;
*a = *b;
*b = i;//a,b互换
}
for(int m = 1;m <= n;m++)//遍历
{
if(p[m].parent != 0)//如果父节点已经不为0了
{
continue;//跳过 (舍弃掉)
}
if(p[*a].weigth > p[m].weigth&&*a != m&&*b != m)
{
*b = *a;
*a = m;
}
else if(p[*b].weigth > p[m].weigth&&*b != m&&*a != m)
{
*b = m;
}
}
}
void HuffmanCoding(HuffmanTree *HT,int *w,int n) //建哈夫曼树
{
if(n <= 1)
{
return;//如果不超过1个字符,直接返回
}
int m = 2 * n - 1;//n个叶子节点,需要结合n-1次,共有2n-1个节点
*HT = (HuffmanTree)malloc((m + 1) * sizeof(HTNode));//为树分配 空间 (第一个位置不用)
int i = 1;//用于for循环
HuffmanTree p = *HT + 1;//指向已有的树的后一个空间 (用于遍历树)
for(;i <= n;++i,++p,++w)//遍历
{
//对前n个节点位置,用来放有效的编码
p->lchild = 0;
p->parent = 0;
p->rchild = 0;
p->weigth = *w;//初始化
}
for(;i <= m;++i,++p)
{
//n之后的位置 ,放用来结合的空根
p->lchild = 0;
p->parent = 0;
p->rchild = 0;
p->weigth = 0;
}
for(int k = n + 1;k <= m;++k)
{
//n+1的空根开始遍历,直到所有的根遍历完成
int s1,s2;
select(*HT,k - 1,&s1,&s2);//k-1代表的是选择的上限位置
(*HT)[s1].parent = k;
(*HT)[s2].parent = k;//把选中的两个权值最小的置为k的左右孩子
(*HT)[k].lchild = s1;
(*HT)[k].rchild = s2;
(*HT)[k].weigth = (*HT)[s1].weigth + (*HT)[s2].weigth;//修改k的权值
}
}
void coding(char ***p,int n,HuffmanTree t) //编码
{
*p = (char **)malloc((n + 1) * sizeof(char *));//分配空间
char *cd;
cd = (char *)malloc(n * sizeof(char));//分配空间
cd[n - 1] = '\0'; //字符串
for(int i = 1;i <= n;++i)//遍历
{
int start = n - 1;
for(int c = i,f = t[i].parent;f != 0;c = f,f = t[f].parent)
{
if(t[f].lchild == c)
{
//左孩子就赋值为0
cd[--start] = '0';
}
else
{
//右孩子就赋值为1
cd[--start] = '1';
}
}
(*p)[i] = (char*)malloc((n - start)*sizeof(char));
strcpy((*p)[i],&cd[start]);
}
free(cd);
}
void decoding(FILE *r,FILE *w,HuffmanTree t,int n,char *a) //译码
{
int num = 2 * n - 1;//根节点
for(int i;fscanf(r,"%1d",&i) != EOF;)
{
if(i == 0)
{
int j = t[num].lchild;//0就是左孩子
if(t[j].lchild == 0)//如果已经没有子树了(此处不一定为左孩子,因为左右成对出现)
{
fprintf(w,"%c",a[j - 1]);//输出
num = 2 * n - 1;//重新给num
}
else
{
num = j;//如果还有左孩子,又继续从j的位置往下
}
}
else if(i == 1)
{
int j = t[num].rchild;//1就是右孩子
if(t[j].rchild == 0)//如果已经没有子树了(此处不一定为右孩子,因为左右成对出现)
{
fprintf(w,"%c",a[j - 1]);//输出
num = 2 * n - 1;//重新给num
}
else
{
num = j;
}
}
}
}
- 哈夫曼编码.c:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include"哈夫曼树.h"
void visit(HuffmanTree t,FILE *w,int n) //打印哈夫曼树
{
HuffmanTree p = t + 1;
for(int m = 0;m < 2 * n - 1;m++)
{
printf("%d\t%d\t%d\t%d\n",p->weigth,p->parent,p->lchild,p->rchild);
fprintf(w,"%d %d %d %d\n",p->weigth,p->parent,p->lchild,p->rchild);
++p;
}
}
void read_weigth(int *w,char *ch,int number) //读取边
{
char q;
for(int n = 0;n < number;n++)
{
scanf("%d%c",&w[n],&ch[n]);//权值-字符
scanf("%c",&q);//空格
//权值存在w,字符存在ch
}
}
void found(FILE *r1,FILE *w2,char **p,int n,char *zh) //将字符串转化为二进制代码
{
//r1为原文,w2为编码加密之后的文,r2为哈夫曼树
char ch;
// fscanf(r2,"%s",zh);//先读取全部的编码
for(;(fscanf(r1,"%c",&ch)) != EOF;)
{//读取原文
int i=0;
while(zh[i] != ch)//找到对应的编码
{
i++;
}
fprintf(w2,"%s",p[i+1]);//写到r2文件
}
}
int main()
{
FILE *ri,*wi,*re,*we,*rd,*wd,*wp,*wt;//定义一系列文件指针
HuffmanTree t;//创建一个哈夫曼树
int n;//字符集的大小
char name[500];
for(;;)
{
printf("\n请选择: I:初始化 E:编码 D:译码 P:打印代码文件 T:打印赫夫曼树 Q:退出\n");
char select;//用来存放命令
scanf("%c",&select);
getchar();
switch(select)
{
case 'Q': //退出
return 0;
case 'I': //初始化
do
{
if((wi = fopen("hfmTree.txt","w")) == NULL)
{
exit(-1);
}
printf("请输入字符集大小:");
scanf("%d",&n);
getchar();
int w[n];//创建一个大小为n的数组,储存加权字符
char ch[n+1];
ch[n] = '\0';//为字符串
printf("请输入%d个权值和字符:",n);
read_weigth(&w[0],&ch[0],n);//读取节点
HuffmanCoding(&t,&w[0],n);//哈夫曼编码
strcpy(name,ch);
// printf("%s\n",ch);
// printf("%s\n",name);
fprintf(wi,"字符集长度为:%d\n",n);
// fprintf(wi,"%s\n",ch);
for(int i = 0;i < n;i++)
{
fprintf(wi,"%d. ",i+1);
fprintf(wi,"字符%c对应的权值为%d \n",ch[i],w[i]);
}
fclose(wi);
printf("初始化成功!结果存在文件'hfmTree.txt'中!\n");
// fprintf(wi,"%s\n",ch);
// fprintf(wi,"%d\n",n);
// for(int i = 0;i < n;i++)
// {
// fprintf(wi,"%d ",w[i]);
// }
// fclose(wi);
// printf("初始化成功!结果存在文件'hfmTree.txt'中!\n");
}while(0);
break;
case 'E': //编码
do
{
printf("请将文件放入'ToBeTran.txt'中(放好请按ENTER继续):");
getchar();
if((ri = fopen("hfmTree.txt","r")) == NULL)
{
exit(-1);
}
if((re = fopen("ToBeTran.txt","r")) == NULL)
{
exit(-1);
}
if((we = fopen("CodeFile.txt","w")) == NULL)
{
exit(-1);
}
char **dh;
coding(&dh,n,t);//编码 ,dh为一个数组,n为字符集的长度,t为一个哈夫曼树
found(re,we,dh,n,name);
fclose(ri);
fclose(we);
fclose(re);
printf("编码成功。结果存在文件'CodeFile.txt'中。\n");
}while(0);
break;
case 'D': //译码
do
{
printf("请将文件放入'CodeFile.txt'中(放好请按ENTER继续):");
getchar();
if((rd = fopen("CodeFile.txt","r")) == NULL)
{
exit(-1);
}
if((wd = fopen("TextFile.txt","w")) == NULL)
{
exit(-1);
}
if((ri = fopen("hfmTree.txt","r")) == NULL)
{
exit(-1);
}
char zh[n + 1];
// fscanf(ri,"%s",zh);
decoding(rd,wd,t,n,name);
fclose(rd);
fclose(wd);
fclose(ri);
printf("译码成功。结果存在文件'TextFile.txt'中。\n");
}while(0);
break;
case 'P': //打印代码文件
do
{
if((ri = fopen("hfmTree.txt","r")) == NULL)
{
exit(-1);
}
if((rd = fopen("CodeFile.txt","r")) == NULL)
{
exit(-1);
}
if((wp = fopen("CodePrint.txt","w")) == NULL)
{
exit(-1);
}
char **dh;
coding(&dh,n,t);
// char zh[n + 1];
// fscanf(ri,"%s",zh);
for(int z = 0;z < n;z++)
{
fprintf(wp,"%c %s\n",name[z],dh[z + 1]);
}
int number = 0;
for(char z;fscanf(rd,"%c",&z) != EOF;)
{
if(number >= 49)
{
printf("%c\n",z);
number = 0;
}
else
{
printf("%c",z);
number++;
}
}
printf("\n");
fclose(rd);
fclose(wp);
fclose(ri);
printf("打印代码成功。结果存在文件'CodePrint.txt'中。\n");
}while(0);
break;
case 'T': //打印哈夫曼树
do
{
if((wt = fopen("TreePrint.txt","w")) == NULL)
{
exit(-1);
}
printf("weigth\tparent\tlchild\trchild\n");
visit(t,wt,n);
fclose(wt);
printf("打印赫夫曼树成功。结果存在文件'TreePrint.txt'中。\n");
}while(0);
break;
default:{
printf("你的输入有误!请重试!\n");
break;
}
}
}
return 0;
}