8.7哈夫曼树
哈夫曼树(最优二叉树):带权路径长度最小的二叉树。
路径:从树中一个结点到另一个结点之间的分支。
路径长度:路径上的分支数。
结点的权:给树的每个结点赋予的实数。
结点的带权路径长度:该结点到根结点之间的路径长度与结点上权的乘积。
树的带权路径长度:
8.7.1构造哈夫曼树
(2)在森林中选取两棵根结点权值最小的树作左右子树,构造一棵新的二叉树,置新二叉树根结点权值为其左右子树根结点权值之和;
(3)在森林中删除这两棵树,同时将新的二叉树加入森林中;
(4)重复上述(2)(3)两步,直到森林中只含一棵树为止,即为哈夫曼树。
例:
有4个结点,权值分别为7,5,2,4,构造有4个叶子结点的哈夫曼树。
解:
例:
有8个结点,权值分别为5,29,7,8,14,23,3,11,构造有8个叶子结点的哈夫曼树。
解:
8.7.2哈夫曼树的特点
权值最大的叶子结点离根结点最近;
该树上无度为1的结点存在;
一棵有n个叶子结点的Huffman树有2n-1个结点。(因为是二叉树)
例:
对n(n>=2)个权值均不相同的字符构成哈夫曼树,下列关于该哈夫曼树的叙述中,错误的是______。
A.该树一定是一棵完全二叉树
B.树中一定没有度为1的结点
C.树中两个权值最小的结点一定是兄弟结点
D.树中任一非叶结点的权值一定不小于下一层任一结点的权值
答案:A
8.7.3哈夫曼编码
根据字符出现频率编码,使电文总长最短。
若对每个字符设计长度不等的编码,且出现次数较多的字符尽可能采取短的编码,则可减少总长。
若编码长度不等,则任一字符的编码都不能成为另一字符编码的前缀。
首先,根据字符出现频率构造Huffman树;
然后,结点左孩子的分支标“0”,右孩子的分支标“1”;
每个字符的编码即为从根到每个叶子的路径上得到的0、1序列。
例题:
要传输的字符集D={C,A,S,T,;},字符出现频率 w={2,4,2,3,3},
构造字符的哈夫曼编码,画出最优二叉树,并求编码的平均长度。
注:编码的平均长度为字符出现的概率乘以字符编码长度,再求和。
编码的平均长度=16/7
例题:
电文是{ CAS;CAT;SAT },其编码是?
解:
11010111011101000011111000
从Huffman树根开始,从待译码电文中逐位取码。若编码是“0”,则向左走;若编码是“1”,则向右走,一旦到达叶子结点,则译出一个字符;再重新从根出发,直到电文结束。
例题:
若接收的电文为:“1101000”,对应的原字符序列?
解:
译文只能是“CAT”
8.7.4哈夫曼算法实现
结点类型:
typedef struct {
int weight;
int parent,lchild,rchild;
//记录父、左右孩子的数组下标
}HTNode,*HuffmanTree;
字符的哈夫曼编码存储类型定义:
typedef char *HuffmanCode[N+1];
(1、2、3、4编号对应的是结点7、5、2、4)
void HuffmanCoding( HuffmanTree HT,HuffmanCode HC,int *w,int n)
{
if(n<=1) return;//n为叶子结点
m=2*n-1;//树中无度为1的结点,因此总结点数为n+n-1
HT=(HuffmanTree)malloc((m+1)*sizeof(HTNode));
//1-n号单元存放叶子结点,进行初始化
for(p=HT+1,i=1;i<=n;++i,++p,++w)
*p={ *w,0,0,0 }; //每个结点都有四个域,权值,左右孩子,双亲
for(;i<=m;++i,++p) *p={ 0,0,0,0 };
//初始化非叶子结点的值,都设为0
for( i=n+1; i<=m; ++i )
{
Select ( HT, i-1, s1,s2 );//在ht[1]到ht[i-1]的范围内选择两个parent为0且weight最小的结点,将序号分别赋值给s1,s2
HT[s1].parent=i; HT[s2].parent=i;
HT[i].lchild=s1; HT[i].rchild=s2;
HT[i].weight=HT[s1].weight+HT[s2].weight;
}
}
哈夫曼编码:
//给每一个叶子结点写上哈夫曼编码(01组成的)
HC=(HuffmanCode)malloc((n+1)*sizeof(char *));
cd=(char *)malloc(n*sizeof(char));
cd[n-1]=‘\0’;//cd就是每个叶子结点的哈夫曼编码域
//f是双亲结点,c是当前结点
for( i=1; i<=n; ++i ) //0不用,i是各个叶子结点的标号
{
start=n-1;
for( c=i, f=HT[i].parent;//第一个叶子结点的标号是1
f!=0;//双亲结点不为0,即还没遍历到最后一个结点(离根最近的结点)
c=f,f=HT[f].parent )//下一循环是该节点的双亲节点
if( HT[f].lchild==c ) cd[--start]=‘0’;//若该节点是它双亲结点的左孩子
else cd[--start]=‘1’;
HC[i]= ( char * ) malloc ( (n-start) *sizeof( char ) );
strcpy( HC[i], & cd[ start ] );
}
free(cd);