h5房卡源码C语言实现哈夫曼树、编码、解码及问题总结

1、准备知识windows

一、Huffman树数组

Huffman树是一类带权路径长度WPL最短的二叉树,中文名叫哈夫曼树或最优二叉树。缓存

相关概念:函数

结点的路径长度:从根结点到该结点的路径上分支的数目。测试

树的路径长度:树中每一个结点的路径长度之和。编码

树的带权路径长度:树中全部叶子结点的带权路径长度之和。spa

 

构造Huffman树的步骤:.net

1)  根据给定的n个权值,构造n棵只有一个根结点的二叉树,n个权值分别是这些二叉树根结点的权;指针

2)  设F是由这n棵二叉树构成的集合,在F中选取两棵根结点权值最小的树做为左、右子树,构形成一颗新的二叉树,置新二叉树根结点的权值等于左、右子树根结点的权值之和。为了使获得的哈夫曼树的结构惟一,规定根结点权值最小的做为新二叉树的左子树。code

3)  从F中删除这两棵树,并将新树加入F;

4)  重复2)、3)步,直到F中只含一棵树为止,这棵树即是Huffman树。

说明:n个结点须要进行n-1次合并,每次合并都产生一个新的结点,最终的Huffman树共有2n-1个结点。

二、Huffman编码

Huffman树在通信编码中的一个应用:

利用哈夫曼树构造一组最优前缀编码。主要用途是实现数据压缩。在某些通信场合,需将传送的文字转换成由二进制字符组成的字符串进行传输。

方法:

利用哈夫曼树构造一种不等长的二进制编码,而且构造所得的哈夫曼编码是一种最优前缀编码,使所传电文的总长度最短。

不等长编码:即各个字符的编码长度不等(如:0,10,110,011),可使传送电文的字符串的总长度尽量地短。对出现频率高的字符采用尽量短的编码,则传送电文的总长度便尽量短。

前缀编码:任何一个字符的编码都不是同一字符集中另外一个字符的编码的前缀。

 

2、代码实现

使用链表结构构建哈夫曼树并进行编码、解码,代码以下:

