哈夫曼树的创建和编码

Posted 火雨_Nick

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了哈夫曼树的创建和编码相关的知识,希望对你有一定的参考价值。

                                                                       哈夫曼树的创建和编码

   

       项目忙的要死,博客停了两天,做外包的真不好受,还是做产品的强。软件最后最值钱的不是代码,而是相关的文档,文档清楚,依葫芦画瓢照做出来应该不难。项目结束了至少要整理出需求规格说明书,系统设计文档,用户使用说明书,开发进度表,投标书,工作说明书等文档。

      本文根据《数据结构与算法》(C语言版)(第三版) 整理。

      本博文作为学习资料,源代码是VC++ 6.0上可执行程序,我挪到了VS2010中执行。

       1.哈夫曼树又称最优二叉树,是一类带权路径长度最短的树。

       对于最优二叉树,权值越大的结点越接近树的根结点,权值越小的结点越远离树的根结点。
       最优二叉树的构造算法步骤:
       (1)根据给定的n个权值w1,w2,...,wn构成n棵二叉树森林F=T1,T2,...,Tn,其中每一棵二叉树Ti中都只有一个权为wi的根结点,其左、右子树为空。
       (2)在森林F中选出两棵根结点权值最小的树作为一棵新二叉树的左、右子树,新二叉树的根结点的权值为其左、右子树根结点的权值之和。
       (3)从F中删除这两棵二叉树,同时把新二叉树加入到F中。
       (4)重复步骤(2)、(3),直到F中只含有一棵树为止,此树便为最优二叉树。

       哈夫曼树的构造过程示意图如下:

                                                

       哈夫曼树的结点类型声明:

    struct TreeNode
    
       int weight;
       int parent;
       int lchild;
       int rchild;
    ;
    typedef struct TreeNode HFTreeNode;
    typedef HFTreeNode HuffmanTree;

        哈夫曼树的构造算法:

     #define MaxSize 1000     //叶子数目
     void Select(HuffmanTree *HT, int g, int &s1, int &s2);
     void CreateHuffmanTree(HuffmanTree T[MaxSize], int n)
     
         int i,p1,p2;
         if(n<1)
           return 1;
         m=2*n;        //计算哈夫曼树的结点大小
         for(i=1; i<m; i++)
         
            T[i].parent=0;
            T[i].lchild=0;
            T[i].rchild=0;
            T[i].weight=0;
         

         for(i=1; i<n; i++)     //读入叶子结点的权值
         
           scanf("%d",&weight);
           T[i].weight=weight;
          

         for(i=n; i<m-1; i++)
         
            SelectMin(T, i-1, p1, p2);
            //在T[0...i-1]中选择两个权值最小的根结点,其序号分别为p1和p2
            T[p1].parent=T[p2].parent=i;
            T[i].lchild=p1;      //最小权的根结点是新结点的左孩子
            T[i].rchild=p2;      //次小权的根结点是新结点的右孩子
            T[i].weight=T[p1].weight+T[p2].weight;
        
    

    void selectMin(HuffmanTree *HT, int g, int &s1, int &s2)
    
       int j, k, m, n;
       for(k=1; k<=g; k++)     //找到一个parent为-1的子树
       
          if(HT[k].parent==0)
          
            s1=k;
            break;
           
       

       for(j=1; j<=g; j++)
       
          if((HT[j].weight<=HT[k].weight)&&(HT[j].parent==0))
          //找到一个parent为-1权值最小的子树
          s1=j;
       

       for(m=1; m<=g; m++)
       
          if((HT[m].parent==0)&&(m!=s1))
          
            s2=m;
            break;
           
       

       for(n=1; n<=g; n++)
       
         if((HT[n].weight<HT[m].weight)&&(HT[n].parent==0)&&(n!=s1))
           s2=n;
       
   

      2.哈夫曼编码

       哈夫曼编码是一种变长编码。其定义如下:
       对于给定的字符集D=d1,d2,...,dn及其频率分布F=w1,w2,...,wn,用d1,d2,...,dn作为叶结点,w1,w2,...,wn作为结点的权,利用哈夫曼算法构造一棵最优二叉树,将树中每个分支结点的左分支标上"0";右分支标上"1",把从根到每个叶子的路径符号("0"或"1")连接起来,作为该叶子的编码。

       哈夫曼编码是在哈夫曼树的基础上求出来的,其基本思想是:从叶子结点di(0<=i<n)出发,向上回溯至根结点,依次求出每个字符的编码。

       示例:对于字符集D=A,B,C,D,其频率(单位:千次)分布为F=12,6,2,18,下图给出D的哈夫曼编码图。

                                    

       哈夫曼编码的回溯步骤如下:
       (1)选出哈夫曼树的某一个叶子结点。
       (2)利用其双亲指针parent找到其双亲结点。
       (3)利用找到的双亲结点的指针域中的lchild和rchild,判断该结点是双亲的左孩子还是右孩子。若该结点是其双亲结点的左孩子,则生成代码0;若该结点是其双亲结点的右孩子,则生成代码1。
       (4)由于生成的编码与要求的编码反序,将所生成的编码反序。
       (5)重复步骤(1)~(4),直到所有结点都回溯完。

       反序方法:首先将生成的编码从后向前依次存放在一个临时的一维数组中,并设一个指针start指示编码在该一维数组中的起始位置。当某个叶子结点的编码完成时,从临时的一维数组的start处将编码复制到该字符对应的bits中即可。

       哈夫曼编码的存储结构:

    struct CodeNode
    
       char ch;           //存储字符
       char bits[n+1];    //存放编码位串
    ;
    typedef struct CodeNode CodeNoe;
    typedef CodeNoe HUffmanCode[n]; 

       哈夫曼编码的算法:

     void CharSetHuffmanEncoding(HuffmanTree T, HuffmanCode H)
        //根据哈夫曼树T求哈夫曼编码表H
        int c, p, i;
        char cd[n+1];
        int start;
        cd[n]='\\0';
        for(i=0; i<n; i++)
        
          //依次求叶子T[i]的编码
          H[i].ch=getchar();      //读入叶子T[i]对应的字符
          start=n;                //编码起始位置的初值
          c=i;                    //从叶子T[i]开始上溯
          while(p=T[c].parent>0)
          
             if(T[p].lchild==c)
             
                cd[--start]='0';
             
             else
             
                cd[--start]='1';
              
             c=p;      //继续上溯
          
          strcpy(H[i].bits, &cd[start]);      //复制编码位串
       
      

      3. 哈夫曼解码

         哈夫曼解码过程:从哈夫曼树的根结点出发,依次识别电文的中的二进制编码,如果为0,则走向左孩子,否则走向右孩子,走到叶结点时,就可以得到相应的解码字符。

        算法如下:

    void CharSetHuffmanDecoding(HuffmanTree T, char* cd, int n)
      
           int p=2*n-2;      //从根结点开始
           int i=0;
           //当要解码的字符串没有结束时
           while(cd[i]!='/0')
           
                //当还没有到达哈夫曼树的叶子并且要解码的字符串没有结束时
               while((T[p].lchild!=0 && T[p].rchild != 0) && cd[i] != '\\0')
                
                     if(cd[i] == '0')
                     
                        //如果是0,则叶子在左子树
                        p=T[p].lchild;
                     
                     else
                     
                        //如果是1,则叶子在左子树
                        p=T[p].rchild;
                     
                     i++;
          
          //如果到达哈夫曼树的叶子时
           if(T[p].lchild == 0 && T[p].rchild == 0)
           
               printf("%c", T[p].ch);
               p = 2*n-1;
            
           else      //如果编号为p的结点不是叶子,那么编码有错
            
                 printf("\\n解码出错! \\n");
                return;
             
       
        printf("\\n");
    

    4. 哈夫曼树的创建和哈夫曼编码程序:

    在VS2010中新建Win32 控制台应用程序的项目:HuffmanTree,创建结果如下图:
       
