一、实验题目
1)初始化。读入每个字符的权值,建立哈夫曼树HuffTree;
2)编码。用已建好的哈夫曼树,进行编码;
3)输出。显示建立的哈夫曼树和对应的编码表;
1.数据结构
//- - - - -哈夫曼树的存储表示- - - - -
typedef struct{
int weight; //结点的权值
int parent,lchild,rchild; //结点的双亲、左孩子、右孩子的下标
) HTNode,*HuffmanTree; //动态分配数组存储哈夫曼树
2.算法
算法1 构造哈夫曼树
算法2 根据哈夫曼树求哈夫曼编码
2.1 构造哈夫曼树
算法步骤
构造哈夫曼树算法的实现可以分成两大部分。
1.初始化:首先动态申请2n个单元;然后循环2n-1次,从1号单元开始,依次将1至2n-1所有单元中的双亲、左孩子、右孩子的下标都初始化为0;最后再循环n次,输人前n个单元中叶子结点的权值。
2.创建树:循环n-1次,通过n-1次的选择、删除与合并来创建哈夫曼树。选择是从当前森林中选择双亲为0且权值最小的两个树根结点s1和s2;删除是指将结点s1和s2的双亲改为非0;合并就是将s1和s2的权值和作为一个新结点的权值依次存人到数组的第n +1之后的单元中,同时记录这个新结点左孩子的下标为s1,右孩子的下标为s2。
算法描述
void CreateHuffmanTree(HuffmanTree &HT,int n)
{//构造哈夫曼树 HT
if(n<=1) return;
m=2*n-1;
HT=new HTNode[m+1]; //0号单元未用,所以需要动态分配m+1个单元,HT[m]表示根结点
for(i=1;i<=m;++i) //将1~m号单元中的双亲、左孩子、右孩子的下标都初始化为0
{HT[i] .parent=O;HT[i] .lchild=O;HT[i] .rchild=O;}
for(i=l;i<=n;++i} //输人前n个单元中叶子结点的权值
cin>>HT[i].weight;
/*- ---- ----- -初始化工作结束, 下面开始创建哈夫曼树- - - - ------ */
for (i=n+1; i<=m; ++i}
{//通过n-1次的选择、删除、合并来创建哈夫曼树
Select (HT, i-1, sl, s2};
//在 HT[k](1<=k<=i-1) 中选择两个其双亲域为0且权值最小的结点,并返回它们在HT中的序号s1和s2
HT[s1].parent=i;HT[s2].parent=i;
//得到新结点i,从森林中删除sl,s2,将s1和s2的双亲域由0改为1.
HT[i].lchild=s1;HT[i].rchild=s2; ///s1,s2分别作为i的左右孩子
HT[i].weight=HT[s1].weight+HT[s2].weight; //i的权值为左右孩子权值之和
}//for
}
2.2 根据哈夫曼树求哈夫曼编码
算法步骤
1.分配存储n个字符编码的编码表空间HC,长度为n+1;分配临时存储每个字符编码的动态数组空间cd,cd[n-1]置为\0’。
2.逐个求解n个字符的编码,循环n次,执行以下操作:
2.1设置变量start用于记录编码在cd中存放的位置,start初始时指向最后,即编码结束符位置n-1;
2.2设置变量c用于记录从叶子结点向上回溯至根结点所经过的结点下标,c初始时为当前待编码字符的下标i,f用于记录i的双亲结点的下标;
2.3从叶子结点向上回溯至根结点,求得字符i的编码,当f没有到达根结点时,循环执行以下操作:
2.3.1回溯一次start向前指一个位置,即–start;
2.3.2若结点c是f的左孩子,则生成代码0,否则生成代码1,生成的代码0或1保存在cd[start]中;
2.3.3继续向上回溯,改变c和f的值。
2.4根据数组cd的字符串长度为第i个字符编码分配空间HC[ i],然后将数组cd 中的编码
复制到HC[ i]中。
3.释放临时空间cd。
算法描述
void CreatHuffmanCode(HuffmanTree HT,HuffmanCode &HC,int n)
{//从叶子到根逆向求每个字符的哈夫曼编码,存储在编码表HC中
HC=new char* [n+1]; //分配存储n个字符编码的编码表空间
cd=new 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!=O) //从叶子结点开始向上回溯,直到根结点
{
--start; //回溯一次start向前指一个位置
if(HT[f].lchild==c) cd[start]='O'; //结点c是f的左孩子,则生成代码0
else cd[start]='1'; //结点c是f的右孩子,则生成代码1
c=f;f=HT[f].parent; //继续向上回溯
} //求出第i个字符的编码
HC[i]=new char[n-start]; //为第i个字符编码分配空间
strcpy(HC[i],&cd[start]); //将求得的编码从临时空间cd复制到HC的当前行中
}//for
delete cd; //释放临时空间
}
二、工具环境
Window10操作系统,Microsoft Visual C++2010学习版 集成开发环境,C语言
三、实验代码
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
typedef struct{
int weight; //结点的权值
int parent,lchild,rchild; //结点的双亲、左孩子、右孩子的下标
} HTNode,*HuffmanTree;
typedef char **HuffmanCode;
void Select(HuffmanTree HT,int n,int *s1,int *s2);
void CreateHuffmanTree(HuffmanTree *HT,int n,int number[]);
void CreateHuffmanCode(HuffmanTree HT,HuffmanCode *HC,int n);
void ShowResults(HuffmanTree HT,HuffmanCode HC,int n);
int main()
{
HuffmanTree HT=NULL;
HuffmanCode HC=NULL;
int n=8,number[8]={5,29,7,8,14,23,3,11};//此处number存储的是权值,读者可以自己写读入权值和数量的初始化函数
CreateHuffmanTree(&HT,n,number);
CreateHuffmanCode(HT,&HC,n);
ShowResults(HT,HC,n);
return 0;
}
void Select(HuffmanTree HT,int n,int *s1,int *s2)
{//选择两个其双亲域为0且权值最小的结点,并返回它们在HT中的序号sl和s2
int num[100],index[100],i,j,k=0,max=0,temp;
for(i=1;i<=n;i++)
{
if(HT[i].parent==0)
{//双亲域为0
k++;
num[k]=HT[i].weight;
index[k]=i; //记录双亲域为0结点的序号和权值
}
}
for(i=1;i<k;i++)
{//排列出最小的两个结点
for(j=i+1;j<=k;j++)
{
if(num[i]>num[j])
{
temp=num[i];
num[i]=num[j];
num[j]=temp;
temp=index[i];
index[i]=index[j];
index[j]=temp;//序号和权值同时交换
}
}
}
*s1=index[1];//将最小权值的两个结点序号返还给s1,s2
*s2=index[2];
}
void CreateHuffmanTree(HuffmanTree *HT,int n,int number[])
{//构造哈夫曼树
int m=2*n-1;
int i,s1=1,s2=1;
if(n<=1) return;
*HT=(HuffmanTree)malloc((m+1)*sizeof(HTNode)); //0号单元未用,所以需要动态分配m+l个单元,HT[m)表示根结点
for(i=1;i<=m;++i)
{//将l~m号单元中的双亲、左孩子,右孩子的下标都初始化为0
(*HT)[i].parent=0;
(*HT)[i].lchild=0;
(*HT)[i].rchild=0;
}
for(i=1;i<=n;++i)
{//输入前n个单元中叶子结点的权值
(*HT)[i].weight=number[i-1];
}
//- ----------初始化工作结束,下面开始创建哈夫曼树---- ------
for ( i=n+1; i<=m; ++i)
{//通过n-1次的选择、删除、合并来创建哈夫曼树
Select(*HT, i-1, &s1, &s2);
//在HT[k](l<=k<=i-1)中选择两个其双亲域为0且权值最小的结点,并返回它们在HT中的序号sl和s2
(*HT)[s1].parent=i;
(*HT)[s2].parent=i;
//得到新结点i, 从森林中删除sl,s2, 将sl和s2的双亲域由0改为i
(*HT)[i].lchild=s1;
(*HT)[i].rchild=s2;
//sl, s2分别作为i的左右孩子
(*HT)[i].weight=(*HT)[s1].weight+(*HT)[s2].weight; //i的权值为左右孩子权值之和
}//for
}
void CreateHuffmanCode(HuffmanTree HT,HuffmanCode *HC,int n)
{//从叶子到根逆向求每个字符的哈夫曼编码,存储在编码表HC中
int i,start,c,f;
char *cd;
*HC=(char **)malloc((n+1)*sizeof(char *)); //分配存储n个字符编码的编码表空间
cd=(char *)malloc(n*sizeof(char)); //分配临时存放每个字符编码的动态数组空间
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((n-start)*sizeof(char)); //为第i个字符编码分配空间
strcpy((*HC)[i],&cd[start]); //将求得的编码从临时空间cd复制到HC的当前行中
}//for
cd=NULL; //置空指针
free(cd); //释放临时空间
}
void ShowResults(HuffmanTree HT,HuffmanCode HC,int n)
{//展示创建的哈夫曼树和对应的哈夫曼编码
int i;
printf("\n\t结点i\tweight\tparent\tlchild\trchild\n");
for(i=1;i<=2*n-1;++i)
{
printf("\t%d\t%d\t%d\t%d\t%d\n",i,HT[i].weight,HT[i].parent,HT[i].lchild,HT[i].rchild);
}
printf("\n\n\n\t结点i\tHuffmanCode\n");
for(i=1;i<=n;++i)
{
printf("\t%d\t%s\n",i,HC[i]);
}
}