什么是哈夫曼树
在介绍哈夫曼树前,我们先介绍二叉树的基本概念,以便大家更好地理解哈夫曼树:
- 路径:两个节点之间分支的连线即两个节点之间的路径。
- 路径长:两个节点之间路径所包含分支的和。
- 深度:根节点的深度为0,其子节点的深度为1,往下逐一递推。
- 子节点数:和普通的树不同,二叉树从根节点出发,每个节点最多只能有两个子节点。
- 满二叉树:除了叶子结点,每个节点都有两个子节点。
哈夫曼树是一种最优的二叉树,它的带权路径最短。那么怎么算一个二叉树的权值呢?我们从根节点开始递推,根节点的权值为0,随着深度的递增,权值也递增,同一深度的节点权值是相同的,每个节点都有它的数据值,每个节点的数据值乘权值累加即可得到树的权值,哈夫曼树是带权路径最短的树。如图:
权值1=73+53+32+11=43
权值2=13+33+52+71=29
比较这两棵树的权值我们可以发现:要使带权路径最短,较大的数应该靠上放,较小的靠下放。第二张图才是带权路径最短的二叉树,即最优二叉树(哈夫曼树)。
如何生成一棵哈夫曼树
- 把需要用来构建哈夫曼树的数组进行排序。
- 取最小的两个数作为叶子结点,生成双亲节点,双亲节点的值为二者的值之和。
- 从数组中删除这两个节点,添加生成的双亲节点入数组并重新排序。
- 重复以上操作直至数组只剩下一个新生成的节点,它就是哈夫曼树的根节点。
哈夫曼编码
哈夫曼树是最优的二叉树,带权路径最短,这意味着哈夫曼树上数值较大的节点更加靠近根节点。信号的传输是以字节为单位的,ASCII码表示的255个字符分别可以用一个字节来表示,一个字符的传输需要用到一个字节(8bit),如果在一段报文中我们只用到了一部分字符,并且不同字符出现的频率还是不同的,我们能否用一些特殊的方法来对其加工从而压缩需要传输的总字节数呢?我们试着去缩短那些出现频率高的字符的编码表示,联系我们学的哈夫曼树,我们做这样的假设:从根节点出发,向左记“0”,向右记“1”,在哈夫曼树中,根节点是不带数据的,它的左子节点可以用“0”来编码,左子节点的左子节点用“00”表示,左子节点的右子节点用“01”表示,根节点的右子节点用“1”表示…以此类推,我们可以为树上所有的节点分配一个编码,编码是不重复的,且靠近根节点的那些出现频次较高的节点对应的编码较短。这样我们每个字符对应的编码表示都会小于等于8位,这极大压缩了需要传输的数据大小。
哈夫曼树的实现
1、节点类:
这是一种支持泛型的节点类,我们定义了一些方法来操作节点的数据。
public class Node<T> implements Comparable<Node<T>>{
private T data;
private int weight;
private Node<T> left;
private Node<T> right;
public Node(T data,int weight)
{
this.data=data;
this.weight=weight;
}
/**
* 获取节点数据
*/
public String toString()
{
return "data:"+data+" "+"weight:"+weight;
}
/**
* 节点权值比较方法
* @param o
* @return
*/
public int compareTo(Node<T> o) {
if(this.weight>o.weight)
return 1;
else if(this.weight<o.weight)
return -1;
return 0;
}
public void setData(T data)
{
this.data=data;
}
public void setWeight(int weight)
{
this.weight=weight;
}
public T getData()
{
return data;
}
public int getWeight()
{
return weight;
}
public void setLeft(Node<T> node)
{
this.left=node;
}
public void setRight(