当前位置:   article > 正文

哈夫曼树(Huffman Tree)及哈夫曼编码(Huffman Coding)_哈夫曼树编码

哈夫曼树编码

目录

一、Huffman树(最优二叉树)

1、定义

2、构造

构造哈夫曼树的算法

哈夫曼树特点

二、Huffman编码


一、Huffman树(最优二叉树)

1、定义

        树的带路径长度,就是树中所有的叶节点的权值乘上其到根节点的路径长度。

        在含有n 个带权叶结点的二叉树中,其中带权路径长度(WPL)最小的二叉树称为哈夫曼树, 也称最优二叉树。如图,c树的WPL=35最小,经验证其为哈夫曼树。

2、构造

构造哈夫曼树的算法

(给定n 个权值分别为wi的结点)

1)将这n个结点分别作为n棵仅含一个结点的二叉树,构成森林F。

2)构造一个新结点,从 F 中选取两棵根结点权值最小的树作为新结点的左、右子树,并且将新结点的权值置为左、右子树上根结点的权值之和。

3)从 F 中删除刚才选出的两棵树,同时将新得到的树加入F中。

4)重复步骤2)和 3), 直至F中只剩下一棵树为止。

  1. //哈夫曼
  2. class Node implements Comparable<Node> {
  3. Byte data;
  4. int weight;
  5. Node left;
  6. Node right;
  7. public Node(Byte data, int weight) {
  8. this.data = data;
  9. this.weight = weight;
  10. }
  11. @Override
  12. public int compareTo(Node o) {
  13. return this.weight - o.weight;
  14. }
  15. @Override
  16. public String toString() {
  17. return "Node [ data = " + data + "weight = " + weight + " ]";
  18. }
  19. //遍历
  20. public void preOrder() {
  21. System.out.println(this);
  22. if (this.left != null) {
  23. this.left.preOrder();
  24. }
  25. if (this.right != null) {
  26. this.right.preOrder();
  27. }
  28. }
  29. }
  30. private static Node createHuffmanTree(List<Node> nodes) {
  31. /**
  32. *@MethodName createHuffmanTree
  33. *@Description TODO 创建哈夫曼树
  34. *@Author SSRS
  35. *@Date 2020-10-31 13:21
  36. *@Param [nodes]
  37. *@ReturnType Java_note.Algorithm.HuffmanCode.Node
  38. */
  39. while (nodes.size() > 1) {
  40. //排序
  41. Collections.sort(nodes);
  42. //取最小两个二叉树
  43. Node leftNode = nodes.get(0);
  44. Node rightNode = nodes.get(1);
  45. //创建新的二叉树(他的根节点没有data,只有权值)
  46. Node parent = new Node(null, leftNode.weight + rightNode.weight);
  47. parent.left = leftNode;
  48. parent.right = rightNode;
  49. //删除已用结点
  50. nodes.remove(leftNode);
  51. nodes.remove(rightNode);
  52. //加入新节点
  53. nodes.add(parent);
  54. }
  55. return nodes.get(0);
  56. }

哈夫曼树特点

1)每个初始结点最终都成为叶结点,且权值越小的结点到根结点的路径长度越大

2)构造过程中共新建了n-1个结点 (双分支结点),因此哈夫曼树的结点总数为2n- 1 。

3)每次构造都选择 2 棵树作为新结点的孩子,因此哈夫曼树中不存在度为1 的结点

例如,权值{7, 5, 2, 4}的哈夫曼树的构造过程如图所示。