[cpp]  view plain  copy
 
  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3. #include <string.h>  
  4.   
  5. typedef int ELEMTYPE;  
  6.   
  7. // 哈夫曼树结点结构体  
  8. typedef struct HuffmanTree  
  9. {  
  10.     ELEMTYPE weight;  
  11.     ELEMTYPE id;        // id用来主要用以区分权值相同的结点,这里表明了下标  
  12.     struct HuffmanTree* lchild;  
  13.     struct HuffmanTree* rchild;  
  14. }HuffmanNode;  
  15.   
  16. // 构建哈夫曼树  
  17. HuffmanNode* createHuffmanTree(int* a, int n)  
  18. {  
  19.     int i, j;  
  20.     HuffmanNode **temp, *hufmTree;  
  21.     temp = malloc(n*sizeof(HuffmanNode));  
  22.     for (i=0; i<n; ++i)     // 将数组a中的权值赋给结点中的weight  
  23.     {  
  24.         temp[i] = (HuffmanNode*)malloc(sizeof(HuffmanNode));  
  25.         temp[i]->weight = a[i];  
  26.         temp[i]->id = i;  
  27.         temp[i]->lchild = temp[i]->rchild = NULL;  
  28.     }  
  29.   
  30.     for (i=0; i<n-1; ++i)       // 构建哈夫曼树须要n-1合并  
  31.     {  
  32.         int small1=-1, small2;      // small一、small2分别做为最小和次小权值的下标  
  33.         for (j=0; j<n; ++j)         // 先将最小的两个下标赋给small一、small2(注意:对应权值未必最小)  
  34.         {  
  35.             if (temp[j] != NULL && small1==-1)  
  36.             {  
  37.                 small1 = j;  
  38.                 continue;  
  39.             } else if(temp[j] != NULL)  
  40.             {  
  41.                 small2 = j;  
  42.                 break;  
  43.             }  
  44.         }  
  45.   
  46.         for (j=small2; j<n; ++j)    // 比较权值,挪动small1和small2使之分别成为最小和次小权值的下标  
  47.         {  
  48.             if (temp[j] != NULL)  
  49.             {  
  50.                 if (temp[j]->weight < temp[small1]->weight)  
  51.                 {  
  52.                     small2 = small1;  
  53.                     small1 = j;  
  54.                 } else if (temp[j]->weight < temp[small2]->weight)  
  55.                 {  
  56.                     small2 = j;  
  57.                 }  
  58.             }  
  59.         }  
  60.         hufmTree = (HuffmanNode*)malloc(sizeof(HuffmanNode));  
  61.         hufmTree->weight = temp[small1]->weight + temp[small2]->weight;  
  62.         hufmTree->lchild = temp[small1];  
  63.         hufmTree->rchild = temp[small2];  
  64.   
  65.         temp[small1] = hufmTree;  
  66.         temp[small2] = NULL;  
  67.     }  
  68.     free(temp);  
  69.     return hufmTree;  
  70. }  
  71.   
  72. // 以广义表的形式打印哈夫曼树  
  73. void PrintHuffmanTree(HuffmanNode* hufmTree)  
  74. {  
  75.     if (hufmTree)  
  76.     {  
  77.         printf("%d", hufmTree->weight);  
  78.         if (hufmTree->lchild != NULL || hufmTree->rchild != NULL)  
  79.         {  
  80.             printf("(");  
  81.             PrintHuffmanTree(hufmTree->lchild);  
  82.             printf(",");  
  83.             PrintHuffmanTree(hufmTree->rchild);  
  84.             printf(")");  
  85.         }  
  86.     }  
  87. }  
  88.   
  89. // 递归进行哈夫曼编码  
  90. void HuffmanCode(HuffmanNode* hufmTree, int depth)      // depth是哈夫曼树的深度  
  91. {  
  92.     static int code[10];  
  93.     if (hufmTree)  
  94.     {  
  95.         if (hufmTree->lchild==NULL && hufmTree->rchild==NULL)  
  96.         {  
  97.             printf("id为%d权值为%d的叶子结点的哈夫曼编码为 ", hufmTree->id, hufmTree->weight);  
  98.             int i;  
  99.             for (i=0; i<depth; ++i)  
  100.             {  
  101.                 printf("%d", code[i]);  
  102.             }  
  103.             printf("\n");  
  104.         } else  
  105.         {  
  106.             code[depth] = 0;  
  107.             HuffmanCode(hufmTree->lchild, depth+1);  
  108.             code[depth] = 1;  
  109.             HuffmanCode(hufmTree->rchild, depth+1);  
  110.         }  
  111.     }  
  112. }  
  113.   
  114. // 哈夫曼解码  
  115. void HuffmanDecode(char ch[], HuffmanNode* hufmTree, char string[])     // ch是要解码的01串,string是结点对应的字符  
  116. {  
  117.     int i;  
  118.     int num[100];  
  119.     HuffmanNode* tempTree = NULL;  
  120.     for (i=0; i<strlen(ch); ++i)  
  121.     {  
  122.         if (ch[i] == '0')  
  123.             num[i] = 0;  
  124.         else  
  125.             num[i] = 1;  
  126.     }  
  127.     if(hufmTree)  
  128.     {  
  129.         i = 0;      // 计数已解码01串的长度  
  130.         while(i<strlen(ch))  
  131.         {  
  132.             tempTree = hufmTree;  
  133.             while(tempTree->lchild!=NULL && tempTree->rchild!=NULL)  
  134.             {  
  135.                 if (num[i] == 0)  
  136.                 {  
  137.                     tempTree = tempTree->lchild;  
  138.                 } else  
  139.                 {  
  140.                     tempTree = tempTree->rchild;  
  141.                 }  
  142.                 ++i;  
  143.             }  
  144.             printf("%c", string[tempTree->id]);     // 输出解码后对应结点的字符  
  145.         }  
  146.     }  
  147. }  
  148.   
  149. int main()  
  150. {  
  151.     int i, n;  
  152.     printf("请输入叶子结点的个数:\n");  
  153.     while(1)  
  154.     {  
  155.         scanf("%d", &n);  
  156.         if (n>1)  
  157.             break;  
  158.         else  
  159.             printf("输入错误,请从新输入n值!");  
  160.     }  
  161.   
  162.     int* arr;  
  163.     arr=(int*)malloc(n*sizeof(ELEMTYPE));  
  164.     printf("请输入%d个叶子结点的权值:\n", n);  
  165.     for (i=0; i<n; ++i)  
  166.     {  
  167.         scanf("%d", &arr[i]);  
  168.     }  
  169.   
  170.     char ch[100], string[100];  
  171.     printf("请连续输入这%d个叶子结点各自所表明的字符:\n", n);  
  172.     fflush(stdin);      // 强行清除缓存中的数据,也就是上面输入权值结束时的回车符  
  173.     gets(string);  
  174.   
  175.     HuffmanNode* hufmTree = NULL;  
  176.     hufmTree = createHuffmanTree(arr, n);  
  177.   
  178.     printf("此哈夫曼树的广义表形式为:\n");  
  179.     PrintHuffmanTree(hufmTree);  
  180.     printf("\n各叶子结点的哈夫曼编码为:\n");  
  181.     HuffmanCode(hufmTree, 0);  
  182.   
  183.     printf("要解码吗?请输入编码:\n");  
  184.     gets(ch);  
  185.     printf("解码结果为:\n");  
  186.     HuffmanDecode(ch, hufmTree, string);  
  187.     printf("\n");  
  188.   
  189.     free(arr);  
  190.     free(hufmTree);  
  191.   
  192.     return 0;  
  193. }  

运行结果如图:

3、程序实现过程中遇到的问题总结

