算法与数据结构(四)利用哈夫曼树编码解码
哈夫曼(Haffman)树(最优树)
定义:
给定n个权值作为n个叶子结点,构造一棵二叉树,若该树的带权路径长度达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树(Huffman Tree)。哈夫曼树是带权路径长度最短的树,权值较大的结点离根较近。
构造过程:
以 1,7,3,4,9,8为例:
第一步:排序,1,3,4,7,8,9
第二步:选出最小的两个数,1,3(哈夫曼树是从下往上排列的),用一个树杈连接上两个最小的数,在顶点处计算出这两个数字的和 并写在上面。
第三步:然后再比较剩下的数字和这个和的大小,再取出两个最小的数字进行排列。
第四步:如果两个数的和正好是下一步的两个最小数的其中的一个那么这个树直接往上生长就可以了;如果这两个数的和比较大不是下一步的两个最小数的其中一个那么,就并列生长。
注意:(由于并未规定左右子树,所以哈夫曼树不唯一,同一层上的结点,位置是可以互换的)
哈夫曼树的数据结构:
1 //haffman 树的结构 2 typedef struct 3 { 4 //叶子结点权值 5 float weight; 6 //指向双亲,和孩子结点的指针 7 unsigned int parent; 8 unsigned int lChild; 9 unsigned int rChild; 10 } Node, *HuffmanTree; 11 12 //动态分配数组,存储哈夫曼编码 13 typedef char *HuffmanCode;
选择两个权值最小的结点:
1 //选择两个parent为0,且weight最小的结点s1和s2的方法实现 2 //n 为叶子结点的总数,s1和 s2两个指针参数指向要选取出来的两个权值最小的结点 3 void select(HuffmanTree *huffmanTree, int n, int *s1, int *s2) 4 { 5 //标记 i 6 int i = 0; 7 //记录最小权值 8 int min; 9 //遍历全部结点,找出单节点 10 for(i = 1; i <= n; i++) 11 { 12 //如果此结点的父亲没有,那么把结点号赋值给 min,跳出循环 13 if((*huffmanTree)[i].parent == 0) 14 { 15 min = i; 16 break; 17 } 18 } 19 //继续遍历全部结点,找出权值最小的单节点 20 for(i = 1; i <= n; i++) 21 { 22 //如果此结点的父亲为空,则进入 if 23 if((*huffmanTree)[i].parent == 0) 24 { 25 //如果此结点的权值比 min 结点的权值小,那么更新 min 结点,否则就是最开始的 min 26 if((*huffmanTree)[i].weight < (*huffmanTree)[min].weight) 27 { 28 min = i; 29 } 30 } 31 } 32 //找到了最小权值的结点,s1指向 33 *s1 = min; 34 //遍历全部结点 35 for(i = 1; i <= n; i++) 36 { 37 //找出下一个单节点,且没有被 s1指向,那么i 赋值给 min,跳出循环 38 if((*huffmanTree)[i].parent == 0 && i != (*s1)) 39 { 40 min = i; 41 break; 42 } 43 } 44 //继续遍历全部结点,找到权值最小的那一个 45 for(i = 1; i <= n; i++) 46 { 47 if((*huffmanTree)[i].parent == 0 && i != (*s1)) 48 { 49 //如果此结点的权值比 min 结点的权值小,那么更新 min 结点,否则就是最开始的 min 50 if((*huffmanTree)[i].weight < (*huffmanTree)[min].weight) 51 { 52 min = i; 53 } 54 } 55 } 56 //s2指针指向第二个权值最小的叶子结点 57 *s2 = min; 58 }
构造哈夫曼树:
1 //创建哈夫曼树并求哈夫曼编码的算法如下,w数组存放已知的n个权值 2 void createHuffmanTree(HuffmanTree *huffmanTree, float w[], int n) 3 { 4 //m 为哈夫曼树总共的结点数,n 为叶子结点数 5 int m = 2 * n - 1; 6 //s1 和 s2 为两个当前结点里,要选取的最小权值的结点 7 int s1; 8 int s2; 9 //标记 10 int i; 11 // 创建哈夫曼树的结点所需的空间,m+1,代表其中包含一个头结点 12 *huffmanTree = (HuffmanTree)malloc((m + 1) * sizeof(Node)); 13 //1--n号存放叶子结点,初始化叶子结点,结构数组来初始化每个叶子结点,初始的时候看做一个个单个结点的二叉树 14 for(i = 1; i <= n; i++) 15 { 16 17 //其中叶子结点的权值是 w【n】数组来保存 18 (*huffmanTree)[i].weight = w[i]; 19 //初始化叶子结点(单个结点二叉树)的孩子和双亲,单个结点,也就是没有孩子和双亲,==0 20 (*huffmanTree)[i].lChild = 0; 21 (*huffmanTree)[i].parent = 0; 22 (*huffmanTree)[i].rChild = 0; 23 }// end of for 24 //非叶子结点的初始化 25 for(i = n + 1; i <= m; i++) 26 { 27 (*huffmanTree)[i].weight = 0; 28 (*huffmanTree)[i].lChild = 0; 29 (*huffmanTree)[i].parent = 0; 30 (*huffmanTree)[i].rChild = 0; 31 } 32 33 printf("\n HuffmanTree: \n"); 34 //创建非叶子结点,建哈夫曼树 35 for(i = n + 1; i <= m; i++) 36 { 37 38 select(huffmanTree,i-1,&s1,&s2); 39 (*huffmanTree)[s1].parent=i; 40 (*huffmanTree)[s2].parent=i; 41 (*huffmanTree)[i].lChild=s1; 42 (*huffmanTree)[i].rChild=s2; 43 (*huffmanTree)[i].weight=(*huffmanTree)[s1].weight+(*huffmanTree)[s2].weight; 44 printf("%f (%f, %f)\n", (*huffmanTree)[i].weight, (*huffmanTree)[s1].weight, (*huffmanTree)[s2].weight); 45 } 46 printf("\n"); 47 }
求每个节点的哈弗曼编码:
1 //哈夫曼树建立完毕,从 n 个叶子结点到根,逆向求每个叶子结点对应的哈夫曼编码 2 void creatHuffmanCode(HuffmanTree *huffmanTree, HuffmanCode *huffmanCode, int n) 3 { 4 //指示biaoji 5 int i; 6 //编码的起始指针 7 int start; 8 //指向当前结点的父节点 9 int p; 10 //遍历 n 个叶子结点的指示标记 c 11 unsigned int c; 12 //分配n个编码的头指针 13 huffmanCode = (HuffmanCode *)malloc((n + 1) * sizeof(char *)); 14 //分配求当前编码的工作空间 15 char *cd = (char *)malloc(n * sizeof(char)); 16 //从右向左逐位存放编码,首先存放编码结束符 17 cd[n - 1] = '\0'; 18 //求n个叶子结点对应的哈夫曼编码 19 for(i = 1; i <= n; i++) 20 { 21 //初始化编码起始指针 22 start = n - 1; 23 //从叶子到根结点求编码 24 for(c = i, p = (*huffmanTree)[i].parent; p != 0; c = p, p = (*huffmanTree)[p].parent) 25 { 26 if( (*huffmanTree)[p].lChild == c) 27 { 28 //从右到左的顺序编码入数组内 29 cd[--start] = '0'; //左分支标0 30 } 31 else 32 { 33 cd[--start] = '1'; //右分支标1 34 } 35 }// end of for 36 //为第i个编码分配空间 37 huffmanCode[i] = (char *)malloc((n - start) * sizeof(char)); 38 strcpy(huffmanCode[i], &cd[start]); 39 } 40 41 free(cd); 42 //打印编码序列 43 for(i = 1; i <= n; i++) 44 { 45 printf("HuffmanCode of %d is %s\n", i, huffmanCode[i]); 46 } 47 48 printf("\n"); 49 }
输入一串数据(数据为1—9之间从小到大连续的数),编译成哈夫曼编码:
1 HuffmanCode *creatHuffmanCode1(HuffmanTree *huffmanTree,char *Huffman, int n,int data[],float num){ 2 //该函数通过改写creatHuffmanCode函数,来实现译码(即是通过匹配即添加字符串的方式来实现) 3 HuffmanCode *huffmanCode; 4 huffmanCode = (HuffmanCode *)malloc((n + 1) * sizeof(char *)); 5 int i; 6 int start; 7 int p; 8 unsigned int c; 9 char *cd = (char *)malloc(n * sizeof(char)); 10 cd[n - 1] = '\0'; 11 for(i = 1; i <= n; i++) 12 { 13 start = n - 1; 14 for(c = i, p = (*huffmanTree)[i].parent; p != 0; c = p, p = (*huffmanTree)[p].parent) 15 { 16 if( (*huffmanTree)[p].lChild == c) 17 { 18 cd[--start] = '0'; 19 } 20 else 21 { 22 cd[--start] = '1'; 23 } 24 } 25 huffmanCode[i] = (char *)malloc((n - start) * sizeof(char)); 26 strcpy(huffmanCode[i], &cd[start]); 27 } 28 free(cd); 29 //打印编码序列 30 int num1=int(num); 31 int l=0; 32 char s[10]=""; 33 itoa(n,s,10); 34 huffmanCode[0] = (char *)malloc((10) * sizeof(char)); 35 strcpy(huffmanCode[0],s); 36 for(int i=1;i<=num1;i++){ 37 int z=data[i]; 38 strcat(Huffman,huffmanCode[z]); 39 } 40 printf("HuffmanCode of this String is %s\n",Huffman); 41 printf("\n"); 42 return huffmanCode; 43 } 44 45 46 HuffmanCode *dataToHuffmanCode(char *Huffman, int data[]){ 47 //data数组是原数据(0—9),Huffman存放编译后的 Huffman编码 48 HuffmanTree huffmanTree; 49 int k=1; 50 int q=0; 51 while(data[k]!=-99){ 52 k++; 53 } 54 float num=float(k-1); 55 float val[10]; 56 for(int j=1;j<10;j++){ 57 int i=1; 58 int n=0; 59 while(data[i]!=-99){ 60 if(data[i]==j){ 61 n++; 62 } 63 i++; 64 } 65 val[j]=n/num; 66 } 67 for(int i=1;i<10;i++){ 68 if(val[i]!=0){ 69 q++; //q为叶子结点数 70 } 71 } 72 float *val2; 73 val2 = (float *)malloc((q+1) * sizeof(float)); 74 int m=1; 75 for(int i=1;i<10;i++){ 76 if(val[i]!=0){ 77 val2[m]=val[i]; 78 } 79 m++; //val2[]储存不同元素的权值 80 } 81 createHuffmanTree(&huffmanTree,val2,q); 82 HuffmanCode *huffmanCode=creatHuffmanCode1(&huffmanTree,Huffman,q,data,num);//调用上面的 creatHuffmanCode1函数 83 return huffmanCode; 84 }
将一串哈夫曼编码,还原成原来的数据(此时哈弗曼树已经构造完成):
1 void HuffmanCodeTodata(char *Huffman, int data[],HuffmanCode *huffmanCode){ 2 //huffmanCode为已经构造好的一颗Huffman树,Huffman存放已经编译好的 Huffman编码 ,data数组存放解码后的原数据(0—9) 3 4 printf("Huffman:%s\n",Huffman); 5 printf("1:%s\n",huffmanCode[1]); 6 printf("2:%s\n",huffmanCode[2]); 7 printf("3:%s\n",huffmanCode[3]); 8 printf("4:%s\n",huffmanCode[4]); 9 //这几行是显示功能,和实现无关 10 11 int num=atoi(huffmanCode[0]); 12 char Huffman1[100]=""; 13 int k1=0; 14 int k2=0; 15 int x=1; 16 int y=1; 17 while(Huffman[k1]=='1'||Huffman[k1]=='0'){ 18 Huffman1[k2]=Huffman[k1]; 19 for(int i=1;i<=num;i++){ 20 if(strcmp(huffmanCode[i],Huffman1)==0){ 21 k2=-1; 22 data[x]=i; 23 x++; 24 //strcpy(Huffman1,""); 25 memset(Huffman1,0,sizeof(Huffman1)); 26 break; 27 } 28 } 29 k1++; 30 k2++; 31 } 32 data[x]=-99; 33 for(int i=1;i<100;i++){ 34 if(data[i]!=-99){ 35 printf("%d",data[i]); 36 } 37 else{ 38 break; 39 } 40 } 41 }
输入一个文本,统计文本中各字符串的数目,计算相应的权重,使用该权重构建哈夫曼编码,使用该编码编译原文件
(即是在编码的基础上加上文件操作):
1 void fileHuff(){ 2 FILE *in; 3 char Huffman2[1000]=""; 4 int data[100]; 5 char file[10]; 6 int k = 1; 7 printf("输入读取的文件名:(输入aaa.txt)"); 8 scanf("%s",file); 9 if((in=fopen(file,"r+"))==NULL){ 10 printf("无法打开此文件!\n"); 11 exit(0); 12 } 13 while(!feof(in)){ 14 fscanf(in,"%d",&data[k++]); 15 } 16 for(int i=1;i<k;i++){ 17 printf("\n%d\n",data[i]); 18 } 19 data[k]=-99; 20 dataToHuffmanCode(Huffman2,data); 21 fprintf(in,"\n"); 22 fprintf(in,"解码后的码为:\n"); 23 fprintf(in,"%s",Huffman2); 24 fflush(in); 25 fclose(in); 26 }
主函数:
1 int main() 2 { 3 HuffmanTree HT; 4 HuffmanCode HC; 5 char Huffman[1000]=""; 6 char Huffman1[1000]=""; 7 HuffmanCode *huffmanCode; 8 int i, n; 9 float *w, wei; 10 int choice; 11 for (;;) 12 { 13 printf("\n 哈夫曼编码 \n"); 14 printf(" 1.哈夫曼编码实现\n"); 15 printf(" 2.输入一串数据(数据为1—9之间从小到大连续的数),编译成哈夫曼编码\n"); 16 printf(" 3.将一串哈夫曼编码,还原成原来的数据\n"); 17 printf(" 4.输入一个文本,统计文本中各字符串的数目,计算相应的权重,使用该权重构建哈夫曼编码,使用该编码编译原文件\n"); 18 printf(" 5.退出系统\n"); 19 printf("请选择:"); 20 scanf("%d", &choice); 21 switch (choice) 22 { 23 case 1: 24 printf("\n 需要创建的文件有多少个码元 = " ); 25 scanf("%d", &n); 26 w = (float *)malloc((n + 1) * sizeof(float)); 27 printf("\ninput the %d element's weight:\n", n); 28 for(i = 1; i <= n; i++) 29 { 30 printf("%d: ", i); 31 fflush(stdin); 32 scanf("%f", &wei); 33 w[i] = wei; 34 } 35 createHuffmanTree(&HT, w, n); 36 creatHuffmanCode(&HT, &HC, n); 37 break; 38 case 2: 39 printf("\n输入数据为:(以-99结束)"); 40 int data[100]; 41 for(int i=1;i<100;i++){ 42 scanf("%d",&data[i]); 43 if(data[i]==-99){ 44 break; 45 } 46 } 47 huffmanCode=dataToHuffmanCode(Huffman,data); 48 break; 49 case 3: 50 int data1[100]; 51 printf("\n哈夫曼编码解码后为:\n"); 52 HuffmanCodeTodata(Huffman,data1,huffmanCode); 53 break; 54 case 4: 55 printf("\n需要实现将aaa.txt文件放在桌面上!\n"); 56 fileHuff(); 57 break; 58 case 5: 59 printf("请退出系统!\n"); 60 exit(0); 61 break; 62 } 63 } 64 return 1; 65 }