1.编写程序任意输入结点个数、结点信息和结点权值,构造一棵哈夫曼树,生成哈夫曼编码序列,并验证是否正确。
①采用不同的权值序列进行哈夫曼树的创建,检验其编码的正确性。
②采用不同的选择方式生成相应的哈夫曼树及其编码。
#全代码在最后。
- 第一题:
运行结果如下:
代码讲解:
1、存储结构
typedef struct{
ElemType elem;
int weight;
int parent,lchild,rchild;
}HTNode,*HuffmanTree;
结合教材,也为方便,故采用顺序结构体数组;
elem 用于存放字符,weight用于存放权值;
parent、lchild、rchild 用于存放其父、左孩子、右孩子的位置
2、Huffman树的初始化前奏
typedef struct weight
{
char elem;
int weight1;
}Weight; // 保存字符权值
Weight *w;
int n,i;
printf("请输入Huffman树的结点个数:");
scanf("%d",&n);
w=(Weight *)malloc(n*sizeof(Weight));
printf("\n注意,字符和权值之间需以空格作间隔\n\n");
for(i=0;i<n;i++){
printf("请输入第%d个结点的代码和其权值 : ",i+1);
scanf("%ls%d",&w[i].elem,&w[i].weight1);
}
用其他结构体来提前存储Huffman树的字符信息和权值,简单,防止混淆
3、构造Huffman树函数
// 构造huffman树
void CreateHuffmantree(HuffmanTree &HT,int n,Weight* &w){
void OutPutHuffmanTree(HuffmanTree HT,int n);
if(n<=1)return;
int i,s1,s2;
int m=2*n-1;
HT=(HuffmanTree)malloc( (m+1) *sizeof(HTNode));// 从1开始
for(i=1;i<=n;i++){
HT[i].elem=w[i-1].elem;
HT[i].weight=w[i-1].weight1;
HT[i].parent=0;
HT[i].lchild=0;
HT[i].rchild=0;
}
for(i=n+1;i<=m;i++){
HT[i].elem='-';
HT[i].weight=0;
HT[i].parent=0;
HT[i].lchild=0;
HT[i].rchild=0;
}
printf("HT树的初态:\n");
OutPutHuffmanTree(HT,n);
for(i=n+1;i<=m;i++){
Select(HT,i-1,s1,s2);
HT[s1].parent=i,HT[s2].parent=i;
if(s1<s2){
HT[i].lchild=s1;
HT[i].rchild=s2;
}
else{
HT[i].lchild=s2;
HT[i].rchild=s1;
}
HT[i].weight=HT[s1].weight+HT[s2].weight;
}
}
首先,开辟一段的HTNode型的空间,易知,N个结点需合并N-1次,故需2*n-1段空间,且由于0号位不用,故多开辟一段空间;
其次,开始初始化,将结构体数组 w 的值传入huffman树中,并将后面的树除字符信息外全设为0;
最后,进行合并。在合并循环中,利用Select函数选出前n个结点权值最小且未被选过(其parent=0)的两个,并将当前循环的i作为其父,选出较小的作为左孩子,较大的作为右孩子,直至循环完毕,由此生成Huffman树。
4、Select 函数:
void Select(HuffmanTree HT,int n,int &s1,int &s2){
int min,i;
// 首先选出第一个最小值
for(i=1;i<=n;i++){
if(HT[i].parent==0){
min=i;
break;
}
}
for(i=min+1;i<=n;i++){
if(HT[i].parent==0&&(HT[i].weight<HT[min].weight) ){
min=i;
}
}
s1=min;
// 再选出第二个最小值
for(i=1;i<=n;i++){
if(HT[i].parent==0 && i!=s1){
min=i;
break;
}
}
for(i=min+1;i<=n;i++){
if(HT[i].parent==0 &&(HT[i].weight<HT[min].weight) && i!=s1){
min=i;
}
}
s2=min;
}
函数比较简单,首先选出一个未被选过(parent=0)的结点,然后逐一比较后面同样未被选过为结点,得到最小的结点;
然后,再选出一个未被选过的结点,同样比较,但结点位置宇上一结点位置不能相同。
5、Huffman编码函数:
void CreateHuffmanCode(HuffmanTree &HT,HuffmanCode &HC,int n){
HC=(HuffmanCode)malloc(n*sizeof(char *) ); // 分配n个字符编码
if(!HC)return;
char *cd; // 作为临时存储编码的空间
int i,c,f,start; // 临时变量
cd = (char *)malloc(n*sizeof(char));
cd[n-1] = '\0'; // 编码结束符,方便复制
for(i=1;i<=n;i++){
start=n-1;
for(c=i,f=HT[i].parent;f!=0;c=f,f=HT[f].parent){
if(HT[f].lchild==c){cd[--start]='0';}
else{cd[--start]='1';}
}
HC[i]=(char *)malloc( (n-start)*sizeof(char));
strcpy(HC[i],&cd[start]);
}
free(cd);
}
该函数是从叶子逆向求每个字符的Huffman编码;
首先,分配n个字符编码的头指针向量,接着分配求编码的工作空间;
其次,在循环中,找起父节点,并以 HT[i].pareng!=0 为终止条件,如果,其为其父的左结点,则让cd[--start]=‘1’,否则为‘0’;其中,cd空间为倒叙输入,为方便输出,且不使用栈;
最后,为第i个字符编码开辟空间,并将已经编好的码赋值给它。
①采用不同的权值序列进行哈夫曼树的创建,检验其编码的正确性。
如上,序列打乱,结果仍同,只是相应的,结果序列也被打乱了;
②采用不同的选择方式生成相应的哈夫曼树及其编码。
如图,改变选择方式,将左右孩子变换,相应的,其编码为原来编码的反码。
2.(选做)结合模板中使用的存储结构,试着使用不同的存储形式来表示哈夫曼树及其编码。
模板应用说明
模板应用举例(结合教材图例6-2的数据,输入如下图左 ):
最后结果(如下图右显示):
- 第二题
其实就是第一题的答案,所采用的存储形式恰好为例题6-2的形式,只是在此基础上多输出一个HT树的初态。
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
typedef char ElemType;
typedef struct{
ElemType elem;
int weight;
int parent,lchild,rchild;
}HTNode,*HuffmanTree;
typedef char** HuffmanCode;
typedef struct weight
{
char elem;
int weight1;
}Weight; // 保存字符权值
// select函数,用于选出最小的两值
void Select(HuffmanTree HT,int n,int &s1,int &s2){
int min,i;
// 首先选出第一个最小值
for(i=1;i<=n;i++){
if(HT[i].parent==0){
min=i;
break;
}
}
for(i=min+1;i<=n;i++){
if(HT[i].parent==0&&(HT[i].weight<HT[min].weight) ){
min=i;
}
}
s1=min;
// 再选出第二个最小值
for(i=1;i<=n;i++){
if(HT[i].parent==0 && i!=s1){
min=i;
break;
}
}
for(i=min+1;i<=n;i++){
if(HT[i].parent==0 &&(HT[i].weight<HT[min].weight) && i!=s1){
min=i;
}
}
s2=min;
}
// 构造huffman树
void CreateHuffmantree(HuffmanTree &HT,int n,Weight* &w){
void OutPutHuffmanTree(HuffmanTree HT,int n);
if(n<=1)return;
int i,s1,s2;
int m=2*n-1;
HT=(HuffmanTree)malloc( (m+1) *sizeof(HTNode));// 从1开始
for(i=1;i<=n;i++){
HT[i].elem=w[i-1].elem;
HT[i].weight=w[i-1].weight1;
HT[i].parent=0;
HT[i].lchild=0;
HT[i].rchild=0;
}
for(i=n+1;i<=m;i++){
HT[i].elem='-';
HT[i].weight=0;
HT[i].parent=0;
HT[i].lchild=0;
HT[i].rchild=0;
}
printf("HT树的初态:\n");
OutPutHuffmanTree(HT,n);
for(i=n+1;i<=m;i++){
Select(HT,i-1,s1,s2);
HT[s1].parent=i,HT[s2].parent=i;
if(s1<s2){
HT[i].lchild=s1;
HT[i].rchild=s2;
}
else{
HT[i].lchild=s2;
HT[i].rchild=s1;
}
HT[i].weight=HT[s1].weight+HT[s2].weight;
}
}
void CreateHuffmanCode(HuffmanTree &HT,HuffmanCode &HC,int n){
HC=(HuffmanCode)malloc(n*sizeof(char *) ); // 分配n个字符编码
if(!HC)return;
char *cd; // 作为临时存储编码的空间
int i,c,f,start; // 临时变量
cd = (char *)malloc(n*sizeof(char));
cd[n-1] = '\0'; // 编码结束符,方便复制
for(i=1;i<=n;i++){
start=n-1;
for(c=i,f=HT[i].parent;f!=0;c=f,f=HT[f].parent){
if(HT[f].lchild==c){cd[--start]='0';}
else{cd[--start]='1';}
}
HC[i]=(char *)malloc( (n-start)*sizeof(char));
strcpy(HC[i],&cd[start]);
}
free(cd);
}
// 输出Huffman树的权值
void OutPutHuffmanTree(HuffmanTree HT,int n){
int j;
printf(" 结点 weight parent lchild rchild");
for (j=1; j<=2*n-1; j++){
printf("\n%4d%8d%8d%8d%8d",j,HT[j].weight,
HT[j].parent,HT[j].lchild, HT[j].rchild);
}
printf("\n");
}
// 输出Huffman树编码
void OutputHuffmanCode(HuffmanTree HT,HuffmanCode HC,int n)
{
int i;
printf("\n各代码的Huffman编码如下:\n");
for(i=1;i<=n;i++)
printf("%c %d 的编码: %s\n",HT[i].elem,HT[i].weight,HC[i]);
}
int main()
{
HuffmanTree HT;
HuffmanCode HC;
Weight *w;
int n,i;
printf("请输入Huffman树的结点个数:");
scanf("%d",&n);
w=(Weight *)malloc(n*sizeof(Weight));
printf("\n注意,字符和权值之间需以空格作间隔\n\n");
for(i=0;i<n;i++){
printf("请输入第%d个结点的代码和其权值 : ",i+1);
scanf("%ls%d",&w[i].elem,&w[i].weight1);
}
CreateHuffmantree(HT,n,w);
CreateHuffmanCode(HT,HC,n);
printf("HT树的末态:\n");
OutPutHuffmanTree(HT,n);
printf("\n");
OutputHuffmanCode(HT,HC,n);
return 0;
}