zoukankan      html  css  js  c++  java
  • Huffman树及其应用

    哈夫曼树又称为最优二叉树,哈夫曼树的一个最主要的应用就是哈夫曼编码,本文通过简单的问题举例阐释哈夫曼编码的由来,并用哈夫曼树的方法构造哈夫曼编码,最终解决问题来更好的认识哈夫曼树的应用--哈夫曼编码。

    一、引子

    在学习中我们经常遇到将各科成绩改为优秀、良好、中等、及格和不及格。那么根据分级原理,代码表示为:

    if(a<60)
       b = "不及格“;
    else if(a<70)
      b = "及格";
    else if(a<80)
      b = "中等";
    else if(a<90)
      b = "良好";
    else if(a<70)
      b = "优秀";
     

    那么用二叉树表示为:

    QQ截图20150818204321

    在实际应用中,5个学生等级的分布规律如表所示

    分数 0~59 60~ 69 70~79 80~89 90~100
    所占比例 5% 15% 40% 30% 10%

    在上面中70分以上的比例是80%,但都需要经过三次比较判断才得出结果,显然不合理。

    Huffman提出了想法,加入修改成如图所示

    QQ截图20150818204755

    二、Huffman定义和原理

    根据上面例子的引出,我们将各个成绩所占比例,当做权重标示在分支上,如图所示

    QQ截图20150818205030

    哈夫曼树定义为:给定n个权值作为n个叶子结点,构造出的一棵二叉树带权路径长度达到最小。

    1、路径和路径长度

    在一棵树中,从一个结点往下可以达到的孩子或子孙结点之间的通路,称为路径。通路中分支的数目称为路径长度。若规定根结点的层数为1,则从根结点到第L层结点的路径长度为L-1。在二叉树a中根节点到结点D路径长度为4,在二叉树b中结点D到根节点路径长度为2.

    树的路径长度:树根到每个结点的路径长度之和。二叉树a = 1+1+2+2+3+3+4+4= 20.二叉树B = 1+2+3+3+2+1+2+2 = 16.

    2、结点的权及带权路径长度

    若将树中结点赋给一个有着某种含义的数值,则这个数值称为该结点的权。结点的带权路径长度为:从根结点到该结点之间的路径长度与该结点的权的乘积。

    3、树的带权路径长度

    树的带权路径长度规定为所有叶子结点的带权路径长度之和。

    树的带权路径长度计算

    二叉树a = 5*1+15*2+40*3+30*4+10*4 = 315;

    二叉树b = 5*3+15*3+40*2+30*2+10*2 = 220;

    如果我们现在用10000个学生需要转换,二叉树a需要31500(别往里是百分数,315/100*10000),二叉树b需要22000次,差不多少了三分之一呢。

       从定义中可以看出哈夫曼树的一个最重要的特点:带权路径长度最短。

    huffman树构建

    哈夫曼编码步骤:

    一、对给定的n个权值{W1,W2,W3,...,Wi,...,Wn}构成n棵二叉树的初始集合F= {T1,T2,T3,...,Ti,...,Tn},其中每棵二叉树Ti中只有一个权值为Wi的根结点,它的左右子树均为空。(为方便在计算机上实现算 法,一般还要求以Ti的权值Wi的升序排列。)
    二、在F中选取两棵根结点权值最小的树作为新构造的二叉树的左右子树,新二叉树的根结点的权值为其左右子树的根结点的权值之和。
    三、从F中删除这两棵树,并把这棵新的二叉树同样以升序排列加入到集合F中。
    四、重复二和三两步,直到集合F中只有一棵二叉树为止。

    简易的理解就是,假如我有A,B,C,D,E五个字符,出现的频率(即权值)分别为5,4,3,2,1,那么我们第一步先取两个最小权值作为左右子树构造一个新树,即取1,2构成新树,其结点为1+2=3,如图:

    12

    虚线为新生成的结点,第二步再把新生成的权值为3的结点放到剩下的集合中,所以集合变成{5,4,3,3},再根据第二步,取最小的两个权值构成新树,如图:

    13

    再依次建立哈夫曼树,如下图:

    14

    其中各个权值替换对应的字符即为下图:

    15

    所以各字符对应的编码为:A->11,B->10,C->00,D->011,E->010

    //在Huffman结点数组中找权值最小的两个结点i1和i2
    void SelectNode(pHuffmanNode itemnode,int &MinNum1,int &MinNum2,int num)
    {
        /* 假设两个结点的权值 */
        int MaxWeight1,MaxWeight2;
        MaxWeight1 = MaxWeight2 = 10000;
        /* 找出所有结点中权值最小、无父结点的两个结点*/
        for(int i = 0;i<num;i++)
        {
            //parent不为-1,就是已经判断过了。
            if (itemnode[i].weight<MaxWeight1&&itemnode[i].parent == -1)
            {
                //将m1赋值给m2保证m1是最小的,m2是第二小的
                MaxWeight2 = MaxWeight1;
                MinNum2 = MinNum1;
                MaxWeight1 = itemnode[i].weight;
                MinNum1 = i;
            }
            else if (itemnode[i].weight<MaxWeight2&& itemnode[i].parent == -1)
            {
                MaxWeight2 = itemnode[i].weight;
                MinNum2     = i;
            }
        }
    }
    
    //创建huffman树
    void CreateHuffman(pHuffmanNode HuffmanNodeArray,int n)
    {    
        //创建树结点
        //叶子结点(度为0)的个数为n,分支结点(度为2的n2)是n-1,二叉树没有n1,所以树结点为2*n-1;
        for (int i = 0;i< 2*n-1;i++)
        {
            HuffmanNodeArray[i].weight =0;
            HuffmanNodeArray[i].parent = -1;
            HuffmanNodeArray[i].lchild = -1;
            HuffmanNodeArray[i].rchild = -1;
            HuffmanNodeArray[i].value = i;
    
        }
    
        //创建权值的结点,也就是在HUffman中叶子结点
        for (int i = 0;i<n;i++)
        {
            cout<<"Please input weight of leaf node"<<endl;
            cin>>HuffmanNodeArray[i].weight;
        }
    
        //循环构建Huffman树,需要判断n-1次
        for (int i = n;i<2*n-1;i++)
        {
            int minNum1 = 0;
            int minNum2 = 0;
            SelectNode(HuffmanNodeArray,minNum1,minNum2,i);
            //minNum1,minNum2数值小于n,其实就是为叶子结点确定父节点是谁。
            HuffmanNodeArray[minNum1].parent = i;
            HuffmanNodeArray[minNum2].parent = i;
            //创建两个叶子结点的父节点(分支结点),确定左右孩子,赋值权重
            HuffmanNodeArray[i].weight = HuffmanNodeArray[minNum1].weight+HuffmanNodeArray[minNum2].weight;
            HuffmanNodeArray[i].rchild = minNum1;
            HuffmanNodeArray[i].lchild = minNum2;
    
        }    
        
    }
    CreateHuffman

    Huffman编码

    赫夫曼在研究这种最优二叉树时的主要目的是解决当年远距离通信(主要是电报)的数据传输的最优化问题。比如传输一串字符“BADCADFEED”,采用二进制数据表示,如下表:

    字母 A B C D E F
    二进制字符 000 001 010 011 100 101

    编码之后的二进制数据流为“001000011010000011101100100011”,对方接收时同样按照3位一组解码。现在假设这6个字母出现的频率不同,A 27%,B %8,C 15%,D 15%,E 30%,F 5%。下面将27、8、15、15、30、5分别作为A、B、C、D、E、F的权值构造赫夫曼树,如下图:

    QQ截图20150818211230

    将右图赫夫曼树的权值左分支改为0,右分支改为1。

    现在将这6个字母用从根节点到叶子所经过路径的0或1来编码,得到的编码表如下:

    字母 A B C D E F
    编码 01 1001 101 00 11 1000

    将“BADCADFEED”再次编码得到“1001010010101001000111100”,共25个字符,与之前编码得到的30个字符相比大约节约了17%的存储和传输成本。

    在解码时,用同样的赫夫曼树,即发送方和接收方约定好同样的赫夫曼编码规则。当接收方接收到“1001010010101001000111100”时,比对右图中的赫夫曼树。

    void CreateHuffmanCode(pHuffmanNode itemHuffmanNode,pHuffmanCode itemHuffmanCode,int n)
    {
        //获取huffman树,从叶子结点开始遍历,现找到叶子结点的父节点,如果此节点在左边就是0,否则就是1
        int ntemp,ptemp;
        HuffmanCode phutemp;
        for (int i = 0;i<n;i++)
        {
             ntemp = i;
            phutemp.start = n-1;
            ptemp = itemHuffmanNode[i].parent;
            //循环这个叶子结点的父节点,当父节点为-1是,就是到了树的根节点
            while(ptemp != -1)
            {
                if (itemHuffmanNode[ptemp].lchild == ntemp)
                {
                    phutemp.Bit[phutemp.start] = 0;
                }
                else
                    phutemp.Bit[phutemp.start] = 1;
                phutemp.start--;
                ntemp = ptemp;
                ptemp = itemHuffmanNode[ntemp].parent;
            }
    
            /* 保存求出的每个叶结点的哈夫曼编码和编码的起始位 */
            for (int j=phutemp.start+1; j<n; j++)
            { itemHuffmanCode[i].Bit[j] = phutemp.Bit[j];}
            itemHuffmanCode[i].start = phutemp.start;
        }
    }
    CreateDecode

    代码

      1 #include <iostream>
      2 #include <stdio.h>
      3 using namespace std;
      4 
      5 const int MAXBIT = 100;
      6 const int MAXVALUE = 1000;
      7 //创建结构体,抽象描述树节点
      8 typedef struct HuffmanNode
      9 {
     10     int parent;
     11     int lchild;
     12     int rchild;
     13     //结点权重
     14     int weight;
     15     //可以写字母等其他雷兴国
     16     int value;
     17 }stu_HuffmanNode,*pHuffmanNode;
     18 //创建结构体,保存编码
     19 typedef struct HuffmanCode
     20 {
     21     //编码的bit
     22     int Bit[MAXBIT];
     23     int start;
     24 }*pHuffmanCode;
     25 
     26 //在Huffman结点数组中找权值最小的两个结点i1和i2
     27 void SelectNode(pHuffmanNode itemnode,int &MinNum1,int &MinNum2,int num)
     28 {
     29     /* 假设两个结点的权值 */
     30     int MaxWeight1,MaxWeight2;
     31     MaxWeight1 = MaxWeight2 = 10000;
     32     /* 找出所有结点中权值最小、无父结点的两个结点*/
     33     for(int i = 0;i<num;i++)
     34     {
     35         //parent不为-1,就是已经判断过了。
     36         if (itemnode[i].weight<MaxWeight1&&itemnode[i].parent == -1)
     37         {
     38             //将m1赋值给m2保证m1是最小的,m2是第二小的
     39             MaxWeight2 = MaxWeight1;
     40             MinNum2 = MinNum1;
     41             MaxWeight1 = itemnode[i].weight;
     42             MinNum1 = i;
     43         }
     44         else if (itemnode[i].weight<MaxWeight2&& itemnode[i].parent == -1)
     45         {
     46             MaxWeight2 = itemnode[i].weight;
     47             MinNum2     = i;
     48         }
     49     }
     50 }
     51 
     52 //创建huffman树
     53 void CreateHuffman(pHuffmanNode HuffmanNodeArray,int n)
     54 {    
     55     //创建树结点
     56     //叶子结点(度为0)的个数为n,分支结点(度为2的n2)是n-1,二叉树没有n1,所以树结点为2*n-1;
     57     for (int i = 0;i< 2*n-1;i++)
     58     {
     59         HuffmanNodeArray[i].weight =0;
     60         HuffmanNodeArray[i].parent = -1;
     61         HuffmanNodeArray[i].lchild = -1;
     62         HuffmanNodeArray[i].rchild = -1;
     63         HuffmanNodeArray[i].value = i;
     64 
     65     }
     66 
     67     //创建权值的结点,也就是在HUffman中叶子结点
     68     for (int i = 0;i<n;i++)
     69     {
     70         cout<<"Please input weight of leaf node"<<endl;
     71         cin>>HuffmanNodeArray[i].weight;
     72     }
     73 
     74     //循环构建Huffman树,需要判断n-1次
     75     for (int i = n;i<2*n-1;i++)
     76     {
     77         int minNum1 = 0;
     78         int minNum2 = 0;
     79         SelectNode(HuffmanNodeArray,minNum1,minNum2,i);
     80         //minNum1,minNum2数值小于n,其实就是为叶子结点确定父节点是谁。
     81         HuffmanNodeArray[minNum1].parent = i;
     82         HuffmanNodeArray[minNum2].parent = i;
     83         //创建两个叶子结点的父节点(分支结点),确定左右孩子,赋值权重
     84         HuffmanNodeArray[i].weight = HuffmanNodeArray[minNum1].weight+HuffmanNodeArray[minNum2].weight;
     85         HuffmanNodeArray[i].rchild = minNum1;
     86         HuffmanNodeArray[i].lchild = minNum2;
     87 
     88     }    
     89     
     90 }
     91 
     92 void CreateHuffmanCode(pHuffmanNode itemHuffmanNode,pHuffmanCode itemHuffmanCode,int n)
     93 {
     94     //获取huffman树,从叶子结点开始遍历,现找到叶子结点的父节点,如果此节点在左边就是0,否则就是1
     95     int ntemp,ptemp;
     96     HuffmanCode phutemp;
     97     for (int i = 0;i<n;i++)
     98     {
     99          ntemp = i;
    100         phutemp.start = n-1;
    101         ptemp = itemHuffmanNode[i].parent;
    102         //循环这个叶子结点的父节点,当父节点为-1是,就是到了树的根节点
    103         while(ptemp != -1)
    104         {
    105             if (itemHuffmanNode[ptemp].lchild == ntemp)
    106             {
    107                 phutemp.Bit[phutemp.start] = 0;
    108             }
    109             else
    110                 phutemp.Bit[phutemp.start] = 1;
    111             phutemp.start--;
    112             ntemp = ptemp;
    113             ptemp = itemHuffmanNode[ntemp].parent;
    114         }
    115 
    116         /* 保存求出的每个叶结点的哈夫曼编码和编码的起始位 */
    117         for (int j=phutemp.start+1; j<n; j++)
    118         { itemHuffmanCode[i].Bit[j] = phutemp.Bit[j];}
    119         itemHuffmanCode[i].start = phutemp.start;
    120     }
    121 }
    122 
    123 //解码
    124 void decodeHuffman(char* str ,pHuffmanNode itemHuffmanNode,int Num)
    125 {
    126     int i,tmp=0,code[1024];
    127     int m=2*Num-1;
    128     char *nump;
    129     char num[1024];
    130     //将权值设置为0,1
    131     for(i=0;i<strlen(str);i++)
    132     {
    133         if(str[i]=='0')
    134             num[i]=0;        
    135         else
    136             num[i]=1;                    
    137     } 
    138     i = 0;
    139     nump = &num[0];
    140     while(nump < (&num[strlen(str)]))
    141     {
    142         tmp = m-1;
    143         //循环获取叶子结点在数组中序号
    144         while(itemHuffmanNode[tmp].lchild != -1&& itemHuffmanNode[tmp].rchild != -1)
    145         {
    146             if (*nump == 0)
    147             {
    148                 tmp = itemHuffmanNode[tmp].lchild;
    149             }
    150             else
    151                 tmp = itemHuffmanNode[tmp].rchild;
    152             nump++;
    153         }
    154     }
    155     cout<<itemHuffmanNode[tmp].value<<endl;
    156 }
    157 
    158 int main()
    159 {
    160     HuffmanNode HuffmanNodeArray[100];
    161     HuffmanCode HuffmanCodeArray[100];
    162     pHuffmanNode phuffman= &HuffmanNodeArray[0];
    163     pHuffmanCode phuffmancode = &HuffmanCodeArray[0];
    164     cout<<"输入个数"<<endl;
    165     int n ;
    166     cin>>n;
    167     CreateHuffman(phuffman,n);
    168     CreateHuffmanCode(phuffman,phuffmancode,n);
    169     for (int i = 0;i< n;i++)
    170     {
    171         cout<<phuffmancode[i].start<<endl;
    172     }
    173     cout<<"输入解码符号"<<endl;
    174     int test;
    175     cin>>test;
    176     decodeHuffman("test",phuffman,n);
    177     system("pause");
    178 }
    HuffmanTest
  • 相关阅读:
    科学开源项目 : 建立一个 新的 光衍射 模型
    光不是电磁波 摩擦力不是电磁力
    收录一篇 贝尔不等式 的 文章
    数学家 程序员 哲学家 艺术家
    论 数学 的 工具性
    数学 怎么用?
    随便说说 广义相对论 的 时间膨胀
    逻辑物理学 : 光子 有 质量 吗 ?
    我对 “光子有质量 , 引力对光子有作用” 存疑
    收录一些 高等数学 的 文章
  • 原文地址:https://www.cnblogs.com/polly333/p/4742261.html
Copyright © 2011-2022 走看看