C语言之霍夫曼编码学习

  

1,霍夫曼编码描述
哈夫曼树─即最优二叉树,带权路径长度最小的二叉树,经常应用于数据压缩。 在计算机信息处理中,“哈夫曼编码”是一种一致性编码法(又称“熵编码法”),用于数据的无损耗压缩。这一术语是指使用一张特殊的编码表将源字符(例如某文件中的一个符号)进行编码。这张编码表的特殊之处在于,它是根据每一个源字符出现的估算概率而建立起来的(出现概率高的字符使用较短的编码,反之出现概率低的则使用较长的编码,这便使编码之后的字符串的平均期望长度降低,从而达到无损压缩数据的目的)。这种方法是由David.A.Huffman发展起来的。 例如,在英文中,e的出现概率很高,而z的出现概率则最低。当利用哈夫曼编码对一篇英文进行压缩时,e极有可能用一个位(bit)来表示,而z则可能花去25个位(不是26)。用普通的表示方法时,每个英文字母均占用一个字节(byte),即8个位。二者相比,e使用了一般编码的1/8的长度,z则使用了3倍多。若能实现对于英文中各个字母出现概率的较准确的估算,就可以大幅度提高无损压缩的比例。

2,问题描述
霍夫曼编码前首先要统计每个字的字频,即出现次数,例如:

 

1、将所有字母出现的次数以从小到大的顺序排序,如上图

2、每个字母都代表一个终端节点(叶节点),比较F.O.R.G.E.T五个字母中每个字母的出现频率,将最小的两个字母频率相加合成一个新的节点。如上图所示,发现F与O的频率最小,故相加2+3=5,将F、O组成一个树,F为左节点,O为右节点,(FO)为根节点,每个节点的取值为其出现频率(FO的出现频率为5)

3、比较5.R.G.E.T,发现R与G的频率最小,故相加4+4=8,将RG组成一个新的节点

4、比较5.8.E.T,发现5与E的频率最小,故相加5+5=10,因此将FO作为左节点,E作为右节点,FOE作为根节点

5、比较8.10.T,发现8与T的频率最小,故相加8+7=15,将RG作为左节点,T作为右节点,RGT作为根节点

6、最后剩10.15,没有可以比较的对象,相加10+15=25,FOE作为左节点,RGT作为右节点

 

根节点不取值,每个左子节点取值0,右子节点取值1,将每个字母从根节点开始遍历,沿途的取值组成编码:

 

 

首先选择一个文本,统计每个字符出现的次数,组成以下数组:
typedef struct FrequencyTreeNode {
    int freq;
    char c;
    struct FrequencyTreeNode *left;
    struct FrequencyTreeNode *right;
} FrequencyTreeNodeStruct, *pFrequencyTreeNodeStruct;

然后将获得的数组frequencies进行排序,按照freq由小到大的顺序组成一个二叉查找树,FrequencyTreeNodeStruct,从二叉查找树中找到最小的节点,从树中删除,再取最小的节点,两个子节点,组成一个新的树,根节点c为0,freq为两个子节点的和,加入frequencies中,并排序,重复该步骤,一直到frequencies中只有一个节点,则该节点为Huffman coding tree的根节点

 

以short类型按照前述的规则为每个字符编码,尔后将文本翻译为Huffman coding,再通过Huffman coding tree进行解码,验证编码的正确性。


