哈夫曼编码Java实现
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
public class Huffman {
private List<Double> nums;
private List<Double> numsMo;
private List<Tree> trees;
private String temp;
public Huffman() {
nums = new ArrayList<Double>();
numsMo = new ArrayList<Double>();
trees = new ArrayList<Tree>();
temp="";
}
public void addNums() {// 给定一组数
System.out.println("请输入一组数,中间用空格分隔:");
Scanner sca = new Scanner(System.in);
String str = sca.nextLine();
String[] strs = str.split(" ");
for (int i = 0; i < strs.length; i++) {
nums.add(Double.parseDouble(strs[i]));
numsMo.add(Double.parseDouble(strs[i]));
}
}
public void compareNum(List<Double> nums,List<Tree> trees) {// 递归算法
double[] min = new double[2];
if(nums.size()>1){
min = minTwo(nums);
Tree t = new Tree(min[0],min[1],min[0]+min[1]);
nums.remove(Double.valueOf(min[0]));
nums.remove(Double.valueOf(min[1]));
nums.add(min[0]+min[1]);
trees.add(t);
compareNum(nums,trees);
}
}
public void print(double num) {// 递归打印编码
for(Tree t : trees){
if(num == t.getRchild()){
temp = 1+temp;
print(t.getParents());
break;
}else if(num == t.getLchild()){
temp = 0+temp;
print(t.getParents());
break;
}
}
}
public void write(double d){
temp = "";
System.out.print(d+" : ");
print(d);
System.out.print(temp);
System.out.println(" 码长:"+temp.length());
}
public double[] minTwo(List<Double> nums) {// 在一组数中选则最小的两个,按递增排序返回
Double temp = 0.0;
for (int j = 0; j < 2; j++) {
for (int i = 1; i < nums.size(); i++) {
if (nums.get(i - 1) < nums.get(i)) {
temp = nums.get(i);
nums.set(i, nums.get(i - 1));
nums.set(i - 1, temp);
}
}
}
double[] n = {nums.get(nums.size()-1),nums.get(nums.size()-2)};
return n;
}
public void start(){
addNums();
compareNum(nums,trees);
while(numsMo.size()>1){
double[] mins = minTwo(numsMo);
if(mins[0]!=mins[1]){
numsMo.remove(Double.valueOf(mins[0]));
write(mins[0]);
}
}
if(!numsMo.isEmpty()){
write(numsMo.get(0));
}
}
public static void main(String[] args){
new Huffman().start();
}
}
public class Tree {
double lChild, rChild, parent;
public Tree (double lChild, double rChild, double parent) {
this.lChild = lChild;
this.rChild = rChild;
this.parent = parent;
}
public double getLchild() {
return lChild;
}
public void setLchild(double lChild) {
this.lChild = lChild;
}
public double getRchild() {
return rChild;
}
public void setRchild(double rChild) {
this.rChild = rChild;
}
public double getParents() {
return parent;
}
public void setParents(double root) {
this.parent = root;
}
}
哈夫曼编码简介
哈弗曼编码几乎是所有压缩算法的基础,其实这个算法并不复杂,简单的理解就是,如何用更短的bit来编码数据 。 我们知道普通的编码都是定长的,比如常用的ASCII编码,每个字符都是8个bit:
字符 编码 A 00101001 B 00101010 C 00101011 … …
这样,计算机就能很方便的把由0和1组成的数据流解析成原始信息,但我们知道,在很多情况下,数据文件中的字符出现的概率是不均匀的,比如在一篇英语文章中,字母“E”出现的频率最高,“Z”最低,如果我们使用不定长的bit编码,频率高的字母用比较短的编码表示,频率低的字母用长的编码表示,岂不是可以大大缩小文件的空间吗? 但这就要求编码要符合“前缀编码”的要求,即较短的编码不能是任何较长的编码的前缀,这样解析的时候才不会混淆,比如下面的编码方法就符合前缀原则:
字符 编码 A 0 B 10 C 110 D 1110 E 11110 … …
根据这个码表,下面一段数据就可以唯一解析成原始信息了: 1110010101110110111100010 – DABBDCEAAB
要生成这种编码,最方便的就是用二叉树,考察一下下面这个树 把要编码的字符放在二叉树的叶子上,所有的左节点是0,右节点是1,从根浏览到叶子上,因为字符只能出现在树叶上,任何一个字符的路径都不会是另一字符路径的前缀路径,符合前缀原则编码就可以得到
现在我们可以开始考虑压缩的问题,如果有一篇只包含这五个字符的文章,而这几个字符的出现的次数如下: A: 6次 B : 15次 C: 2次 D : 9次 E: 1次 用过用定长的编码,每个字符3bit,这篇文章总长度为: 3*6 + 3*15 + 3*2 + 3*9 + 3*1 = 99 而用上面用二叉树生成的编码,总长度为: 2*6 + 3*15 + 2*2 + 2*9 + 2*1 = 80
显然,这颗树还可以进一步优化,使得编码更短,比如下面的编码 生成的数据长度为: 3*6 + 1*15 + 4*2 + 2*9 + 4*1 = 63
可以看出,构造更优的二叉树,原则就是权重越大的叶子,距离根应该越近,而我们的终级目标是生成“最优”的二叉树,最优二叉树必须符合下面两个条件:
所有上层节点都大于等于下层节点。 某节点,设其较大的子节点为m,较小的子节点为n,m下的任一层的所有节点都应大于等于n下的该层的所有节点。
上面这个例子是比较简单的,实际的文件中,一个字节有256种可能的取值,所以二叉树的叶子节点多达256个,最终的树形可能非常复杂,但有一种非常精巧的算法可以快速地建起一棵最优二叉树,这种算法由D.Huffman(戴?哈夫曼)提出,下面我们先来介绍哈弗曼算法的步骤,然后再来证明通过这么简单的步骤得出的树形确实是一棵最优二叉树。 哈夫曼算法的步骤是这样的:
从各个节点中找出最小的两个节点,给它们建一个父节点,值为这两个节点之和。 然后从节点序列中去除这两个节点,加入它们的父节点到序列中。 重复上面两个步骤,直到节点序列中只剩下唯一一个节点。这时一棵最优二叉树就已经建成了,它的根就是剩下的这个节点。 比如上面的例子,哈弗曼树建立的过程如下:
1) 列出原始的节点数据: 2) 将最小的两个节点C和E结合起来: 3) 再将新的节点和A组合起来 4) 再将D节点加入 5) 如此循环,最终得到一个最优二叉树 生成的数据文件长度为: 3*6 + 1*15 + 4*2 + 2*9 + 4*1 = 63
下面我们用逆推法来证明对于各种不同的节点序列,用哈弗曼算法建立起来的树总是一棵最优二叉树:
当这个过程中的节点序列只有两个节点时(比如前例中的15和18),肯定是一棵最优二叉树,一个编码为0,另一个编码为1,无法再进一步优化。 然后往前步进,节点序列中不断地减少一个节点,增加两个节点,在步进过程中将始终保持是一棵最优二叉树,这是因为:
按照哈弗曼树的建立过程,新增的两个节点是当前节点序列中最小的两个,其他的任何两个节点的父节点都大于(或等于)这两个节点的父节点,只要前一步是最优二叉树,其他的任何两个节点的父节点就一定都处在它们的父节点的上层或同层,所以这两个节点一定处在当前二叉树的最低一层。 这两个新增的节点是最小的,所以无法和其他上层节点对换。符合我们前面说的最优二叉树的第一个条件。 只要前一步是最优二叉树,由于这两个新增的节点是最小的,即使同层有其他节点,也无法和同层其他节点重新结合,产生比它们的父节点更小的上层节点来和同层的其他节点对换。它们的父节点小于其他节点的父节点,它们又小于其他所有节点,只要前一步符合最优二叉树的第二个条件,到这一步仍将符合。
这样一步步逆推下去,在这个过程中哈弗曼树每一步都始终保持着是一棵最优二叉树。
〖 作者:海鸟之恋 〗〖 大小:2k 〗〖 发布日期:2010-04-05 〗〖 浏览:0 〗
如何进行哈夫曼编码:
哈夫曼树的建立过程(FLASH动画)
/**
* @(#) 岑村高科
*/
package cn.javayy.struct;
/**
* 定义了一种接口,要进行编码的最小单元类必需实现些接口
* @author boss
*
* create on : 下午10:56:59 2009-5-19
*/
public interface Combinable<T> extends Comparable<T> {
T combinate(T a,T b);
}
=====================================================
/**
* @(#) 岑村高科
*/
package cn.javayy.struct;
/**
* the huffman tree Class
* <p> 哈夫曼树,包括当前节点数据信息,左节点,右节点信息。
* @author boss
*
* create on : 下午10:16:23 2009-5-19
*/
public class HuffmanTree<T extends Combinable<T>> implements Comparable<HuffmanTree<T>>{
/**the root of huffman tree*/
private T root;
/**the left node of huffman tree*/
private HuffmanTree<T> left;
/**the right node of huffman tree*/
private HuffmanTree<T> right;
/**哈夫曼编码字符串,如:0000110*/
private String huffmanCodeString = "";
/**是否对最终生成的哈夫曼进行过编码操作*/
private static boolean isSettedHuffmanCoderString = false;
public T getRoot() {
return root;
}
public void setRoot(T root) {
this.root = root;
}
public HuffmanTree<T> getLeft() {
return left;
}
public void setLeft(HuffmanTree<T> left) {
this.left = left;
}
public HuffmanTree<T> getRight() {
return right;
}
public void setRight(HuffmanTree<T> right) {
this.right = right;
}
/**
* 重写此方法用于递归合并节点后进行排序操作
*/
@Override
public int compareTo(HuffmanTree<T> o) {
// TODO Auto-generated method stub
return o.getRoot().compareTo(this.getRoot());
}
@Override
public String toString(){
return "the root:" + this.getRoot()
+ "\r\nthe left:" + this.getLeft()
+ "\r\nthe right:" + this.getRight();
}
/**
* 对最终生成的树进行哈夫曼编码,使每个叶子节点生成01的路径编码
*/
private void setHuffmanCoderString(){
HuffmanTree<T> left = this.getLeft();
//如果有左节点则在路径中追加"0"
if(left != null){
left.huffmanCodeString = this.huffmanCodeString + "0";
left.setHuffmanCoderString();//递归编码
}
HuffmanTree<T> right = this.getRight();
//如果有右节点则在路径中追加"1"
if(right != null){
right.huffmanCodeString = this.huffmanCodeString + "1";
right.setHuffmanCoderString();//递归编码
}
}
public void printHuffmanCoderString(){
//打印最终生成树的哈夫曼编码前要进行编码操作,
//且此操作只执行一次,所以用全局标识变量判断
if(!HuffmanTree.isSettedHuffmanCoderString){
this.setHuffmanCoderString();
HuffmanTree.isSettedHuffmanCoderString = true;//标识已执行过编码
}
//如果是叶子节点(即要编码的单元),则打印出编码信息,如果是非叶子结点(中间临时生成的节点),则不打印
if(this.left == null && this.right == null)
System.out.println("the " + this.getRoot() + " huffmanCoder:" +
this.huffmanCodeString);
if(this.left != null)
this.left.printHuffmanCoderString();//递归打印
if(this.right != null)
this.right.printHuffmanCoderString();//递归打印
}
}
==================================================
/**
* @(#) 岑村高科
*/
package cn.javayy.struct;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
/**
* 用类用于生成一个哈夫曼树
* @author boss
*
* create on : 下午10:51:39 2009-5-19
*/
public class HuffmanTreeFactory<T extends Combinable<T>> {
/**初始时一个list列表当作要编码的单元类的容器*/
private List<HuffmanTree<T>> HuffmanTreeCollection;
// public HuffmanTreeFactory(){}
/**
* @param unitClasses 待编码的单元类集合
*/
public HuffmanTreeFactory(List<T> unitClasses){
if(unitClasses == null || unitClasses.size() == 0)
throw new IllegalArgumentException(
"the unit classes collection can't be empty");
HuffmanTreeCollection = new LinkedList<HuffmanTree<T>>();
//初始化哈夫曼集合容器
for(T unitClass : unitClasses){
HuffmanTree<T> huffmanTree = new HuffmanTree<T>();
huffmanTree.setRoot(unitClass);
huffmanTree.setLeft(null);
huffmanTree.setLeft(null);
HuffmanTreeCollection.add(huffmanTree);
}
Collections.sort(HuffmanTreeCollection);
}
/**
* 将待编码的哈夫曼集合处理成只含有一个元素的集合,则这最后一个元素
* 即为最终生成的哈夫曼树
*/
private void generateHuffmanTree(){
while(true){
if(HuffmanTreeCollection == null || HuffmanTreeCollection.size() <= 1)
break ;
//处理之前一定要重新排序,这是哈夫曼编码的关键原理
Collections.sort(HuffmanTreeCollection);
HuffmanTree<T> huffmanTreeOne = HuffmanTreeCollection.remove(0);
HuffmanTree<T> huffmanTreeTwo = HuffmanTreeCollection.remove(0);
HuffmanTree<T> huffmanTreeNew = new HuffmanTree<T>();
//将集合中前面两个元素合并成一个元素插到集合中去
//并将第一个元素和第二个元素分别作为新元素的左,右节点
huffmanTreeNew.setRoot(huffmanTreeOne.getRoot().
combinate(huffmanTreeOne.getRoot(), huffmanTreeTwo.getRoot()));
huffmanTreeNew.setLeft(huffmanTreeOne);
huffmanTreeNew.setRight(huffmanTreeTwo);
HuffmanTreeCollection.add(huffmanTreeNew);
}
}
/**
*
* @return 生成最终的哈夫曼树
*/
public HuffmanTree<T> getHuffmanTree(){
generateHuffmanTree();
return this.HuffmanTreeCollection.get(0);
}
}
================================================
/**
* @(#) 岑村高科
*/
package cn.javayy.struct;
import java.io.Serializable;
/**
* 自定义一个用于测试的单元类
* @author boss
*
* create on : 下午09:53:07 2009-5-19
*/
public class UnitClass implements Serializable,Combinable<UnitClass>{
/**
* serialVersionUID
*/
private static final long serialVersionUID = -4109190579335512743L;
/**出现概率数据*/
private int rate;
public UnitClass(int rate){
this.rate = rate;
}
public int getRate() {
return rate;
}
public void setRate(int rate) {
this.rate = rate;
}
/**
* implements thid compartTo() in order to sort the
* collection stored from unitclass
*/
@Override
public int compareTo(UnitClass o) {
return o.getRate() > this.rate ? 1: o.getRate() < this.rate ? -1 : 0;
}
@Override
public String toString(){
return "the rate is:" + rate;
}
/**
* 重写此方法用于哈夫曼编码时可以合并两个分支点信息
*/
@Override
public UnitClass combinate(UnitClass a, UnitClass b) {
if(a == null || b == null)
return null;
return new UnitClass(a.getRate() + b.getRate());
}
}
========================================
/**
* @(#) 岑村高科
*/
package cn.javayy.struct;
import java.util.ArrayList;
import java.util.List;
/**
* @author boss
*
* create on : 下午10:03:12 2009-5-19
*/
public class Main {
public static void main(String[] args){
List<UnitClass> l = new ArrayList<UnitClass>();
l.add(new UnitClass(2));
l.add(new UnitClass(4));
l.add(new UnitClass(10));
l.add(new UnitClass(7));
l.add(new UnitClass(20));
HuffmanTreeFactory<UnitClass> factory = new HuffmanTreeFactory<UnitClass>(l);
// System.out.println(factory.getHuffmanTree());
factory.getHuffmanTree().printHuffmanCoderString();
}
}
=========================================
打印结果:
the the rate is:20 huffmanCoder:0
the the rate is:10 huffmanCoder:10
the the rate is:2 huffmanCoder:1100
the the rate is:4 huffmanCoder:1101
the the rate is:7 huffmanCoder:111