哈夫曼树的定义:
给定n个权值作为n的叶子结点,构造一棵二叉树,若带权路径长度达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树(Huffman Tree)。哈夫曼树是带权路径长度最短的树,权值较大的结点离根较近。
上面有一大堆名词,我们一个一个来看看。
1、路径和路径长度
在一棵树中,从一个结点往下可以达到的孩子或孙子结点之间的通路,称为路径。
通路中分支的数目称为路径长度。
若规定根结点的层数为1,则从根结点到第L层结点的路径长度为L-1;
2、结点的权及带权路径长度
若将树中结点赋给一个有着某种含义的数值,则这个数值称为该结点的权。
(权通常被认为是某个字符编码出现的概率
比如有一个字符数组是a,b,c,d. 那么假设一篇文章中出现这四个字符的概率分别为1,2,3,4,因此,a的权为1,依次对应)
结点的带权路径长度为:
从根结点到该结点之间的路径长度与该结点的权的乘积。
3、树的带权路径长度
树的带权路径长度规定为所有叶子结点的带权路径长度之和,记为WPL
。
以上有三种构造树的方式,其中WPL值最小的树即为哈夫曼树,即为最优二叉树,WPL值为35;
哈夫曼树的构造:
根据哈弗曼树的定义,一棵二叉树要使其WPL值最小,必须使权值越大的叶子结点越靠近根结点,而权值越小的叶子结点
越远离根结点。
哈弗曼树的应用:
在电文传输中,需要将电文中出现的每个字符进行二进制编码。在设计编码时需要遵守两个原则:
(1)发送方传输的二进制编码,到接收方解码后必须具有唯一性,即解码结果与发送方发送的电文完全一样;
(2)发送的二进制编码尽可能地短。下面我们介绍两种编码的方式。
1. 等长编码
这种编码方式的特点是每个字符的编码长度相同(编码长度就是每个编码所含的二进制位数)。假设字符集只含有4个字符A,B,C,D,用二进制两位表示的编码分别为00,01,10,11。若现在有一段电文为:ABACCDA,则应发送二进制序列:00010010101100,总长度为14位。当接收方接收到这段电文后,将按两位一段进行译码。这种编码的特点是译码简单且具有唯一性,但编码长度并不是最短的。
2. 不等长编码
在传送电文时,为了使其二进制位数尽可能地少,可以将每个字符的编码设计为不等长的,使用频度较高的字符分配一个相对比较短的编码,使用频度较低的字符分配一个比较长的编码。例如,可以为A,B,C,D四个字符分别分配0,00,1,01,并可将上述电文用二进制序列:000011010发送,其长度只有9个二进制位,但随之带来了一个问题,接收方接到这段电文后无法进行译码,因为无法断定前面4个0是4个A,1个B、2个A,还是2个B,即译码不唯一,因此这种编码方法不可使用。
因此,为了设计长短不等的编码,以便减少电文的总长,还必须考虑编码的唯一性,即在建立不等长编码时必须使任何一个字符的编码都不是另一个字符的前缀,这宗编码称为前缀编码(prefix code)
通过哈夫曼树构造的编码称为哈夫曼编码:
代码奉上:
#include <iostream>
#include <stdlib.h>
#define MaxValue 1000
using namespace std;
struct tree
{
int parent;
int lchild;
int rchild;
int weight;
int flag;
}myhufftree[10];
struct code
{
int bit[10]; //倒序存
int ary[10]; //顺序存
int len; //每个编码的长度
}myhuffcode[10];
void hufftree(int n ,int weight[])
{
//包括初始化和构建树
int x1,x2,m1,m2;
int i ,j;
for(i = 0;i < 2*n - 1;i++) //初始化
{
if(i < n)
myhufftree[i].weight = weight[i];
else
myhufftree[i].weight = 0;
myhufftree[i].parent = 0;
myhufftree[i].flag = 0;
myhufftree[i].lchild = myhufftree[i].rchild = -1;
}
for(i = 0; i < n - 1;i++) //构造哈弗曼树 这里的两个循环要好好看
{
m1 = m2 = MaxValue;
x1 = x2 = 0;
for(j = i;j < n + i;j++)
{
if(myhufftree[j].weight < m1 && myhufftree[j].flag == 0)
{
m2 = m1;
m1 = myhufftree[j].weight;
x2 = x1;
x1 = j;
}
else
{
if(myhufftree[j].weight < m2 && myhufftree[j].flag == 0)
{
m2 = myhufftree[j].weight;
x2 = j;
}
}
}
myhufftree[x1].parent = n + i;
myhufftree[x2].parent = n + i;
myhufftree[n+i].lchild = x1;
myhufftree[n+i].rchild = x2;
myhufftree[x1].flag = myhufftree[x2].flag = 1;
myhufftree[n+i].flag = 0;
myhufftree[n+i].weight = myhufftree[x1].weight + myhufftree[x2].weight;
}
}
void huffcode(int n)
{//进行哈夫曼编码
int i, j ;
int child;
int parent;
int len;
for(i = 0;i < n;i++)
{
child = i;
parent = myhufftree[i].parent;
len = 0;
while(parent != 0) //对每次最小的两个下标进行组合
{
if(myhufftree[parent].lchild == child)
myhuffcode[i].bit[len] = 0;
else
myhuffcode[i].bit[len] = 1;
len++;
child = parent;
parent = myhufftree[parent].parent;
}
myhuffcode[i].len = len;
for(j = 0;j < len;j++) //将编码顺序存入数组
{
myhuffcode[i].ary[j] = myhuffcode[i].bit[len - j - 1];
}
}
}
void main()
{
int weight[] ={2,4,5,7};
int n = 4;
int sum;
int i ,j;
hufftree(n,weight);
huffcode(n);
for(sum = 0,i =0; i < n ;i++) //输出
{
cout<<"weight : "<<weight[i]<<" code :";
for(int j = 0; j< myhuffcode[i].len ;j++)
{
cout<<myhuffcode[i].ary[j];
}
sum += myhufftree[i].weight * myhuffcode[i].len; //计算WPL
cout<<endl;
}
cout<<endl;
cout<<"WPL :"<<sum<<endl;
};