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

​
#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代码示例: ``` #include <stdio.h> #include <stdlib.h> #include <string.h> #define MAX_TREE_HT 100 struct MinHeapNode { char data; unsigned freq; struct MinHeapNode *left, *right; }; struct MinHeap { unsigned size; unsigned capacity; struct MinHeapNode **array; }; struct MinHeapNode *newNode(char data, unsigned freq) { struct MinHeapNode *temp = (struct MinHeapNode *)malloc(sizeof(struct MinHeapNode)); temp->left = temp->right = NULL; temp->data = data; temp->freq = freq; return temp; } struct MinHeap *createMinHeap(unsigned capacity) { struct MinHeap *minHeap = (struct MinHeap *)malloc(sizeof(struct MinHeap)); minHeap->size = 0; minHeap->capacity = capacity; minHeap->array = (struct MinHeapNode **)malloc(minHeap->capacity * sizeof(struct MinHeapNode *)); return minHeap; } void swapMinHeapNode(struct MinHeapNode **a, struct MinHeapNode **b) { struct MinHeapNode *t = *a; *a = *b; *b = t; } void minHeapify(struct MinHeap *minHeap, int idx) { int smallest = idx; int left = 2 * idx + 1; int right = 2 * idx + 2; if (left < minHeap->size && minHeap->array[left]->freq < minHeap->array[smallest]->freq) smallest = left; if (right < minHeap->size && minHeap->array[right]->freq < minHeap->array[smallest]->freq) smallest = right; if (smallest != idx) { swapMinHeapNode(&minHeap->array[smallest], &minHeap->array[idx]); minHeapify(minHeap, smallest); } } int isSizeOne(struct MinHeap *minHeap) { return (minHeap->size == 1); } struct MinHeapNode *extractMin(struct MinHeap *minHeap) { struct MinHeapNode *temp = minHeap->array[0]; minHeap->array[0] = minHeap->array[minHeap->size - 1]; --minHeap->size; minHeapify(minHeap, 0); return temp; } void insertMinHeap(struct MinHeap *minHeap, struct MinHeapNode *minHeapNode) { ++minHeap->size; int i = minHeap->size - 1; while (i && minHeapNode->freq < minHeap->array[(i - 1) / 2]->freq) { minHeap->array[i] = minHeap->array[(i - 1) / 2]; i = (i - 1) / 2; } minHeap->array[i] = minHeapNode; } void buildMinHeap(struct MinHeap *minHeap) { int n = minHeap->size - 1; int i; for (i = (n - 1) / 2; i >= 0; --i) minHeapify(minHeap, i); } void printArr(int arr[], int n) { int i; for (i = 0; i < n; ++i) printf("%d", arr[i]); printf("\n"); } int isLeaf(struct MinHeapNode *root) { return !(root->left) && !(root->right); } struct MinHeap *createAndBuildMinHeap(char data[], int freq[], int size) { struct MinHeap *minHeap = createMinHeap(size); for (int i = 0; i < size; ++i) minHeap->array[i] = newNode(data[i], freq[i]); minHeap->size = size; buildMinHeap(minHeap); return minHeap; } struct MinHeapNode *buildHuffmanTree(char data[], int freq[], int size) { struct MinHeapNode *left, *right, *top; struct MinHeap *minHeap = createAndBuildMinHeap(data, freq, size); while (!isSizeOne(minHeap)) { left = extractMin(minHeap); right = extractMin(minHeap); top = newNode('$', left->freq + right->freq); top->left = left; top->right = right; insertMinHeap(minHeap, top); } return extractMin(minHeap); } void printCodes(struct MinHeapNode *root, int arr[], int top) { if (root->left) { arr[top] = 0; printCodes(root->left, arr, top + 1); } if (root->right) { arr[top] = 1; printCodes(root->right, arr, top + 1); } if (isLeaf(root)) { printf("%c: ", root->data); printArr(arr, top); } } void HuffmanCodes(char data[], int freq[], int size) { struct MinHeapNode *root = buildHuffmanTree(data, freq, size); int arr[MAX_TREE_HT], top = 0; printCodes(root, arr, top); } int main() { char arr[] = {'a', 'b', 'c', 'd', 'e', 'f'}; int freq[] = {5, 9, 12, 13, 16, 45}; int size = sizeof(arr) / sizeof(arr[0]); HuffmanCodes(arr, freq, size); return 0; } ``` 这个示例程序使用了Min Heap、霍夫曼树和深度优先搜索的概念来实现霍夫曼编码和解码。它可以生成给定字符集和频率的霍夫曼编码,并将其打印出来。该程序可以根据需要进行修改,以满足不同的编解码要求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值