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