关于哈弗曼树的理解
今天我们就来梳理一下哈夫曼树。哈夫曼树的思想我觉得可以归结成,由小到大,逐步合并。为了更好地理解,我们来看一个实际问题吧:
我们知道,在我们使用26键拼音输入时,每一个字母都会有一个使用频率。现在假定 (A:3;B:0.2;C:0.1;D:4),其中这四个字母有各自的权值。当我们将这四个字母进行二进制编码时,我们会引发思考。假设我们固定编码长度,让计算机按固定的二进制位数(如本例子为两位)来进行识别,是一种可行的方法 (A:00 ,B:01 ,C:10 ,D:11)计算机每次取两位来进行识别。
但是这样运行效率就不高了,比如当字母数量增加到26个字母时候,计算机为了准确识别,只能增加识别二进制的位数。我们不禁思考,如果让频繁出现的 D字母能更快的识别,就能增加效率了。此时,哈弗曼树便因运而生。先看看上述例子的哈弗曼树构建过程。
可以看到每个字母就是各个叶子,如果按照左0右1的规则,那么可以编写成:D :1 , A:01,C:001,B:000 。(这里是因为)我们可以发现,这样编写的好处是识别的唯一性。比如一段二进制信息(101000),我们发现只有唯一一个结果:DAB。不存在因为编码的重合导致识别错误。
我先给出哈夫曼树及其编码,然后再进行分析
#include<iostream>
#include<cstring>
using namespace std;
#define leaves 8
#define pointSum leaves*2-1
#define MaxValue 10000
typedef struct{
char ch;
double weight;
int parent;
int Lchild,Rchild;
}Htreetype;
typedef struct{
int bit[leaves];
int start;
char ch;
}Hcodetype;
void select(Htreetype t[],int k,int *p1,int *p2)
{
*p1 = *p2 = 0;
int smallOne,smallTwo;
smallOne = smallTwo = MaxValue;
int i;
for(i=0; i<k; i++)
{
if(t[i].parent == -1)
{
if(t[i].weight < smallOne)
{
smallTwo = smallOne;
smallOne = t[i].weight;
*p2 = *p1;
*p1 = i;
}
else if(t[i].weight <smallTwo)
{
smallTwo = t[i].weight;
*p2 = i;
}
}
}
}
void HffmanTree(Htreetype t[])
{
int i,p1,p2;
double value;
p1 = p2 = 0;
for(i=0; i<pointSum; i++)
{
t[i].weight = 0;
t[i].parent = -1;
t[i].Lchild = -1;
t[i].Rchild = -1;
}
cout<<"-----------请输入8字母的权值-----------"<<endl;
for(i=0;i<leaves;i++)
{
cin>>t[i].ch;
cin>>value;
t[i].weight = value;
}
for(i=leaves; i<pointSum;i++)
{
select(t,i,&p1,&p2);
t[p1].parent = i;
t[p2].parent = i;
t[i].Lchild = p1;
t[i].Rchild = p2;
t[i].weight = t[p1].weight + t[p2].weight;
}
}
void HffmanCode(Hcodetype code[],Htreetype t[])
{
int i,c,p;
Hcodetype cd;
HffmanTree(t);
for (i = 0; i < leaves; i++)
{
cd.start = leaves;
cd.ch = t[i].ch;
c = i;
p = t[i].parent;
while (p != -1)
{
cd.start--;
if (t[p].Lchild == c)
cd.bit[cd.start] = 0;
else
cd.bit[cd.start] = 1;
c = p;
p = t[c].parent;
}
code[i] = cd;
}
}
void show(Htreetype t[], Hcodetype code[])
{
int i, j;
for (i = 0; i<leaves; i++)
{
cout<<code[i].ch<<" ";
for (j = code[i].start; j<leaves; j++)
cout<<code[i].bit[j]<<" ";
cout<<endl;
}
}
int main()
{
Htreetype t[pointSum];
Hcodetype code[leaves];
HffmanCode(code, t);
show(t,code);
return 0;
}
typedef struct{
char ch;
double weight;
int parent;
int Lchild,Rchild;
}Htreetype;
先建立一个结构体,结构体中有存储数据类型,权值,父母和左右孩子。
void HffmanTree(Htreetype t[])
{
int i,p1,p2;
double value;
p1 = p2 = 0;
for(i=0; i<pointSum; i++)
{
t[i].weight = 0;
t[i].parent = -1;
t[i].Lchild = -1;
t[i].Rchild = -1;
}
cout<<"-----------请输入8字母的权值-----------"<<endl;
for(i=0;i<leaves;i++)
{
cin>>t[i].ch;
cin>>value;
t[i].weight = value;
}
for(i=leaves; i<pointSum;i++)
{
select(t,i,&p1,&p2);
t[p1].parent = i;
t[p2].parent = i;
t[i].Lchild = p1;
t[i].Rchild = p2;
t[i].weight = t[p1].weight + t[p2].weight;
}
}
void select(Htreetype t[],int k,int *p1,int *p2)
{
*p1 = *p2 = 0;
int smallOne,smallTwo;
smallOne = smallTwo = MaxValue;
int i;
for(i=0; i<k; i++)
{
if(t[i].parent == -1)
{
if(t[i].weight < smallOne)
{
smallTwo = smallOne;
smallOne = t[i].weight;
*p2 = *p1;
*p1 = i;
}
else if(t[i].weight <smallTwo)
{
smallTwo = t[i].weight;
*p2 = i;
}
}
}
}
然后是建立哈弗曼树的过程,首先先初始化哈夫曼树,然后找出哈弗曼树中最小的两个元素进行合并,并把刚刚已经使用的那两个元素进行标记,最后可以得到如下哈夫曼树的列表形式。
void HffmanCode(Hcodetype code[],Htreetype t[])
{
int i,c,p;
Hcodetype cd;
HffmanTree(t);
for (i = 0; i < leaves; i++)
{
cd.start = leaves;
cd.ch = t[i].ch;
c = i;
p = t[i].parent;
while (p != -1)
{
cd.start--;
if (t[p].Lchild == c)
cd.bit[cd.start] = 0;
else
cd.bit[cd.start] = 1;
c = p;
p = t[c].parent;
}
code[i] = cd;
}
}
然后是哈夫曼编码的过程,先取出叶子,然后由叶子开始向上寻亲,然后逐步判断自己是左孩子还是右孩子,根据左0右1的规则进行哈夫曼编码。
void show(Htreetype t[], Hcodetype code[])
{
int i, j;
for (i = 0; i<leaves; i++)
{
cout<<code[i].ch<<" ";
for (j = code[i].start; j<leaves; j++)
cout<<code[i].bit[j]<<" ";
cout<<endl;
}
}
最后是输出哈夫曼代码的过程。看一下运行结果