目前市面上的压缩软件,都是在美国数学家哈夫曼发明的哈夫曼编码的基础上进行延伸的(站在巨人的肩膀上)。也就是说,哈夫曼编码的出现,能把文件的大小进行压缩,压缩之后,还要能复原,就需要根据一张已有的编码表进行。本文仅先介绍哈夫曼树的基础知识及搭建,当然还会在接下来写有关哈夫曼编码、解码的文章。
一、哈夫曼压缩的原理
引入一个生活中的例子,在对一个班级的学生成绩划分等级的时候,小于60为E,[60,70)为D,[70,80)为C,[80,90)为B,[90,100]为A。如果对于一个学生的成绩,都从60开始进行等级判定,在数据量很大的时候,是不是会很费时间?因为按正常来说,学生成绩应集中分布在80左右,那对于这些数据还要进行小于60的判断,这个动作就多余了。可以根据历史数据,统计出哪一个区间段人数,从大到小赋予权值,人数最多的区间范围权值最大。
这样一来,在对学生成绩等级判定的时候,总体上就会减小很多工作量。其实,这也就是哈夫曼编码能够压缩的原理:根据字符出现的频率大小进行编码,出现频率越高的字符的编码越短,这样一来,字符出现频率越高的整体使用的编码长度越短,就达到了整体压缩的效果。
举个具体的例子:对于一个字符串:aabbbcccc,因为数据在计算机中是以二进制的方式存储的,a的二进制为1100001,b的二进制为1100010,c的二进制为1100011,这样上面那个字符串的二进制为:110000111000011100010110001011000101100011110001111000111100011,长度为63。使用哈夫曼编码,a对应的哈夫曼编码为:10,b:11,c:0,字符串的二进制为:10101111110000,长度为14,相较于原来的63位,压缩效率达到77%。
二、哈夫曼树
哈夫曼树首先是一棵二叉树,只不过对其中的节点中数据进行了频率的统计(权重),然后按照从小到大的顺序排列,之后按照规则:每次取当前所有节点中,权重最小的两个节点进行构建树,形成的父节点没有数据域,只有权值域,然后删除刚刚的最小的两个节点,再把新生成的父节点加入到节点队列中,并重新排序,再重复以上步骤,当节点队列中仅存在一个节点时,整个哈夫曼树就搭建完成了,这个节点就是整棵树的根节点。下面用代码来实现:
先定义一个节点类
//让节点继承Comparable,并重写其中的compareTo方法,就可以使用Collections中的sort方法给节点排序
class Node implements Comparable<Node>{
//便于测试,从其他类直接获取,设置属性为public
public int value;//权值
public Node left,right;//该节点的左右子节点
public Node(int value) {
this.value = value;
}
//从小到大对节点进行排序,如果要进行从大到小的排序,