哈夫曼编码
哈夫曼编码(Huffman Coding),又称霍夫曼编码,是一种编码方式,哈夫曼编码是可变字长编码(VLC)的一种。Huffman于1952年提出一种编码方法,该方法完全依据字符出现概率来构造异字头的平均长度最短的码字,有时称之为最佳编码,一般就叫做Huffman编码(有时也称为霍夫曼编码)。
哈夫曼编码步骤:
一、对给定的n个权值{W1,W2,W3,...,Wi,...,Wn}构成n棵二叉树的初始集合F= {T1,T2,T3,...,Ti,...,Tn},其中每棵二叉树Ti中只有一个权值为Wi的根结点,它的左右子树均为空。(为方便在计算机上实现算 法,一般还要求以Ti的权值Wi的升序排列。)
二、在F中选取两棵根结点权值最小的树作为新构造的二叉树的左右子树,新二叉树的根结点的权值为其左右子树的根结点的权值之和。
三、从F中删除这两棵树,并把这棵新的二叉树同样以升序排列加入到集合F中。
四、重复二和三两步,直到集合F中只有一棵二叉树为止。
比如字符串 abbccc;那么计算机中存储的应该是01100001(a)01100001(a)01100010 (b)01100010 (b)01100011 (c)01100011 (c)01100011 (c),一共占6个字节;
现在利用哈夫曼树将这个文件进行编码
将每个字出现的次数作为权值来构造哈夫曼树,上面的字符串中a出现了1次,b出现了2次,c 出现了2次,那这三个字符的权重分别为 1,2,3 那么我们得到的哈夫曼树如下图:

