哈夫曼树的定义
树的结点常常赋予一个表示某种意义的数值,称为结点的权。从树的根结点到任意结点的路径长度与该结点上权值的乘积,称为该结点的带权路径长度,树中所有叶结点的带权路径长度之和称为该树的带权路径长度,记为
式中,wi是第i个叶结点所带的权值,li是该叶结点到根结点的路径长度。
在含有n个带权叶子结点的二树中,其中带权路径长度(WPL)最小的二叉树称为哈夫曼树,同时也称为最优二 叉树。
例如,下图中3棵二叉树都有4个叶子结点a,b,c,d;,分别带权7,5,2,4。
它们的带权路径长度分别为
(a)WPL=72+52+22+42=36
(b)WPL=73+53+21+42=46
(c)WPL=71+52+23+43=35
显然(c)中的树为哈夫曼树,它的WPL最小。通过观察,我们可以看到权值越大的叶结点离根结点越近,权值越小的叶结点离根结点越远。
哈夫曼树的构造
根据前面的内容我们已经清楚哈夫曼树的定义和结构了,那么该如何构造出最优二叉树呢?
1)将n个结点看作一个结点数组
2)根据权值对结点数组进行排序,按照权值由小到大排序
3)将结点数组中第一第二个元素(权值最小的两个结点)作为新结点的左右子结点,新结点的权值为左右子结点的权值之和
4)删除选出的两个结点,并将新结点加入到结点数组中
5)重复2),3),4)的操作,直到结点数组中只剩下一个结点
代码实现
//根据权值对结点排序
public HTree[] sortHtree(HTree[] nodes){
for(int i=0;i<nodes.length;i++){
for(int j=i+1;j<nodes.length;j++){
if(nodes[i].weight>nodes[j].weight){
HTree tem=nodes[j];
nodes[j]=nodes[i];
nodes[i]=tem;
}
}
}
return nodes;
}
//创建哈夫曼树
public HTree createTree(int[] weight,String[] strs){
HTree[] nodes=new HTree[weight.length];//
for(int i=0;i<nodes.length;i++){
nodes[i]=new HTree();
nodes[i].weight=weight[i];
nodes[i].str=strs[i];
}
while(nodes.length>1){
nodes=sortHtree(nodes);//根据权重对节点排序
HTree node=new HTree();
node.left=nodes[0];
node.right=nodes[1];
node.weight=nodes[0].weight+nodes[1].weight;
HTree[] nodes2=new HTree[nodes.length-1];
for(int i=2;i<nodes.length;i++){
nodes2[i-2]=nodes[i];
}
nodes2[nodes2.length-1]=node;
nodes=nodes2;
}
return nodes[0];
}
哈夫曼编码
相信很多小伙伴都听说过从二叉树到哈夫曼压缩,那么它是怎么利用哈夫曼来压缩的呢,或者说的它原理是什么?
这涉及到哈夫曼编码了,哈夫曼编码其实很简单,首先构造好哈夫曼树,每个叶结点的哈夫曼码是根据根结点到叶结点的的路径来编的,我们要先定义好编码规则,如向左走为0,向右走为1(0和1没有明确表示左边还是右边),
//给哈夫曼树每个叶结点编码
public HTree getCode(HTree root,String code){
if(root!=null){
if(root.left==null&&root.right==null){
String st=code;
root.code=st;
}
getCode(root.left,code+"0");
getCode(root.right,code+"1");
}
return root;
}
根据字符的编码可以得到字符串的码串,如按照图上字符编码可得字符串aabcc的码串为0 0 101 100 100,一个0、1的储存大小为1个bite,按照哈夫编码,a的存储大小为1个bite,而按ASCII编码,每个字母的存储大小为8个bite。同理,对照其他的字符,可以看到利用哈夫曼可以实现文件压缩。