// HuffmanTree.cpp : 定义控制台应用程序的入口点。

 #include "stdafx.h"
 #include <stdlib.h>
 #include <stdio.h>
 #include <string.h>

 typedef struct HuffmanTree
 
   int weight;
   int parent, lchild, rchild;
  HuffmanTree;

 typedef struct CodeNode
 
   int ch;
   char bits[4+1];
 CodeNode;

 void SelectMin(HuffmanTree tree[], int len, int * pos1, int* pos2)
 
   int min=255;
   int i, j;
   *pos1=0;
   *pos2=0;
   for(i=0; i<len; i++)
   
     if(tree[i].parent==-1)
       if(min>tree[i].weight)
       
         min=tree[i].weight;
         *pos1=i;
       
   
   min=255;

   for(j=0; j<len; j++)
   
     if(j==*pos1)
       continue;
     if(tree[j].parent==-1)
       if(min>tree[j].weight)
       
         min=tree[j].weight;
         *pos2=j;
       
   
 

 void CreateHuffmanTree(HuffmanTree tree[], int n)
 
   int m=2*n;
   int i;
   for(i=n; i<m-1; i++)
   
     int pos1, pos2;
     HuffmanTree node;
     SelectMin(tree, i, &pos1, &pos2);
     printf("pos1=%d,pos2=%d\\n", pos1, pos2);
     node.weight=tree[pos1].weight+tree[pos2].weight;
     tree[pos1].parent=i;
     tree[pos2].parent=i;
     node.lchild=pos1;
     node.rchild=pos2;
     node.parent=-1;
     tree[i]=node;
   
 

 void HuffmanEncoding(HuffmanTree tree[])
 
   int c, p, i;
   int start;
   char cd[4+1];
   cd[4]='\\0';

   for(i=0; i<4; i++)
   
     printf("\\n");
     printf("%d",tree[i].weight);
     printf(":");
     start=4;
     c=i;
     while((p=tree[c].parent)!=-1)
     
       if(tree[p].lchild==c)
       
         cd[--start]='0';
       
       else
       
         cd[--start]='1';
       
       c=p;
     
     printf(&cd[start]);
   
 

 int main(int argc, char* argv[])
 
   HuffmanTree tree[4*2];
   int i, j;
   for(i=0; i<4; i++)
   
     tree[i].lchild=-1;
     tree[i].rchild=-1;
	 tree[i].parent=-1;
   

   printf("请输入哈夫曼树叶子结点的权值: \\n");
   for(i=0; i<4; i++)     //读入叶子结点的权值
   
     int weight;
     scanf("%d",&weight);
     tree[i].weight=weight;
   

   CreateHuffmanTree(tree, 4);
   for(j=0; j<2*4-1; j++)
   
      printf("tree[%d]:weight=%d \\n", j, tree[j].weight);
   

   HuffmanEncoding(tree);
   
   return 0;
 

    Ctrl+F5执行HuffmanTree.cpp结果如下图:

    

以上是关于哈夫曼树的创建和编码的主要内容,如果未能解决你的问题,请参考以下文章

哈夫曼编码测试学习博客

哈夫曼树和哈夫曼编码

构造哈夫曼树的过程

赫夫曼树及赫夫曼编码

哈夫曼编码--贪心策略

哈夫曼编码的实现