哈夫曼树编码
1.实验目的
了解二叉树的定义,理解二叉树的基本性质和存储结构,掌握哈夫曼树的构造,实现哈夫曼编码与译码算法。
2.实验内容
从键盘输入一串电文字符与权值,输出对应的哈夫曼编码;从键盘输入一串二进制代码,输出对应的电文字符串。具体步骤如下:
- 构造一棵哈夫曼树;
- 实现哈夫曼编码;
- 对哈夫曼编码生成的二进制串进行译码;
- 要求程序中字符和权值是可变的,实现程序的灵活性。
3.实验工具
Dev-C++
4.实验代码
//Authors:xioabei
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
typedef char **HuffmanCode; //动态分配数组存储哈夫曼编码表
typedef struct{
int weight;
int parent,lchild,rchild;
}HTNode,*HuffmanTree;
typedef struct Code{
char ch;
int weight;
Code *next;
}*CodeLink;
//创建字符与权值
void CreateCouple(CodeLink &Couple,int n){
int i;
CodeLink r,p;
Couple = (CodeLink)malloc(sizeof(Code));
Couple->next = NULL;
r = Couple;
for(i = 1;i<=n;i++){
p = (CodeLink)malloc(sizeof(Code));
printf("[请输入第%d个字符与权值:]\n>>>",i);
getchar();
scanf("%c %d",&Couple[i].ch,&Couple[i].weight);
p->next = NULL;
r->next = p;
r = p;
}
printf("\n[成功创建字符与权值]\n");
}
//选择最小的两个
void Select(HuffmanTree HT,int n,int *s1, int *s2){
int i,min,max;
for(i=1;i<=n;i++){
if(HT[i].parent == 0){
*s2 = i;
max = i;
*s1 = i;
}
}
for(i=1;i<=n;i++){
if(HT[*s1].weight>HT[i].weight && HT[i].parent == 0)
*s1 = i;
if(HT[max].weight<HT[i].weight && HT[i].parent == 0)
max = i;
}
min = HT[*s1].weight;
HT[*s1].weight = HT[max].weight;
for(i=1;i<=n;i++)
if(HT[*s2].weight>HT[i].weight && HT[i].parent == 0)
*s2 = i;
HT[*s1].weight = min;
printf("%d--%d\n",HT[*s1].weight,HT[*s2].weight);
}
//创建哈夫曼树
void CreateHuffmanTree(HuffmanTree &HT,CodeLink Couple,int n){
printf("\n---------开始创建哈夫曼树---------\n");
int m,i,s1=1,s2=1,*p = &s1,*q = &s2;
//构造哈夫曼树HT
if(n<=1)
return;
m=2*n-1;
HT = (HTNode*)malloc(sizeof(HTNode)*(m+1)); //由于0号单元未用,所以需要动态分配m+1个单元,HT[m]表示根结点
for(i=1;i<=m;i++){ //将1~m号单元初始化为0
HT[i].parent = 0;
HT[i].lchild = 0;
HT[i].rchild = 0;
}
printf("\n初始化成功……\n");
for(i=1;i<=n;i++) //输入前n个单元叶子结点权值
HT[i].weight = Couple[i].weight;
//---------- 初始化工作结束,开始创建哈夫曼树 -----------//
for(i=n+1;i<=m;i++){
// 通过n-1次选择、删除、合并来创建哈夫曼树
Select(HT,i-1,p,q);
// 在HT[k](1<=k<=i-1)中选择双亲域为0且权值最小的结点,并且返回它们在HT中序号s1,s2
HT[s1].parent = i;
HT[s2].parent = i;
// 得到新结点i,从森林中删除s1,s2,将s1,s2双亲域由0改为i
HT[i].lchild = s1; //s1,s2分别作为i的左右孩子
HT[i].rchild = s2;
HT[i].weight = HT[s1].weight + HT[s2].weight; //i的权值为左右孩子权值之和
}
printf("\n---------结束创建哈夫曼树---------\n");
}
//创建哈夫曼编码
void CreateHuffmanCode(HuffmanTree HT,HuffmanCode &HC,int n){
int c,f,start,i;
char *cd;
//从叶子结点逆向求每个字符的哈夫曼编码,存储在编码表HC中
HC = (HuffmanCode)malloc(sizeof(char*)*(n+1)); //分配n个字符编码表空间
cd = (char*)malloc(sizeof(char)*n); //分配临时存放每个字符编码动态数组空间
cd[n-1] = '\0'; //编码结束符
for(i=1;i<=n;i++){ //逐个字符求哈夫曼编码
start = n-1; //start开始指向最后,即编码结束位置
c = i;
f = HT[i].parent; //f指向c的双亲结点
while(f!=0){ //从叶子结点开始向上回溯,直到根结点
--start; //回溯一次start向前指一个位置
if(HT[f].lchild==c)
cd[start] = '0';//结点c是f的左孩子,则生成代码"0"
else
cd[start] = '1';//结点c是f的右孩子,则生成代码"1"
c = f;
f = HT[f].parent; //继续向上回溯
} //求出第i个字符编码
HC[i] = (char*)malloc(sizeof(char)*(n-start)); //为第i个字符编码分配空间
strcpy(HC[i],&cd[start]);//将求得的编码从临时空间cd中复制到HC当前行中
puts(HC[i]);
}
free(cd); //释放临时空间
printf("\n---------哈夫曼树编码成功---------\n");
}
//编码
void Encode(HuffmanCode HC,CodeLink Couple,char T[],int n){
int i,j,k,t;
printf("[编码如下:]\n");
for(i=0;T[i]!='\0';i++)
for(j=1;j<=n;j++){
if(Couple[j].ch==T[i]){
for(k=1;k<=n;k++){
if(Couple[j].weight==Couple[k].weight)
puts(HC[k]);
}
}
}
}
//译码
void Decode(HuffmanCode HC,CodeLink Couple,char T[],int n){
int i,j,k,location = 0,len,tag;
printf("[译码如下:]\n");
for(i=0;T[location+1]!='\0';i++){
for(j=1;j<=n;j++){
len = strlen(HC[j]);
tag = 1;
for(k=0;k<len;k++){
if(HC[j][k]!=T[location+k]){
tag = 0;
break;
}
}
if(tag==1){
printf("%c",Couple[j].ch);
break;
}
}
len = strlen(HC[j]);
location += len;
}
}
// 打印菜单
void PrintMenu(){
printf("\n**********菜单**********\n");
printf("\n1.创建字符与权值;\n");
printf("2.创建哈夫曼树;\n");
printf("3.生成哈夫曼编码;\n");
printf("4.编码;\n");
printf("5.译码;\n");
printf("0.退出;\n");
printf("\n************************\n");
printf("[请输入你的选择:]\n>>>");
}
//主函数
int main(){
HuffmanTree HT;
HuffmanCode HC;
CodeLink Couple;
int i,n,user;
char T[100];
while(1){
PrintMenu();
scanf("%d",&user);
switch(user){
case 1:{
printf("[请输入叶子结点数:]\n>>>");
scanf("%d",&n);
CreateCouple(Couple,n);
break;
}
case 2:CreateHuffmanTree(HT,Couple,n);break;
case 3:CreateHuffmanCode(HT,HC,n);break;
case 4:{
printf("[请输入要编码的字符:]\n>>>");
getchar();
gets(T);
Encode(HC,Couple,T,n);
break;
}
case 5:{
printf("[请输入要编码的字符:]\n>>>");
getchar();
gets(T);
Decode(HC,Couple,T,n);
break;
}
case 0:exit(0);
}
}
return 0;
}
5.实验结果
6.实验分析
1.HT初态
2.HT终态
3.示意图
7.资料
1951年,哈夫曼在麻省理工学院(MIT)攻读博士学位,他和修读信息论课程的同学得选择是完成学期报告还是期末考试。导师罗伯特·法诺(Robert Fano)出的学期报告题目是:查找最有效的二进制编码。由于无法证明哪个已有编码是最有效的,哈夫曼放弃对已有编码的研究,转向新的探索,最终发现了基于有序频率二叉树编码的想法,并很快证明了这个方法是最有效的。哈夫曼使用自底向上的方法构建二叉树,避免了次优算法香农-范诺编码(Shannon–Fano coding)的最大弊端──自顶向下构建树。
给定N个权值作为N个叶子结点,构造一棵二叉树,若该树的带权路径长度达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树(Huffman Tree)。哈夫曼树是带权路径长度最短的树,权值较大的结点离根较近。
在计算机数据处理中,哈夫曼编码使用变长编码表对源符号(如文件中的一个字母)进行编码,其中变长编码表是通过一种评估来源符号出现机率的方法得到的,出现机率高的字母使用较短的编码,反之出现机率低的则使用较长的编码,这便使编码之后的字符串的平均长度、期望值降低,从而达到无损压缩数据的目的。