二、Huffman编码

        在数据通信中,若对每个字符用相等长度的二进制位表示,称这种编码方式为固定长度编码。 若允许对不同字符用不等长的二进制位表示,则这种编码方式称为可变长度编码

        可变长度编码 比固定长度编码要好得多,其特点是对频率高的字符赋以短编码,而对频率较低的字符则赋以较长一些的编码,从而可以使字符的平均编码长度减短,起到压缩数据的效果。哈夫曼编码是一种被广泛应用而且非常有效的数据压缩编码

        若没有一个编码是另一个编码的前缀,则称这样的编码为前缀编码。哈夫曼编码是前缀编码。

 

  1. class HuffmanCode {
  2. public static void main(String[] args) {
  3. String str = "I like like like java Do you like a java";
  4. byte[] strBytes = str.getBytes();
  5. System.out.println(str + "的原长度是" + strBytes.length);
  6. HuffmanCode huffmanCode = new HuffmanCode();
  7. System.out.println("转成哈夫曼编码为:");
  8. String huffmanstr = huffmanCode.createHuffmanCode(strBytes);
  9. System.out.println(huffmanstr);
  10. System.out.println("其长度为:" + huffmanstr.getBytes().length);
  11. byte[] bytes = huffmanCode.zipbytes(strBytes);
  12. System.out.println("压缩后的结果是:" + Arrays.toString(bytes));
  13. System.out.println("其长度为:" + bytes.length);
  14. System.out.println("解压缩:" + new String(huffmanCode.rezip(bytes)));
  15. }
  16. private static List<Node> getNodes(byte[] bytes) {
  17. /**
  18. *@MethodName getNodes
  19. *@Description TODO 获得构建哈夫曼树的Node
  20. *@Author SSRS
  21. *@Date 2020-10-31 13:07
  22. *@Param [bytes]
  23. *@ReturnType java.util.List<Java_note.Algorithm.HuffmanCode.Node>
  24. */
  25. ArrayList<Node> nodes = new ArrayList<Node>();//要返回的nodes集合
  26. Map<Byte, Integer> counts = new HashMap<>();
  27. //统计每一个byte出现的次数
  28. for (byte b : bytes) {
  29. Integer count = counts.get(b);
  30. if (count == null) {
  31. counts.put(b, 1);
  32. } else {
  33. counts.put(b, count + 1);
  34. }
  35. }
  36. //把每一个键值对转换成Node对象,并加入nodes集合
  37. for (Map.Entry<Byte, Integer> each : counts.entrySet()) {
  38. nodes.add(new Node(each.getKey(), each.getValue()));
  39. }
  40. return nodes;
  41. }
  42. private static Node createHuffmanTree(List<Node> nodes) {
  43. /**
  44. *@MethodName createHuffmanTree
  45. *@Description TODO 创建哈夫曼树
  46. *@Author SSRS
  47. *@Date 2020-10-31 13:21
  48. *@Param [nodes]
  49. *@ReturnType Java_note.Algorithm.HuffmanCode.Node
  50. */
  51. while (nodes.size() > 1) {
  52. //排序
  53. Collections.sort(nodes);
  54. //取最小两个二叉树
  55. Node leftNode = nodes.get(0);
  56. Node rightNode = nodes.get(1);
  57. //创建新的二叉树(他的根节点没有data,只有权值)
  58. Node parent = new Node(null, leftNode.weight + rightNode.weight);
  59. parent.left = leftNode;
  60. parent.right = rightNode;
  61. //删除已用结点
  62. nodes.remove(leftNode);
  63. nodes.remove(rightNode);
  64. //加入新节点
  65. nodes.add(parent);
  66. }
  67. return nodes.get(0);
  68. }
  69. static Map<Byte, String> codes = new HashMap<Byte, String>();
  70. private static Map<Byte, String> getCodes(Map<Byte, String> codes, Node node, String code, StringBuilder stringBuilder) {
  71. StringBuilder stringBuilder1 = new StringBuilder(stringBuilder);
  72. stringBuilder1.append(code);
  73. if (node != null) {
  74. if (node.data == null) {
  75. getCodes(codes, node.left, "0", stringBuilder1);
  76. getCodes(codes, node.right, "1", stringBuilder1);
  77. } else {
  78. codes.put(node.data, stringBuilder1.toString());
  79. }
  80. }
  81. return codes;
  82. }
  83. private static byte[] bytesArrays(String string) {
  84. int len;//bytes数组长度
  85. if (string.length() % 8 == 0) {
  86. len = string.length() / 8;
  87. } else {
  88. len = string.length() / 8 + 1;
  89. }
  90. int index = 0;//bytes角标
  91. byte[] huffmanCodeBytes = new byte[len];
  92. for (int i = 0; i < string.length(); i += 8) {
  93. String strTemp;
  94. if (i + 8 > string.length()) {
  95. strTemp = string.substring(i);
  96. } else {
  97. strTemp = string.substring(i, i + 8);
  98. }
  99. //转换
  100. huffmanCodeBytes[index] = (byte) Integer.parseInt(strTemp, 2);
  101. index++;
  102. }
  103. return huffmanCodeBytes;
  104. }
  105. public static String createHuffmanCode(byte[] bytes) {
  106. /**
  107. *@MethodName createHuffmanCode
  108. *@Description TODO 得到哈夫曼转码
  109. *@Author SSRS
  110. *@Date 2020-10-31 20:15
  111. *@Param [bytes]
  112. *@ReturnType java.lang.String
  113. */
  114. Node root = createHuffmanTree(getNodes(bytes));
  115. StringBuilder stringBuilder = new StringBuilder();
  116. getCodes(codes, root, "", stringBuilder);
  117. String str = "";
  118. for (Byte each : bytes) {
  119. str += codes.get(each);
  120. }
  121. return str;
  122. }
  123. public byte[] zipbytes(byte[] bytes) {
  124. return bytesArrays(createHuffmanCode(bytes));
  125. }
  126. //解压缩
  127. private String byteToBitString(boolean flag, byte b) {
  128. /**
  129. *@MethodName byteToBitString
  130. *@Description TODO 将一个byte转成二进制字符串
  131. *@Author SSRS
  132. *@Date 2020-10-31 20:56
  133. *@Param [flag 正数为true要补高位, b]
  134. *@ReturnType java.lang.String
  135. */
  136. int temp = b;//转int
  137. //如果是正数还要补高位
  138. if (flag) {
  139. temp |= 256;//按位与 256 是 1 0000 0000 | 0000 0001=》1 0000 0001
  140. }
  141. String str = Integer.toBinaryString(temp);//返回的是二进制的补码
  142. if (flag) {
  143. return str.substring(str.length() - 8);
  144. } else {
  145. return str;
  146. }
  147. }
  148. public byte[] rezip(byte[] huffmanbytes) {
  149. StringBuilder stringBuilder = new StringBuilder();//储存二进制字符串
  150. //全部转成二进制字符串
  151. for (int i = 0; i < huffmanbytes.length; i++) {
  152. //如果是最后一个可能不满8位就转码的那个就不用补高位无论正负,因此要判断
  153. boolean flag = (i == huffmanbytes.length - 1);
  154. stringBuilder.append(byteToBitString(!flag, huffmanbytes[i]));
  155. }
  156. //把哈夫曼编码表反向
  157. Map<String, Byte> map = new HashMap<String, Byte>();
  158. for (Map.Entry<Byte, String> each : codes.entrySet()) {
  159. map.put(each.getValue(), each.getKey());
  160. }
  161. List<Byte> list = new ArrayList();//解压缩后的语句的储存位置
  162. for (int i = 0; i < stringBuilder.length(); ) {
  163. int count = 1;//计数,计每一个字符的二进制字符位数
  164. boolean flag = true;
  165. Byte b = null;
  166. //找字符对应哈夫曼编码
  167. while (flag) {
  168. String key = stringBuilder.substring(i, i + count);
  169. b = map.get(key);
  170. if (b == null) {
  171. count++;
  172. } else {//匹配成功
  173. flag = false;
  174. }
  175. }
  176. list.add(b);
  177. i = i + count;
  178. }
  179. byte b[] = new byte[list.size()];
  180. for (int i = 0; i < b.length; i++) {
  181. b[i] = list.get(i);
  182. }
  183. return b;
  184. }
  185. public void preOrder(Node root) {
  186. if (root != null) {
  187. root.preOrder();
  188. } else {
  189. System.out.println("二叉树为空。");
  190. }
  191. }
  192. }

 

 本文所有概念性描述及图示均来自王道考研数据结构一书

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小丑西瓜9/article/detail/693604
推荐阅读
相关标签
  

闽ICP备14008679号