#include <stdio.h>
#include <stdlib.h> //malloc函数和free()函数用到
#include <string.h> //strcpy函数用到
#define N 30 //最大的叶子数目
#define M 2*N-1 // 最大的结点个数(N个度为0的叶子结点,N-1个度为2的叶子节点)
#define K 30 //要译码的01字符序列,最多30位
#define MAXINT 32767 //32767
//三叉链表结点结构
typedef struct
{
int weight; //结点的权值
int parent,Lchild,Rchild; //结点的双亲,左孩子,右孩子
}HTNode;
//search函数声明
void search(HTNode huff[],int c,int *s1,int *s2); //在前m项中找最小的权值和次小的权值
//建立哈夫曼树
void crthuff(HTNode huff[],int w[],int n) //HTNode结构体数组huff[],每个结点的权值数组w,n个有效的叶子结点
{
int i;
int s1,s2; //声明语句给s1与s2分配了存储空间,s1是权值最小的结点的下标,s2是权值次小的结点的下标
int m=2*n-1; //有效的结点总数为m
for(i=1;i<=n;i++) //huff[]数组与w[]数组都是从下标1开始的
huff[i]={w[i],0,0,0}; //初始化前n个叶子结点(记住这种给结构体数组赋值的方法)
for(i=n+1;i<=m;i++)
huff[i]={0,0,0,0}; //初始化从下标n+1到m的节点
for(i=n+1;i<=m;i++)
{
search(huff,i-1,&s1,&s2); //在前i-1项中找权值最小的结点,传数组的时候只写数组名,&s1指的是下标的地址
// printf("最小权值的下标为:s1=%d s2=%d",s1,s2);
// printf("最小的两个权值是%d %d",huff[s1].weight,huff[s2].weight);
huff[i].weight=huff[s1].weight+huff[s2].weight;
huff[i].Lchild=s1;
huff[i].Rchild=s2;
huff[s1].parent=i;
huff[s2].parent=i;
}
}
//找最小权值的两个节点,记住这个找最小的两个数的方法
void search(HTNode huff[],int c,int *s1,int *s2) //在数组的前c项中找,指针变量s1与s2,传过来的是下标的地址
{ //在该函数中 *s1是m1在数组中的下标,*s2是m2在数组中的下标
int i,m1,m2; //m1存放最小的权值,m2存放次小的权值
m1=m2=MAXINT; //先让m1与m2赋成最大
for(i=1;i<=c;i++)
{
if(huff[i].weight<m1&&huff[i].parent==0)
{
m2=m1; //让次小的m2等于m1
m1=huff[i].weight; //huff[i]的权值比最小的还小,就让记录最小的m1等于huff[i].weight
*s2=*s1; //*s1与*s2在最开始声明的时候就是有值的,只不过该值是随机产生的,第一次进入该循环的时候,下标*s2被赋予的*s1是一个随机的数
*s1=i; //m1的下标*s1等于i
}
else if(huff[i].weight<m2&&huff[i].parent==0)
{
m2=huff[i].weight;
*s2=i;
}
}
}
//哈夫曼编码函数
char ** crthuffcode(HTNode huff[],int n) //n个叶子结点,对每一个叶子结点都需要编码,因为返回值的数组名是一个指针的指针,所以前面是char **
{
char **hc;
// char *hc[n]; //字符型的指针数组hc,数组中的每个元素都是一个指向字符串的指针变量
int i,start,c,p;
char *cd; //字符型的指针变量
cd=(char *)malloc(n*sizeof(char)); //malloc函数的返回值是一个(指向已申请空间的起始地址的)指针,cd指向可用空间的起始地址,这里的n是因为一个字符的编码序列,它的长度不超过叶子结点个数n
hc=(char **)malloc(n*sizeof(char*)); //给hc这个二重指针分配空间,
cd[n-1]='\0'; //从后向前逐位求编码 ,首先最后一位放编码结束符,留给编码字符的空间是n-1位
for(i=1;i<=n;i++) //对每一个叶子结点都需要编码
{
start=n-1; //n-1处存的是'\0'
c=i; //c记录当前结点
p=huff[i].parent; //p记录当前结点的双亲
while(p!=0)
{
start--; //从下标为n-2处开始存编码
if(huff[p].Lchild==c)
{
cd[start]='0';
}
else
{
cd[start]='1';
}
c=p; //当前结点为p
p=huff[p].parent; //把当前结点的双亲赋给p
} //哈夫曼树的根节点的双亲为0
hc[i]=(char *)malloc((n-start)*sizeof(char)); //hc[i],数组的第i号元素是一个字符型的指针变量,是一个指向第i号字符串的首地址
strcpy(hc[i],&cd[start]); //从cd[start]指向的字符开始复制给hc[i],一直复制直到遇到第一个'\0'为止,被复制的字符数组
// printf("%p %s\n", &hc[i], hc[i]);
// printf("%c:",'A'+i-1);
// printf("%s\n",hc[i]);
} //a与b是两个数组的数组名,在strcpy函数中,这样用strcpy(a,b);把b数组赋值到a数组里。
free(cd);
// printf("%p\n", hc);
return hc; //数组名是二重指针,该函数的返回值是char **
}
void print(char **hc,int n)
{
int i;
for(i=1;i<=n;i++)
{
// printf("i=%d\n",i);
// printf("这里:%s\n",hc[1]);
printf("%c:\n",'A'+i-1);
printf("%s\n",hc[i]);
// printf按%s输出时,遇到'\0'即停止输出
}
}
//for(i=1;i<=n;i++)
//{
// switch(i)
// {
// case 1:printf("%s",hc[1]);break;
// case 2:printf("%s",hc[2]);break;
// case 3:printf("%s",hc[3]);break;
// case 4:printf("%s",hc[4]);break;
// case 5:printf("%s",hc[5]);break;
// case 6:printf("%s",hc[6]);break;
// }
//
// }
//}
//给定字符串求其对应的编码串
void word(char s[],char **hc,int n) //s是传过来的待编码的字符串,n是叶子节点
{
int i;
for(i=0;s[i];i++) //'\0'的逻辑值为0
{
for(int j=1;j<n+1;j++)
{
if(s[i]=='A'+j-1)
{
printf("%s",hc[j]);
// printf("这里:%s",hc[2]);
break;
}
}
}
printf("\n");
}
//哈夫曼树译码(从上到下)
void huffdecode(HTNode huff[],char yima[],int f,int n)
{
int x=2*n-1;
for(int i=0;i<f;i++)
{
if(yima[i]=='0')
{
x=huff[x].Lchild;
}
else if(yima[i]=='1')
{
x=huff[x].Rchild;
}
if(huff[x].Lchild==0)
{
printf("%c",'A'+x-1);
x=2*n-1;
}
}
}
int main()
{
int i,j;
int n=6;
int f=0; //记录待译编码有多少个字符
int w[N+1]; //权值数组,最多有N个叶子结点
char yima[K]; //待译码的01字符序列
char bianma[K]; //带编码的字符串
char ** hc;
HTNode huff[2*n]; //定义一个哈夫曼树的结构体数组,本来大小是2*n-1,但是该数的0号单元不使用,从下标1开始到2*n-1结束
for(i=1;i<=n;i++)
{
scanf("%d",&w[i]); //读入权值
}
getchar();
gets(bianma); //gets()按回车键产生一个换行符,电脑会读取换行符之前的所有字符,并在这些字符后添加一个'\0'
gets(yima);
f=strlen(yima); //strlen测的是字符数组有效的长度
crthuff(huff,w,n); //建立哈夫曼树
hc=crthuffcode(huff,n); //给哈夫曼树编码函数,返回值是指针数组名,是一个二重指针
// for(int i = 1; i <= n; i++){
// printf("%p %s\n", &hc[i], hc[i]);
//
// }
for(i=1;i<=n;i++)
{
// printf("i=%d\n",i);
// printf("这里:%s\n",hc[1]);
// printf("main:%c:\n",'A'+i-1);
printf("这里:%s\n",hc[i]);
// printf按%s输出时,遇到'\0'即停止输出
}
//print(hc,n);
word(bianma,hc,n); //给定字符串求其编码
huffdecode(huff,yima,f,n); //译码
return 0;
}
//3 4 10 8 6 5
//BEE
//0010000100111101
在crthuffcode(HTNode huff[],int n)函数中,如果定义的是char **hc;定义了一个二重指针,那么在函数中应该首先hc=(char **)malloc(n*sizeof(char*));也就是应该首先给hc这个二重指针开辟空间,也就是给n个指向字符串的指针变量来分配空间,malloc函数的返回值是一个指针,指向该n个指针变量的首地址。
如果在该函数中,定义的是char *hc[n];并且没有hc=(char **)malloc(n*sizeof(char*));这句话,那么后面的hc[i]=(char *)malloc((n-start)*sizeof(char)); 这个语句只是在给每个指针变量所指向的字符串分配空间,而不是对该指针变量分配空间。所以该字符型指针数组中的每个元素还是没有一个明确的地址,只是一个在函数中的局部变量数组,那么在退出该函数的时候,这个局部变量数组会被释放,该数组的每个元素,即字符型的指针变量将丢失,在主函数中调用该函数时,除了第一个hc[1]有明确的指向外,其他的hc[i]所指向的都是不确定的字符。
运行结果: