哈夫曼编码及压缩实现

哈夫曼编码及压缩实现

说点闲话

这个是大概两三个星期前做完了,一直想着要写篇博客,却迟迟没有动手。总是一件事没做完,又来了一堆事。写哈夫曼,算是体会到跟以前写代码不同的感觉。写代码一开始,做些简单的,只是为了掌握技术,完成作业,包括在做通信时,也没有十分专心,没有当成是自己的事来做。哈夫曼是期末开始的,中间放了好久,却是一直惦记着的,大概是觉得哈夫曼这三个字比较有感觉,也不想有始无终。

哈夫曼,在我之前有无数人写过,在我之后也必将还有无数人来写。至于实现它因此变得无意义还是有价值,就看个人的想法了。而我也只能从无意义中找到价值,如同每次经历新的事物,那个过程是难忘的。

一件一件事做完,代价是生命的流逝,你不禁会思考到底留下了什么,所以我在这里写下这篇博客,不管做的怎么样,都算是一个句号。

闲话说得差不多了,就开始讲讲哈夫曼吧。

来源

哈夫曼在MIT读书的时候,导师的期末作业是:寻找最有效的二进制编码,由于不能证明那个方法是最有效的,哈夫曼就另寻思路,使用自底向上的方法构建二叉树,也就是现在耳熟能详的哈夫曼编码。

哈夫曼编码是什么?

从它的来源就可以看出,它是一种二进制编码方式。因为计算机只能理解0,1.所以要将我们要传递的信息进行编码,将它转换成二进制。至于是怎么转换的,得看看通信原理。

那为什么它是最有效的?它的实现过程是从节点集合中选权值最小的两个合并成一个,在加入到节点集合中,再重复上述过程,直到只剩下一个节点。因为每次都选的最小权值的,所以当然带权路径长度最小了。这是符合生活直觉的。

哈夫曼编码如何实现?

Node类

首先定义节点Node类,将每个节点看做对象,放入队列中。

Node结构如下:

《哈夫曼编码及压缩实现》

public class Node implements Cloneable {

private Object data;
private int weight;
private Node left;
private Node right;
private StringBuffer code = new StringBuffer();//保存节点的编码
private int x, y;// 座标,画图时使用

将元素封装成节点,是为了方便存取,调用,这里就体现了OO的思想。

HFMtree类

《哈夫曼编码及压缩实现》

定义的属性有:

/**
 * 
 * @author yxm
 *实现编码压缩的类
 */
public class HFMTree extends JFrame {
ArrayList<Node> arrayNode = new ArrayList<Node>();// 节点的队列
String str = "asfsfwsdcasefwsdcsdaedas";// 源字符串
int[] charArray = new int[255];// 权值
HashMap<Character, String> map = new HashMap<Character, String>();// 存储叶子节点及其编码
HashMap<String, Character> unmap = new HashMap<String, Character>();// 根据map的value找出key,用于解码
String[] codeStr;// 字符串编码后的,str长度确定后在确定数组长度
String fileString = "";// 文件中读到的字符串
String copystr = "";// 解码后得字符串

实现编码的思路是