1)关于哈夫曼树,知道了叶子结点,如何不用静态数组存储整个哈夫曼树及构建过程当中的生成树?

答:使用malloc函数开辟一段内存空间存结构体类型的树,若往树中添加新的结点挂在结构体指针上便可,这就要求定义的结构体里面包含结构体指针,这也是结构体指针的做用。也就是使用链表动态存储,每一个结点都是一个包含结构体指针的结构体,生成过程当中动态开辟,无论这棵树有多少个结点均可以存下。

2)

[cpp]  view plain  copy
 
  1. typedef struct stHuNode  
  2. {  
  3.     int data;       // 权值  
  4.     struct stHuNode* lchild;        //这两句等价于 struct stHuNode* lchild, *rchild;  
  5.     struct stHuNode* rchild;      
  6. }HUNODE;  

3)scanf语句里不要有换行符?scanf函数的用法,scanf(" %d", &i);和scanf("%d ",&i);效果不一样,差异在哪?

答:scanf函数的通常形式为:scanf(“格式控制字符串”, 地址表列);格式控制字符串中不能显示非格式字符串,也就是不能显示提示字符串和换行符。” %d” 和“%d”做用同样,%d前面的空格不起做用,”%d “空格加在%d后面赋值时要多输入一个值,实际赋值操做时多输入的数并无被赋值只是缓存到了输入流stdin中,下次若是再有scanf和gets语句要求赋值时,要先用fflush(stdin);语句强制清除缓存再赋值,不然原先在stdin中的值就会被赋过去致使错误。

4)灵感:怎样解码?根据输入的01串解出相应的权值?

答:No!若是两个不一样的字符对应的权值相同呢?如何区分?起初想到若是在创建哈夫曼树的过程当中能够记录下相应的下标就不会致使相同权值没法区别的问题,但在具体如何实现上,刚开始想输出每一次建树的small1和small2,但发现这样很不清晰,用户要想肯定每一个字符的下标得按照建树过程走一遍才行,那要程序何用,并且给用户形成了很大的麻烦,不可取。后来想到,能够在结点的结构体中添加id信息,这样即便权值相同的结点也能够区分开来,h5房卡源码h5.mostsheng.com这里的id能够是下标,由于用户输入权值的顺序必定则下标惟一。若是解码解出来的是权值标号的话就没有异议了,但是下标又不是很直观清晰,不如直接输出相应的字符好,又想到两个解决办法:a)将结点id信息直接定义成字符,只不过在建树的过程当中要将字符和权值都加入结点中;b)id仍然是下标,在用户输入权值对应的字符时,用字符数组存储并和id对应起来,这样解码获得id以后,能够输出对应的字符,实现起来相对比较简单。

5)疑问:若是根据用户输入的字符串进行编码解码获得了新的字符串,旧字符串和新字符串有没有直接看出来的规律,就是说人眼观察和推导可否获得相应的规律,或者说有没有可能直接能得出规律不用通过编码解码。留为疑问!

答:后经测试发现不行。

6)gets()函数的用法,如何获取一个字符串,赋值时跳过gets()函数的执行,貌似gets()没起做用的问题。

答:当使用gets()函数以前有过数据输入,而且,操做者输入了回车确认,这个回车符没有被清理,被保存在输入缓存中时,gets()会读到这个字符,结束读字符操做。所以,从用户表面上看,gets()没有起做用,跳过了。

解决办法:

方法1、在gets()前加fflush(stdin); //强行清除缓存中的数据(windows下可行)

方法2、根据程序代码,肯定前面是否有输入语句,若是有,则增长一个getchar()命令,而后再调用 gets()命令。

方法3、检查输入结果,若是获得的字符串是空串,则继续读入,如:

[cpp]  view plain  copy
 
  1. char str[100]={0};  
  2. do {  
  3.     gets(str);  
  4. while( !str[0] );  

7)初始化数组语句:memset(str,0, sizeof(str)); 理解一下!

答:函数解释:将 str 中前 n 个字节用 ch 替换并返回 str。memset(str, 0, sizeof(str));意思是将数组str的长度(字节数,不是元素个数)置零。

memset:做用是在一段内存块中填充某个给定的值,它是对较大的结构体或数组进行清零操做的一种最快方法。

8)关于strlen()函数

答:strlen函数的用法,包含头文件string.h。且是对字符数组中的字符串求长度!

其原型为:  unsigned int strlen (char *s);

【参数说明】s为指定的字符串。

strlen()用来计算指定的字符串s 的长度,不包括结束字符"\0"。

【返回值】返回字符串s 的字符数。

注意:strlen() 函数计算的是字符串的实际长度,遇到第一个'\0'结束。若是你只定义没有给它赋初值,这个结果是不定的,它会从首地址一直找下去,直到遇到'\0'中止。而sizeof返回的是变量声明后所占的内存数,不是实际长度,此外sizeof不是函数,仅仅是一个操做符,strlen()是函数。

相关文章
相关标签/搜索