3,代码实现

  1. #include<stdio.h>
  2. #define n 5 //叶子数目
  3. #define m (2*n-1) //结点总数
  4. #define maxval 10000.0
  5. #define maxsize 100 //哈夫曼编码的最大位数
  6.  
  7.  
  8. //定义结构体
  9. typedef struct FrequencyTreeNode {
  10.     int freq;
  11.     char c;
  12.     struct FrequencyTreeNode *left;
  13.     struct FrequencyTreeNode *right;
  14. } FrequencyTreeNodeStruct, *pFrequencyTreeNodeStruct;
  15.  
  16.  
  17. FrequencyTreeNodeStruct frequencies[MAXALPABETNUM];
  18.  
  19.  
  20. typedef struct
  21. {
  22.      char bits[n]; //位串
  23.      int start; //编码在位串中的起始位置
  24.      char ch; //字符
  25. }codetype;
  26.  
  27.  
  28. // 读取文件内容,统计字符以及出现频率
  29. void readTxtStatistics(char* fileName)
  30. {
  31.     unsigned int nArray[52] = {0};
  32.     unsigned int i, j;
  33.     char szBuffer[MAXLINE];
  34.     int k=0;
  35.     // 读取文件内容
  36.     FILE* fp = fopen(fileName, \"r\");
  37.     if (fp != NULL)
  38.     { /*读取文件内容,先统计字母以及出现次数*/
  39.         while(fgets(szBuffer, MAXLINE, fp)!=NULL)
  40.         {
  41.             for(i = 0; i < strlen(szBuffer); i++)
  42.             {
  43.                 if(szBuffer[i] <= \'Z\' && szBuffer[i] >= \'A\')
  44.                 {
  45.                     j = szBuffer[i] - \'A\';
  46.                 }
  47.                 else if(szBuffer[i] <= \'z\' && szBuffer[i] >= \'a\')
  48.                 {
  49.                     j = szBuffer[i] - \'a\' + 26;
  50.                 }
  51.                 else
  52.                 continue;
  53.                 nArray[j]++;
  54.             }
  55.         }
  56.  
  57.  
  58.         // 然后赋值给frequencies数组
  59.         for(i = 0, j = \'A\'; i < 52; i++, j++)
  60.         {
  61.             if (nArray[i] >0)
  62.             {
  63.             /*****/
  64.                 frequencies[k].c=j;
  65.                 frequencies[k].freq=nArray[i];
  66.                 frequencies[k].left=NULL;
  67.                 frequencies[k].right=NULL;
  68.                 k++;
  69.                 printf(\"%c:%d\\n\", j, nArray[i]);
  70.             }
  71.             if(j == \'Z\')
  72.                 j = \'a\' - 1;
  73.         }
  74.     }
  75. }
  76.  
  77.  
  78. //建立哈夫曼树
  79. void huffMan(frequencies tree[]){
  80.     int i,j,p1,p2;//p1,p2分别记住每次合并时权值最小和次小的两个根结点的下标
  81.     float small1,small2,f;
  82.     char c;
  83.     for(i=0;i<m;i++) //初始化
  84.     {
  85.          tree[i].parent=0;
  86.          tree[i].lchild=-1;
  87.          tree[i].rchild=-1;
  88.          tree[i].weight=0.0;
  89.     }
  90.      printf(\"【依次读入前%d个结点的字符及权值(中间用空格隔开)】\\n\",n);
  91.  
  92.  
  93.     //读入前n个结点的字符及权值
  94.     for(i=0;i<n;i++)
  95.     {
  96.         printf(\"输入第%d个字符为和权值\",i+1);
  97.         scanf(\"%c %f\",&c,&f);
  98.         getchar();
  99.         tree[i].ch=c;
  100.         tree[i].weight=f;
  101.     }
  102.     //进行n-1次合并,产生n-1个新结点
  103.     for(i=n;i<m;i++)
  104.     {
  105.          p1=0;p2=0;
  106.          //maxval是float类型的最大值
  107.          small1=maxval;small2=maxval;
  108.          //选出两个权值最小的根结点
  109.          for(j=0;j<i;j++)
  110.          {
  111.               if(tree[j].parent==0)
  112.               if(tree[j].weight<small1)
  113.                {
  114.                     small2=small1; //改变最小权、次小权及对应的位置
  115.                     small1=tree[j].weight;
  116.                     p2=p1;
  117.                     p1=j;
  118.                }
  119.                else if(tree[j].weight<small2)
  120.                 {
  121.                  small2=tree[j].weight; //改变次小权及位置
  122.                  p2=j;
  123.                 }
  124.                tree[p1].parent=i;
  125.                tree[p2].parent=i;
  126.                tree[i].lchild=p1; //最小权根结点是新结点的左孩子
  127.                tree[i].rchild=p2; //次小权根结点是新结点的右孩子
  128.                tree[i].weight=tree[p1].weight+tree[p2].weight;
  129.         }
  130.    }
  131. }
  132.  
  133.  
  134. //根据哈夫曼树求出哈夫曼编码,code[]为求出的哈夫曼编码,tree[]为已知的哈夫曼树
  135. void huffmancode(codetype code[],frequencies tree[])
  136. {
  137.      int i,c,p;
  138.      codetype cd; //缓冲变量
  139.      for(i=0;i<n;i++)
  140.      {
  141.           cd.start=n;
  142.           cd.ch=tree[i].ch;
  143.           c=i; //从叶结点出发向上回溯
  144.           p=tree[i].parent; //tree[p]是tree[i]的双亲
  145.           while(p!=0)
  146.           {
  147.                cd.start--;
  148.                if(tree[p].lchild==c)
  149.                     cd.bits[cd.start]=\'0\'; //tree[i]是左子树,生成代码\'0\'
  150.                else
  151.                     cd.bits[cd.start]=\'1\'; //tree[i]是右子树,生成代码\'1\'
  152.                c=p;
  153.                p=tree[p].parent;
  154.           }
  155.           code[i]=cd; //第i+1个字符的编码存入code[i]
  156.      }
  157. }
  158.  
  159.  
  160.  
  161.  
  162. //根据哈夫曼树解码
  163. void decode(hufmtree tree[])
  164. {
  165.      int i,j=0;
  166.      char b[maxsize];
  167.      char endflag=\'2\'; //电文结束标志取2
  168.      i=m-1; //从根结点开始往下搜索
  169.      printf(\"输入发送的编码(以\'2\'为结束标志):\");
  170.      gets(b);
  171.      printf(\"编码后的字符为\");
  172.      while(b[j]!=\'2\')
  173.      {
  174.           if(b[j]==\'0\')
  175.            i=tree[i].lchild; //走向左子节点
  176.           else
  177.            i=tree[i].rchild; //走向右子节点
  178.           if(tree[i].lchild==-1) //tree[i]是叶结点
  179.           {
  180.              printf(\"%c\",tree[i].ch);
  181.              i=m-1; //回到根结点
  182.           }
  183.           j++;
  184.      }
  185.      printf(\"\\n\");
  186.      if(tree[i].lchild!=-1&&b[j]!=\'2\') //文本读完,但尚未到叶子结点
  187.      printf(\"\\nERROR\\n\"); //输入文本有错
  188. }
  189.  
  190.  
  191.  
  192.  
  193. void main()
  194. {
  195.      printf(\"---------------—— 哈夫曼编码实战 ——\\n\");
  196.      printf(\"总共有%d个字符\\n\",n);
  197.      frequencies tree[m];
  198.      codetype code[n];
  199.      int i,j;//循环变量
  200.      huffMan(tree);//建立哈夫曼树
  201.      huffmancode(code,tree);//根据哈夫曼树求出哈夫曼编码
  202.      printf(\"【输出每个字符的哈夫曼编码】\\n\");
  203.      for(i=0;i<n;i++)
  204.      {
  205.           printf(\"%c: \",code[i].ch);
  206.           for(j=code[i].start;j<n;j++)
  207.           printf(\"%c \",code[i].bits[j]);
  208.           printf(\"\\n\");
  209.      }
  210.      printf(\"【读入内容,并进行编码】\\n\");
  211.      // 开始编码
  212.      decode(tree);
  213. }

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值