赫夫曼(Huffman)树又称最优树,是一类带权路径长度最短的树。
解析部分术语:
路径:从树中的一个结点到另一个结点之间的分支构成这两个结点之间的路径
路径长度:路径上的分支数目称作路径长度
树的路径长度:从树根到每一结点的路径长度之和
权:赋予某个实体的一个量是对实体的某个或某些属性的数值化描述
结点的带权路径长度:从该结点到树根之间的路径长度与结点上权的乘积
树的带权路径长度:树中所有叶子结点的带权路径长度之和
赫夫曼树:假设有 m 个权值{w1,w2,w3.......wm},可以构造一棵含 n 个叶子结点的二叉树,每个叶子结点的权为 w1,则其中带权路径长度 WPL 最小的二叉树称做最优二叉树或赫夫曼树
赫夫曼编码问题
已知某系统在通信联络中只可能出现8种字符,其概率分别为0.05,0.29,0.07,0.08,0.14,0.23,0.03,0.11,试编写算法求其赫夫曼编码。
根据其出现的概率可设 8 个字符的权值为:w=(5,29,7,8,14,23,3,11),其对应的赫夫曼树如图
HC
1 — — >0 1 1 0
2 — — >1 0
3 — — > 1 1 1 0
4 — — >1 1 1 1
5 — — >1 1 0
6 — — >0 0
7 — — >0 1 1 1
8 — — >0 1 0
源程序为:
#include<iostream>
#include<string.h>
using namespace std;
typedef char **HuffmanCode; //赫夫曼编码表的存储表示
typedef struct{
int weight;
int parent,lchild,rchild;
}HTNode,*HuffmanTree;
void Select(HuffmanTree &HT,int n,int &s1,int &s2)
{
int min=0;
for(int i=1;i<=n;i++){
if(HT[i].parent==0){
if(min==0){
min=HT[i].weight;
min++;
s1=i;
}
if(HT[i].weight<min){
min=HT[i].weight;
s1=i; }
}
}
min=0;
for(int i=1;i<=n;i++){
if(HT[i].parent==0 && i!=s1){
if(min==0){
min=HT[i].weight;
min++;
s2=i;
}
if(HT[i].weight<min){
min=HT[i].weight;
s2=i;
}
}
}
if(s2<s1){//保证s1的下标值 小于 s2实现左右子树的大小对称
s1=s1+s2;
s2=s1-s2;
s1=s1-s2;
/*int t=s1;
s1=s2;
s2=t;*/
}
}
//构造赫夫曼树 HT
void CreatHuffmanTree(HuffmanTree &HT,int n)
{
int m,s1,s2;
if(n<=1) return ;
m=2*n-1;
HT=new HTNode[m+1]; //树的信息存储数组
for(int i=1;i<=m;i++){
HT[i].parent=0;
HT[i].lchild=0;
HT[i].rchild=0;
}
//输入前 n 个单元中叶子结点的权值
cout<<"请输入8个权值:"<<endl;
for(int i=1;i<=n;i++)
cin>>HT[i].weight;
//开始创建赫夫曼树
for(int i=n+1;i<=m;i++){
//在HT数组中选择两个其双亲域为 0 且权值最小的结点,并返回它们的下标 S1,S2
Select(HT,i-1,s1,s2);
cout<<"s1="<<s1<<" s2="<<s2<<endl;
HT[s1].parent=i;
HT[s2].parent=i;
//得到新结点 i 从森林中删除 s1、s2,将 s1和s2的双亲域由 0 改为 i
HT[i].lchild=s1;
HT[i].rchild=s2;
HT[i].weight=HT[s1].weight+HT[s2].weight;
// i 的权值为左右子树权值之和
}
}
//从叶子到根逆向求每个字符的赫夫曼编码,存储在编码表 HC 中
void CreatHuffmanCode(HuffmanTree HT,HuffmanCode &HC,int n)
{
int start,c,f;
HC=new char* [n+1];
char *cd=new char[n];
cd[n-1]='\0';
for(int i=1;i<=n;i++){
start=n-1;
c=i;
f=HT[i].parent; // f 指向结点 c 的双亲结点
while(f!=0){ //从叶子开始向上回溯,直到根结点
--start; //回溯一次 start 向前指一个位置
if(HT[f].lchild==c) cd[start]='0'; //结点 c 是 f 的左子树,则生成代码 0
else cd[start]='1'; //结点 c 是 f 的右子树,则生成代码 1
c=f;
f=HT[f].parent; //继续向上回溯
}
HC[i]=new char[n-start];
strcpy(HC[i],&cd[start]); //将求得的编码从临时空间 cd 复制到 HC 的当前行中
}
delete cd; //释放临时空间
}
int main()
{
HuffmanTree HT;
HuffmanCode HC;
int n=8;
CreatHuffmanTree(HT,n);
CreatHuffmanCode(HT,HC,n);
for(int i=1;i<=n;i++)
cout<<"第"<<i<<"个字符的编码为"<<HC[i]<<endl;
return 0;
}
输入:5 29 7 8 14 23 3 11
运行结果为:
s1=1 s2=7
s1=3 s2=4
s1=8 s2=9
s1=5 s2=10
s1=6 s2=11
s1=2 s2=12
s1=13 s2=14
第1个字符的编码为0110
第2个字符的编码为10
第3个字符的编码为1110
第4个字符的编码为1111
第5个字符的编码为110
第6个字符的编码为00
第7个字符的编码为0111
第8个字符的编码为010