哈夫曼编码

哈夫曼编码(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 }

  

 
 
posted @ 2017-12-24 12:02  南极山  阅读(1323)  评论(0)    收藏  举报