赫夫曼樹及赫夫曼編碼是資料結構與算法中的一個重要知識點,在生活中也是應用廣泛,現在我來教大家如何建構一顆赫夫曼樹并且實作它的赫夫曼編碼。先講解思路,後面會附完整代碼及運作效果圖。
建構赫夫曼樹的算法思路如下
1.輸入一串字元,統計出每個字元的出現頻度建立字元頻度表。
typedef struct //字元頻度表
{
int weight; //權值
char c; //對應字元
}F_W;
F_W w[100];//字元頻度存放數組
int char_count(string s,char a) //計算單個字元頻度
{ int i,count=0;
for(i=0;i<s.length();i++)
if(s[i]==a) //如果字元a出現一次 count+1
count++;
return count;
}
void create_w(string s) //建立字元頻度表w
{ int i,j,flag;
w[0].weight=char_count(s,s[0]);//先統計第一個字元頻度并入表
w[0].c=s[0];
int t=1; //t表示數組的循環變量
for(i=1;i<s.length();i++)
{ flag=1; //表示這個字元之前沒有出現過
for(j=0;j<i;j++)
{if(s[j]==s[i])//如果這個字元之前已經出現過
{ flag=0;
break;
}
}
if(flag==1) //如果之前沒出現過,統計該字元頻度并放進數組w
{ w[t].weight=char_count(s,s[i]);
w[t].c=s[i];
t++;
}
}
}
2.從頻度表中選擇權值(即頻度)最小的兩個節點依次建構赫夫曼樹
typedef struct
{ int weight; //權值
int parent,lchild,rchild;//雙親及左右孩子
char c; //對應字元
}HTNode,*HuffmanTree;
void Select(HuffmanTree HT,int j,int &s1,int &s2) //在所有的字元節點中挑選兩個權值最小的指派給s1,s2
{ int i,k,min1=0,min2=0;
for(i=1;i<=j;i++)
{ if(HT[i].parent!=0) //如果節點已經有雙親則不算在比較範圍内
continue;
if(HT[min1].weight>HT[i].weight)
min1=i;
}
for(k=1;k<=j;k++)
{ if(HT[k].parent!=0) //如果節點已經有雙親則不算在比較範圍内
continue;
if(k==min1) //如果循環到min1直接跳到下一個,不能讓min1的值與min2的值相等
continue;
if(HT[min2].weight>HT[k].weight)
{
min2=k;
}
}
s1=min1;
s2=min2;
}
int Init(HuffmanTree &HT,F_W *w,int n) //初始化赫夫曼樹
{ if(n<=1)
return 0;
int m=2*n-1,i; //m表示總結點數
int s1,s2; //存儲最小兩節點的變量
HuffmanTree p;
HT=(HuffmanTree)malloc((m+1)*sizeof(HTNode));//0号不啟用
HT[0].weight=999;//預設0号為最大權值,為上面的min1,min2的預設值
for(p=HT+1,i=1;i<=n;i++,p++,w++) //将n個字元所對應的單個根節點賦予權值
{ p->lchild=p->rchild=p->parent=0;
p->weight=w->weight;
p->c=w->c;
}
for(;i<=m;i++) //将剩下的分支節點置為空
p->lchild=p->rchild=p->parent=p->weight=0;
for(i=n+1;i<=m;i++) //建立赫夫曼樹
{ Select(HT,i-1,s1,s2); //選出兩個最小的節點
HT[s1].parent=HT[s2].parent=i;
HT[i].lchild=s1;HT[i].rchild=s2;
HT[i].weight=HT[s1].weight+HT[s2].weight; //權值合并為一個新節點
HT[i].parent=0;
}
return 1;
}
3.求出每個字元所對應的赫夫曼編碼,建立編碼表
int CreateTable(HuffmanCode &HC,HuffmanTree HT,int n) //建立編碼表 對每個字元進行編碼
{ HC=(HuffmanCode)malloc((n+1)*sizeof(char *));
char *cd=(char *)malloc(n*sizeof(char));
cd[n-1]='\0';
int start,c,i,f;
for(i=1;i<=n;++i)
{ start=n-1; //編碼結束符位置
for(c=i,f=HT[i].parent;f!=0;c=f,f=HT[f].parent)
if(HT[f].lchild==c) cd[--start]='0'; //左分支設定0右分支設定1
else cd[--start]='1';
HC[i]=(char *)malloc((n-start)*sizeof(char));
strcpy(HC[i],&cd[start]);
}
free(cd);
return 1;
}
如下面題目要求示例:
1、初始化(Init):能夠對輸入的任意長度的字元串s進行統計,統計每個字元的頻度,并建立哈夫曼樹
2、建立編碼表(CreateTable):利用已經建好的哈夫曼樹進行編碼,并将每個字元的編碼輸出。
3、編碼(Encoding):根據編碼表對輸入的字元串進行編碼,并将編碼後的字元串輸出。
4、譯碼(Decoding):利用已經建好的哈夫曼樹對編碼後的字元串進行譯碼,并輸出譯碼結果。
下面給出完整代碼及運作結果
#include <iostream>
#include <stdlib.h>
#include <string.h>
using namespace std;
typedef char * *HuffmanCode; //動态配置設定數組存儲每個字元的赫夫曼編碼表
typedef struct //字元頻度表
{
int weight; //權值
char c; //對應字元
}F_W;
typedef struct
{ int weight; //權值
int parent,lchild,rchild;//雙親及左右孩子
char c; //對應字元
}HTNode,*HuffmanTree;
F_W w[100];//字元頻度存放數組
char S1[1000]; //将字元串編碼後形成的二進制字元串存放數組
int char_count(string s,char a) //計算單個字元頻度
{ int i,count=0;
for(i=0;i<s.length();i++)
if(s[i]==a) //如果字元a出現一次 count+1
count++;
return count;
}
void create_w(string s) //建立字元頻度表w
{ int i,j,flag;
w[0].weight=char_count(s,s[0]);//先統計第一個字元頻度并入表
w[0].c=s[0];
int t=1; //t表示數組的循環變量
for(i=1;i<s.length();i++)
{ flag=1; //表示這個字元之前沒有出現過
for(j=0;j<i;j++)
{if(s[j]==s[i])//如果這個字元之前已經出現過
{ flag=0;
break;
}
}
if(flag==1) //如果之前沒出現過,統計該字元頻度并放進數組w
{ w[t].weight=char_count(s,s[i]);
w[t].c=s[i];
t++;
}
}
}
void Select(HuffmanTree HT,int j,int &s1,int &s2) //在所有的字元節點中挑選兩個權值最小的指派給s1,s2
{ int i,k,min1=0,min2=0;
for(i=1;i<=j;i++)
{ if(HT[i].parent!=0) //如果節點已經有雙親則不算在比較範圍内
continue;
if(HT[min1].weight>HT[i].weight)
min1=i;
}
for(k=1;k<=j;k++)
{ if(HT[k].parent!=0) //如果節點已經有雙親則不算在比較範圍内
continue;
if(k==min1) //如果循環到min1直接跳到下一個,不能讓min1的值與min2的值相等
continue;
if(HT[min2].weight>HT[k].weight)
{
min2=k;
}
}
s1=min1;
s2=min2;
}
int Init(HuffmanTree &HT,F_W *w,int n) //初始化赫夫曼樹
{ if(n<=1)
return 0;
int m=2*n-1,i; //m表示總結點數
int s1,s2; //存儲最小兩節點的變量
HuffmanTree p;
HT=(HuffmanTree)malloc((m+1)*sizeof(HTNode));//0号不啟用
HT[0].weight=999;//預設0号為最大權值,為上面的min1,min2的預設值
for(p=HT+1,i=1;i<=n;i++,p++,w++) //将n個字元所對應的單個根節點賦予權值
{ p->lchild=p->rchild=p->parent=0;
p->weight=w->weight;
p->c=w->c;
}
for(;i<=m;i++) //将剩下的分支節點置為空
p->lchild=p->rchild=p->parent=p->weight=0;
for(i=n+1;i<=m;i++) //建立赫夫曼樹
{ Select(HT,i-1,s1,s2); //選出兩個最小的節點
HT[s1].parent=HT[s2].parent=i;
HT[i].lchild=s1;HT[i].rchild=s2;
HT[i].weight=HT[s1].weight+HT[s2].weight; //權值合并為一個新節點
HT[i].parent=0;
}
return 1;
}
int CreateTable(HuffmanCode &HC,HuffmanTree HT,int n) //建立編碼表 對每個字元進行編碼
{ HC=(HuffmanCode)malloc((n+1)*sizeof(char *));
char *cd=(char *)malloc(n*sizeof(char));
cd[n-1]='\0';
int start,c,i,f;
for(i=1;i<=n;++i)
{ start=n-1; //編碼結束符位置
for(c=i,f=HT[i].parent;f!=0;c=f,f=HT[f].parent)
if(HT[f].lchild==c) cd[--start]='0'; //左分支設定0右分支設定1
else cd[--start]='1';
HC[i]=(char *)malloc((n-start)*sizeof(char));
strcpy(HC[i],&cd[start]);
}
free(cd);
return 1;
}
int Encoding(string S,int n,HuffmanCode HC) //編碼字元串
{ int i,j;
for(i=0;i<S.length();i++)
{ for(j=0;j<n;j++) //在頻度表中找對應的字元
if(w[j].c==S[i])
{
strcat(S1,HC[j+1]);} //将該字元的赫夫曼編碼存進S數組
}
return 1;
}
int Decoding(HuffmanTree HT,HuffmanCode HC,int n) //譯碼
{ int m=2*n-1,i=0,j;
while(S1[i]!='\0')
{ if(S1[i]=='0')
m=HT[m].lchild;
else
m=HT[m].rchild;
if(HT[m].lchild==0) //找到葉子節點(赫夫曼樹必須有兩個節點,是以隻需判斷一個左或者右孩子)
{ for(j=0;j<n;j++)
if(w[j].weight==HT[m].weight&&HT[m].c==w[j].c) //找到權值對應的字元
cout<<w[j].c; //輸出
m=2*n-1; //重新從根節點出發
}
i++;
}
}
int main()
{ string s;
int n=0,i;
cin>>s;
create_w(s);
HuffmanTree HT;
HuffmanCode HC;
for(i=0;w[i].weight!=0;i++)
n++;
Init(HT,w,n);
CreateTable(HC,HT,n);
cout<<"編碼表為:"<<endl;
for(i=1;i<=n;i++)
cout<<w[i-1].c<<":"<<HC[i]<<" ";
cout<<endl;
cout<<"編碼為:"<<endl;
Encoding(s,n,HC);
cout<<S1<<endl;
cout<<"譯碼為:"<<endl;
Decoding(HT,HC,n);
return 0;
}
運作結果圖: