關於哈弗曼樹的理解
今天我們就來梳理一下哈夫曼樹。哈夫曼樹的思想我覺得可以歸結成,由小到大,逐步合併。爲了更好地理解,我們來看一個實際問題吧:
我們知道,在我們使用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;
}
}
最後是輸出哈夫曼代碼的過程。看一下運行結果