【关于赫夫曼编码java实现】
Author: Stephen. Bo
在开始赫夫曼编码的java实现之前先来说下我现在对于java中class的理解吧~
在做完操作系统的Assignment1 的作业之后我对于java语言又有了一个全新的认识,相信大家在完成CSE108 assignment1时都看了《深入理解操作系统》那本书吧,那本书中在构建缓冲区时用到了typedef struct{ }这个东西,这个东西其实就是一个结构体,而java中的class其实就是C语言的结构体,只不过java中将C语言中对于结构体内部元素的寻找指针操作都在java编译器底层完成了,比如我们构建class a{int a; int b;} q其实就相当于构建C语言中的typedefstruct{int a;int b} a; 而java中对于int a 与 int b的调用使用了建立对象的方法,也就是a a = new a(); a.a调用了int a; a.b调用了int b。而C语言中对于int a 与 int b的调用使用了建立结构体变量的方法,也就是a a;定义了一个结构,然后a->a;使得结构体变量a指向了结构体中的int a; a->b;使得结构体变量a指向了结构体中的int b;所以我现在认为java其实就是C语言的延伸版,所有java可以实现的面向对象,用C语言的结构体都可以实现,只不过要比java多一个对于指针的操作,所以java是一个比较健壮的语言,因为它不会和出现空指针溢出,只会出现空指针exception 报错。而c语言则很容易出现指针溢出的错误。
说这些是因为赫夫曼编码中需要用到完全二叉树的建立,而完全二叉树其实就是用结构体来建立的,java中用到的是class对象来建立。
现在开始介绍一下我对于赫夫曼编码的java实现的理解吧。
1. 先说一下赫夫曼编码的思想吧:
(如果我说的不是很清楚大家可以参考一下《算法导论》的赫夫曼编码那一节)
赫夫曼编码就是构建一颗完全二叉树来实现编码的,为什么要构建完全二叉树》其他树不可以么,是因为赫夫曼编码在编码时,总是要有左孩子和右孩子的,因为左孩子要编码为0,右孩子要编码为1。其次赫夫曼编码是将出现频率低的字符用短的二进制码编码,而频率高的字符则用长的二进制码编码,怎么实现这一点呢,一个完全二叉树从根节点开始到叶节点,距离根节点近的节点它的编码就越少,这就要用到一个优先队列来实现对于完全二叉树的建立,优先队列中的元素是放入树的节点,然后优先队列按照节点中字符出现频率的大小进行从大到小排序,从叶节点开始放入队列,每次取队列最头的两个节点进行合并,建立父母节点,再将建立好的父母节点放入优先队列,一次循环进行,一共循环叶节点个数减一次,为什么是叶节点个数减一次,大家随便写几个字符画一个赫夫曼编码的的完全二叉树就明白了~ 编码时从叶节点开始,如果为左孩子编码0,右孩子编码1,一直编码到根节点,遍历所有叶节点即可。解码时从树的根节点开始往下进行,如果是0转向左孩子,是1转向右孩子,一直搜索到树的叶节点,最后输出叶节点的字符就可以了。
2. 完全二叉树的建立:
怎么建立一个完全二叉树,我们先画一个完全二叉树,会发现完全二叉树中的每个节点需要连接一个parent节点,一个左孩子节点,一个右孩子节点。而赫夫曼编码中的每个节点中又包含2个数据,一个是字符,一个是该字符出现的频率。那么我们节点的结构体也就是节点的类中就会包含5个数据,一个字符串型变量记录节点中字符,一个int型变量记录节点中字符出现的频率,三个指针也就是三个新节点对象各为该节点的parent节点,左孩子节点与右孩子节点。其次在定义树的节点的方法就行了,首先的五个方法是给这五个数据赋值的方法,其次是得到这五个数据的方法,其次赫夫曼编码需要用到判定是否为左孩子节点,是否为右孩子节点,是否为叶节点等方法,根据自己程序的需要建立节点的操作方法就行了。最后需要定义一个树的对象,树的对象中不用包含很对数据,就包含一个根节点的地址就行了,这样我们构建好一个完全二叉树以后就可以通过该树的根节点遍历整棵树了。遍历树的方法有两种,一种是从叶节点遍历到根节点,一种是从根节点遍历到叶节点,赫夫曼编码在编码时是从叶节点开始遍历的,而解码时是从根节点开始遍历的,为什么这么做呢,大家写程序的时候就会发现了。
在建立赫夫曼编码的完全二叉树时,需要先用一个方法返回map类型来统计每个字符出现的频率,然后根据频率先将所有叶节点放入一个list中记录叶节点,再将所有叶节点放入一个按字符出现频率从小到大排序的优先队列中建立整棵树就好了。建立完整棵树赫夫曼编码的第一步就完成了。
3. 得到编码本codebook
怎么得到编码本呢,这就要从已经建立好树的叶节点开始遍历整棵树了。那么你可能会问我该怎么找到叶节点,这就是为什么我们在建立完全二叉树时要用一个list来记录叶节点了,一次取出list中的叶节点,然后建立一个String code来记录字符的二进制编码,如果叶节点为其父母节点的左孩子,就在code前放一个0,若为右孩子,就在code前放一个1,最后一直遍历到树的根,一个字符所对应的二进制编码就完成了,然后在完成list中的所有叶节点的二进制编码就行了,最后将所有完成好的字符与字符所对应的二进制编码存入一个map中返回此map就是一个codebook了。
4. 编码
有了codebook编码就很简单了,检索所有输入字符,然后在map的codebook中寻找对应的编码输出就ok了
5. 解码
解码与编码不同,是从根节点遍历整棵树的,先将整个二进制编码输入到解码方法中,然后根据索引一次向后检索,若从根节点开始编码为0则转向左孩子,编码为1则转向右孩子,一直深度优先搜索到叶节点,在输出叶节点对应的字符就是一个编码的字符了,然后循环从根节点用此方法遍历整棵树,直到所有二进制编码都已检所完成,那么解码就完成了,最后返回记录解码字符的字符串String a;就是解码好的字符串
就不写代码了,如果在代码上有问题推荐大家一个网站可以去看看:
http://justsee.iteye.com/blog/1106693
希望大家喜欢本篇文章!~ 本文仅供分享与学习使用~ 不喜勿喷。。。
与君共勉!~