  • 根据源字符串,统计字符出现次数,得到权值。根据权值生成节点,加入到节点序列中。

/**
 * 根据字符出现次数,得到权值
 * 
 * @param str
 * @param charArray
 */
public void countWeight() {
    for (int i = 0; i < str.length(); i++) {
        char c = str.charAt(i);
        System.out.println("c=" + c);
        charArray[c]++;// 根据字符ASC码,将对应字符权值增加
    }
}

/**
 * 根据权值生成节点,并加入到节点序列中
 * 
 * @param charArray
 * @param arrayNode
 */
public void add() {
    for (int i = 0; i < charArray.length; i++) {
        if (charArray[i] != 0) {
            int weight = charArray[i];
            char ch = (char) i;// 字符
            Node newnode = new Node(ch, weight);// 生成新节点
            arrayNode.add(newnode);
            System.out.println(ch + "      " + weight);
        }
    }
}

}
  • 排序,合并,生成根节点。

/**
 * 排序
 * 
 * @param arrayNode
 * @return
 */
public ArrayList<Node> sort() {
    for (int i = 0; i < arrayNode.size(); i++) {
        for (int j = 0; j < arrayNode.size() - i - 1; j++) {
            if (arrayNode.get(j).getWeight() > arrayNode.get(j + 1)
                    .getWeight()) {
                Node temp = arrayNode.get(j + 1);
                arrayNode.set(j + 1, arrayNode.get(j));
                arrayNode.set(j, temp);
            }
        }
    }
    return arrayNode;
}

/**
 * 合并节点,生成新节点,直到只有一个节点
 */
public ArrayList<Node> merge() {
    for (int i = 0; arrayNode.size() > 1; i++) {
        // 排序
        arrayNode = sort();
        // 合并
        String temp = arrayNode.get(0).getData().toString();
        temp = temp + arrayNode.get(1).getData().toString();
        int weight = arrayNode.get(0).getWeight()
                + arrayNode.get(1).getWeight();
        Node newnode = new Node(temp, weight);
        // 设置左右节点
        newnode.setLeft(arrayNode.get(0));
        newnode.setRight(arrayNode.get(1));
        System.out.println(arrayNode.get(0).getData() + "        "
                + arrayNode.get(1).getData());
        // 删除节点
        arrayNode.remove(0);
        arrayNode.remove(0);
        // 添加新生成的节点
        arrayNode.add(newnode);
    }
    System.out.println("arrayNode.size=" + arrayNode.size());
    System.out.println("arrayNode.size2=" + arrayNode.size());
    return arrayNode;
  • 遍历,编码,压缩,解压。

/**
 * 前序遍历,进行哈弗曼编码
 * 
 * @param args
 */
public void PreOrderTraverse(Node root) {
    // 防止空指针异常,终止条件
    if (root != null) {
        System.out.println(root.getData().toString());
        PreOrderTraverse(root.getLeft());
        PreOrderTraverse(root.getRight());
    }
}

/**
 * 进行编码
 * 
 * @param args
 * @return
 */
public HashMap<Character, String> hfmCode(Node root) {
    if (root != null) {
        if (root.getLeft() != null) {
            StringBuffer stl = new StringBuffer();// 重定义一个stl,防止改变root的code
            stl.append(root.getCode());// 保存父节点的编码
            root.getLeft().setCode(stl.append(0));
        }
        if (root.getRight() != null) {
            StringBuffer str = new StringBuffer();
            str.append(root.getCode());
            root.getRight().setCode(str.append(1));
        }
        hfmCode(root.getLeft());
        hfmCode(root.getRight());
        if (root.getLeft() == null && root.getRight() == null) {
            // 得到叶子节点的编码,用于压缩
            System.out.println(root.getData().toString() + "        "
                    + root.getCode().toString());
            int[] leafArray = new int[255];
            char c = root.getData().toString().charAt(0);// 将单个字符的字符串转成char型
            System.out.println("c=" + c);
            // 将字符及其编码存入hashmap
            map.put(c, root.getCode().toString());
            unmap.put(root.getCode().toString(), c);
            System.out.println("map.get(c)=" + map.get(c));
        }
    }
    System.out.println("map=           " + map);
    return map;
}

/**
 * 进行哈弗曼压缩
 * 
 * @param args
 */
public void compress() {
    codeStr = new String[str.length()];
    for (int i = 0; i < str.length(); i++) {
        char s = str.charAt(i);
        // System.out.println("s=" + s);
        codeStr[i] = map.get(s);
        System.out.print(codeStr[i]);
    }
    // System.out.println(codeStr.length+"长度");
}

还有许多扩展功能,比如画出哈夫曼树,从文件中读取和输出,并进行优化,到最后能做出一个真正的压缩软件。

在做的过程中,遇到许多问题,解决的心情是急切的,这个过程也让你相信任何问题都是事出有因,只要去思考和尝试,都是可以解决的。解决问题的方法是各种各样的,但通常你最后正真选用的都是最简单巧妙的思路。解决问题的过程也是代码的一个魅力所在。具体的问题就不多说了,我也想不起来了。正真让我记住的是,一件事,分步骤,一步一步去做,坚持不懈,总是会有一个结果。

所以任何技术问题都不是问题,敲代码只是表象,真正在做的是,通过代码,切入这个世界,学会做人做事。

还有就是,文章要及时写,不然就大打折扣了。每个技术点去扩展,都可以无限纵深,也不能因为现在水平很有限就不去做,现在做的就是当下状况的体现。

点赞