如上图,三个字符的哈夫曼编码为: a: 00 b: 01 c: 1
原来的编码:abbccc => 01100001 01100001 01100010 01100010 01100011 01100011 01100011
现在的哈夫曼编码:abbccc => 00 01 01 1 1 1
原来的6个字节现在变成了不到 2个字节了;我们只要把这个新的01串转成十进制整数,再把这个十进制的整数写入到压缩文件中即完成了压缩;
Java代码如下:
1 /** 2 * 哈夫曼树的节点 3 */ 4 public class Node implements Comparable<Node> { 5 6 private char c = 0; // 以16位为单元的字符 7 private int weight = 0; // 字符出现的次数 8 private Node left = null; 9 private Node right = null; 10 11 public Node() { 12 } 13 14 public Node(char c, int weight) { 15 this.c = c; 16 this.weight = weight; 17 } 18 19 public char getC() { 20 return c; 21 } 22 23 public void setC(char c) { 24 this.c = c; 25 } 26 27 public int getWeight() { 28 return weight; 29 } 30 31 public void setWeight(int weight) { 32 this.weight = weight; 33 } 34 35 public Node getLeft() { 36 return left; 37 } 38 39 public void setLeft(Node left) { 40 this.left = left; 41 } 42 43 public Node getRight() { 44 return right; 45 } 46 47 public void setRight(Node right) { 48 this.right = right; 49 } 50 51 @Override 52 public int compareTo(Node o) { 53 return this.weight - o.getWeight(); 54 } 55 56 }
1 public class HuffmanAlgorithm { 2 3 public EncodeResult encode(String str) { 4 List<Node> letterList = toList(str); 5 Node rootNode = createTree(letterList); 6 Map<Character, String> letterCode = getLetterCode(rootNode); 7 EncodeResult result = encode(letterCode, str); 8 return result; 9 } 10 11 /** 12 * 把一个字符串转化为节点列表 13 * 14 * @param letters 15 * @return 16 */ 17 private List<Node> toList(String letters) { 18 19 Map<Character, Integer> charMap = new HashMap<Character, Integer>(); 20 for (int i = 0; i < letters.length(); i++) { 21 Character character = letters.charAt(i); 22 if (!charMap.containsKey(character)) { 23 charMap.put(character, 1); 24 } else { 25 Integer oldValue = charMap.get(character); 26 charMap.put(character, oldValue + 1); 27 } 28 } 29 30 return charMap.entrySet().stream().map(entry -> { 31 return new Node(entry.getKey(), entry.getValue()); 32 }).collect(Collectors.toList()); 33 } 34 35 protected Node createTree(List<Node> letterList) { 36 int n = letterList.size(); 37 38 PriorityQueue<Node> queue = new PriorityQueue<Node>(n); 39 for (Node node : letterList) { 40 queue.add(node); 41 } 42 // 需要n-1步 43 for (int i = 1; i <= n - 1; i++) { 44 Node left = queue.poll(); 45 Node right = queue.poll(); 46 Node parent = new Node(); 47 48 parent.setWeight(left.getWeight() + right.getWeight()); 49 parent.setLeft(left); 50 parent.setRight(right); 51 queue.add(parent); 52 } 53 return queue.poll(); 54 } 55 56 /** 57 * 编码字符串。 58 * 59 * @param letterCode 60 * 字符/编码对集合(编码器: 如: a -> 01) 61 * @param letters 62 * 指定的需要编码的字符串。 63 * @return 编码结果 64 */ 65 private EncodeResult encode(Map<Character, String> letterCode, String letters) { 66 StringBuilder encode = new StringBuilder(); 67 for (int i = 0, length = letters.length(); i < length; i++) { 68 Character character = letters.charAt(i); 69 encode.append(letterCode.get(character)); 70 } 71 EncodeResult result = new EncodeResult(encode.toString(), letterCode); 72 return result; 73 } 74 75 /** 76 * 获得所有字符编码对 77 * 78 * @param rootNode哈夫曼树的根节点 79 * @return 所有字符编码对 80 */ 81 private Map<Character, String> getLetterCode(Node rootNode) { 82 Map<Character, String> letterCode = new HashMap<Character, String>(); 83 // 处理只有一个节点的情况 84 if (rootNode.getLeft() == null && rootNode.getRight() == null) { 85 letterCode.put(rootNode.getC(), "1"); 86 return letterCode; 87 } 88 89 getLetterCode(rootNode, "", letterCode); 90 return letterCode; 91 } 92 93 /** 94 * 先序遍历哈夫曼树,获得所有字符编码对。 95 * 96 * @param rooNode 97 * 哈夫曼树根结点 98 * @param suffix 99 * 编码前缀,也就是编码这个字符时,之前路径上的所有编码 100 * @param letterCode 101 * 用于保存字符编码结果 102 */ 103 private void getLetterCode(Node rooNode, String suffix, Map<Character, String> letterCode) { 104 if (rooNode != null) { 105 if (rooNode.getLeft() == null && rooNode.getRight() == null) { 106 letterCode.put(rooNode.getC(), suffix); // 到根节点了,把编码加上(字符->哈夫曼编码对应) 107 } 108 getLetterCode(rooNode.getLeft(), suffix + "0", letterCode); 109 getLetterCode(rooNode.getRight(), suffix + "1", letterCode); 110 } 111 } 112 113 public String decode(EncodeResult decodeResult) { 114 StringBuffer decodeStr = new StringBuffer(); 115 116 Map<String, Character> decodeMap = getDecoder(decodeResult.getLetterCode()); // 获得解码器 117 // 待解码的(被编码的)字符串 118 String encode = decodeResult.getEncode(); 119 // 从最短的开始匹配之所以能够成功,是因为哈夫曼编码的唯一前缀性质 120 // 临时的可能的键值 121 String temp = ""; 122 // 改变temp值大小的游标 123 int i = 1; 124 while (encode.length() > 0) { 125 temp = encode.substring(0, i); 126 if (decodeMap.containsKey(temp)) { 127 Character character = decodeMap.get(temp); 128 decodeStr.append(character); 129 encode = encode.substring(i); 130 i = 1; 131 } else { 132 i++; 133 } 134 } 135 return decodeStr.toString(); 136 } 137 138 /** 139 * 获得解码器,也就是通过字母/编码对得到编码/字符对。 140 * 141 * @param letterCode 142 * @return 143 */ 144 private Map<String, Character> getDecoder(Map<Character, String> letterCode) { 145 Map<String, Character> decodeMap = new HashMap<String, Character>(); 146 Set<Character> keys = letterCode.keySet(); 147 for (Character key : keys) { 148 String value = letterCode.get(key); 149 decodeMap.put(value, key); 150 } 151 return decodeMap; 152 } 153 }
1 /** 2 * 对字符串编码后的结果:包括编码后的字符串和字符/编码对 3 */ 4 public class EncodeResult { 5 // 字符串编码后的结果 6 private String encode; 7 // 字符编码对 8 private Map<Character, String> letterCode; 9 10 public EncodeResult(String encode, Map<Character, String> letterCode) { 11 this.encode = encode; 12 this.letterCode = letterCode; 13 } 14 15 public String getEncode() { 16 return encode; 17 } 18 19 public Map<Character, String> getLetterCode() { 20 return letterCode; 21 } 22 }

浙公网安备 33010602011771号