1.基本概念
-
霍夫曼树又称最优二叉树,是一种带权路径长度最短的二叉树。所谓树的带权路径长度,就是树中所有的叶结点的权值乘上其到根结点的路径长度(若根结点为0层,叶结点到根结点的路径长度为叶结点的层数)。
-
树的带权路径长度:设一棵二叉树有 n 个叶子结点,每个叶子结点拥有一个权值W 1 ,W 2 , ...... W n ,从根结点到每个叶子结点的路径长度分别为 L1 , L2......Ln ,那么树的带权路径长度为从树根到每一结点的路径长度之和,记为WPL=(W1*L1+W2*L2+W3*L3+...+Wn*Ln)。可以证明霍夫曼树的WPL是最小的。
2.构造霍夫曼树的方法
对于已知的一组叶子的权值W 1 ,W 2...... ,W n
①首先把 n 个叶子结点看做 n 棵树(仅有一个结点的二叉树),把它们看做一个森林。
②在森林中把权值最小和次小的两棵树合并成一棵树,该树根结点的权值是两棵子树权值之和。这时森林中还有 n-1 棵树。
③重复第②步直到森林中只有一棵为止。此树就是哈夫曼树。
n个叶子结点的哈夫曼树共有2n-1个结点。
3.霍夫曼编码
霍夫曼编码(Huffman Coding)是一种编码方式,是一种用于无损数据压缩的熵编码(权编码)算法。1952年,David A. Huffman在麻省理工攻读博士时所发明的,并发表于《一种构建极小多余编码的方法》(A Method for the Construction of Minimum-Redundancy Codes)一文。
在通信及数据传输中多采用二进制编码。为了使电文尽可能的缩短,可以对电文中每个字符出现的次数进行统计。设法让出现次数多的字符的二进制码短些,而让那些很少出现的字符的二进制码长一些。假设有一段电文,其中A,B,C,D出现的频率为0.4, 0.3, 0.2, 0.1。则得到的哈夫曼树和二进制前缀编码如图所示。在树中令所有左分支取编码为 0 ,令所有右分支取编码为1。将从根结点起到某个叶子结点路径上的各左、右分支的编码顺序排列,就得这个叶子结点所代表的字符的二进制编码。
这些编码拼成的电文不会混淆,因为每个字符的编码均不是其他编码的前缀,这种编码称做前缀编码。
4.霍夫曼编码实现
下面是java实现的霍弗曼编码,包括霍夫曼树的构造,编码和解码。
- package com.iteye.cake513.code;
- import java.util.*;
- /**
- * @ClassName: Huffman
- * @Description: TODO(Huffman编码)
- * @author liujie
- * @date 2011-10-4 下午09:49:29
- *
- */
- class HuffmanNode implements Comparable<HuffmanNode> {
- private char letter;
- private int count;//字符letter出现的频率,即权重
- private HuffmanNode left;
- private HuffmanNode right;
- public HuffmanNode(char letter, int count) {
- this.letter = letter;
- this.left = null;
- this.right = null;
- this.count = count;
- }
- public HuffmanNode(HuffmanNode left, HuffmanNode right) {
- this.letter = '?';
- this.left = left;
- this.right = right;
- this.count = left.count + right.count;
- }
- //为了进行排序
- public int compareTo(HuffmanNode o) {
- return count - o.count;
- }
- public boolean isLeaf() {
- return(this.left == null && this.right == null);
- }
- public HuffmanNode getLeft() {
- return left;
- }
- public HuffmanNode getRight() {
- return right;
- }
- public char getLetter() {
- return letter;
- }
- }
- public class Huffman {
- private HuffmanNode root;
- private HashMap<Character, String> map;//用于存放字符到编码的映射
- @SuppressWarnings("unchecked")
- public Huffman() {
- char[] letters = "bekprs_&".toCharArray();
- int[] counts = {2,7,1,1,1,2,2,1};
- map = new HashMap<Character, String>();
- root = generateTree(letters, counts);
- generateCodes(root, "");
- for (int i = 0; i < letters.length; i++) {
- System.out.print(letters[i] + "(" + counts[i] +") ");
- }
- System.out.println("生成二进制编码如下:");
- Iterator itr = map.entrySet().iterator();
- while (itr.hasNext()) {
- Map.Entry entry = (Map.Entry)itr.next();
- System.out.print(entry.getKey() + ":" + entry.getValue() +" ");
- }
- System.out.println();
- }
- //构造霍夫曼树
- private HuffmanNode generateTree(char[] letters, int[] counts) {
- List<HuffmanNode> list = new LinkedList<HuffmanNode>();
- for(int i = 0; i < letters.length; i++) {
- list.add(new HuffmanNode(letters[i], counts[i]));
- }
- while (true) {
- Collections.sort(list);
- HuffmanNode a = list.remove(0);
- if (list.isEmpty()) {
- return a;
- }
- HuffmanNode b = list.remove(0);
- list.add(new HuffmanNode(a, b));
- }
- }
- //生成编码,存放在HashMap中,key对应letter,value对应letter的二进制编码
- private void generateCodes(HuffmanNode root, String code) {
- if (root.isLeaf()) {
- map.put(root.getLetter(), code);
- } else {
- generateCodes(root.getLeft(), code + "0");
- generateCodes(root.getRight(), code + "1");
- }
- }
- //对给定电文进行编码,messag中的字符必须属于生成编码用到的字符集合letters
- private String encode(String message) {
- StringBuilder result = new StringBuilder();
- for (char c : message.toCharArray()) {
- result.append(map.get(c) + " ");
- }
- return result.toString();
- }
- //对编码进行解码
- public String decode(String encodeMessage) {
- StringBuilder result = new StringBuilder();
- HuffmanNode node = root;
- for (char c : encodeMessage.toCharArray()) {
- if (c == '0') {
- node = node.getLeft();
- } else if (c == '1') {
- node = node.getRight();
- }
- if (node.isLeaf()) {
- result.append(node.getLetter());
- node = root;
- }
- }
- return result.toString();
- }
- public static void main(String[] args) {
- Huffman hfm = new Huffman();
- String encode = hfm.encode("beekeepers_&_bees");
- System.out.println("对beekeepers_&_bees编码:" + encode);
- String decode = hfm.decode(encode);
- System.out.println("对上面进行解码:" + decode);
- }
- }