哈夫曼树的编码译码(注释超详细)

​
#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]所指向的都是不确定的字符。
运行结果:

  • 21
    点赞
  • 182
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
好的,以下是我为您提供的c语言版哈夫曼树编码译码系统,每行都有注释: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> // 定义哈夫曼树结点结构体 typedef struct huf_node { int weight; // 权重 int parent; // 父结点下标 int lchild; // 左子结点下标 int rchild; // 右子结点下标 } huf_node; // 构建哈夫曼树 void create_huf_tree(huf_node *huf_tree, int n) { // 找出最小的两个结点 int min1, min2; min1 = min2 = 0; for (int i = 1; i < n; i++) { if (huf_tree[i].parent == -1) { if (huf_tree[i].weight < huf_tree[min1].weight) { min2 = min1; min1 = i; } else if (huf_tree[i].weight < huf_tree[min2].weight) { min2 = i; } } } // 合并最小的两个结点并构建哈夫曼树 huf_tree[min1].parent = n; huf_tree[min2].parent = n; huf_tree[n].weight = huf_tree[min1].weight + huf_tree[min2].weight; huf_tree[n].lchild = min1; huf_tree[n].rchild = min2; if (n < 2 * (n - 1) - 1) { create_huf_tree(huf_tree, n + 1); } } // 哈夫曼编码 void huf_encode(huf_node *huf_tree, char *str, char *code) { int len = strlen(str); for (int i = 0; i < len; i++) { int j = i; int k = 0; // 从叶子结点向上回溯,直到根结点 while (huf_tree[j].parent != -1) { int parent = huf_tree[j].parent; if (huf_tree[parent].lchild == j) { code[k++] = '0'; } else { code[k++] = '1'; } j = parent; } code[k] = '\0'; // 将编码翻转 int m = 0, n = strlen(code) - 1; while (m < n) { char temp = code[m]; code[m] = code[n]; code[n] = temp; m++; n--; } printf("%c:%s\n", str[i], code); } } // 哈夫曼译码 void huf_decode(huf_node *huf_tree, char *code) { int len = strlen(code); int p = 2 * (len - 1); for (int i = 0; i < len; i++) { if (code[i] == '0') { p = huf_tree[p].lchild; } else { p = huf_tree[p].rchild; } if (huf_tree[p].lchild == -1 && huf_tree[p].rchild == -1) { printf("%c", (char) p); p = 2 * (len - 1); } } } int main() { // 输入字符集 char chars[6] = {'A', 'B', 'C', 'D', 'E', 'F'}; // 输入权值 int weights[6] = {5, 4, 3, 2, 1, 1}; // 计算字符集中字符数量 int n = sizeof(chars) / sizeof(chars[0]); // 构建哈夫曼树 huf_node *huf_tree = (huf_node *) malloc(sizeof(huf_node) * (2 * n - 1)); for (int i = 0; i < 2 * n - 1; i++) { huf_tree[i].weight = 0; huf_tree[i].parent = -1; huf_tree[i].lchild = -1; huf_tree[i].rchild = -1; } for (int i = 0; i < n; i++) { huf_tree[i].weight = weights[i]; } create_huf_tree(huf_tree, n); // 哈夫曼编码 char code[100]; for (int i = 0; i < n; i++) { code[0] = '\0'; huf_encode(huf_tree, &chars[i], code); } // 哈夫曼译码 char str[] = "101010000010100111011100"; huf_decode(huf_tree, str); return 0; } ```
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值