哈夫曼树的实现
1.编码思想
哈夫曼编码是一种变长的编码方案,字符的编码根据使用频率的的不同而长短不一, 使用频率高的字符其编码较短,使用频率低的字符编码较长,从而使所有的编码总长度为最短.
- 统计原始数据中个新号符号的频率,安频率高低的次序排列
- 将两个频率最小的相加,作为原本两个节点的父节点,次父节点的频率为子节点之和
- 重复上述两部,直到和为只剩下一个元素,那么这个元素就是根
2.解码思想
利用Haffman树进行解码,已知一个二进制位串S,
从串S的第一位出发,逐位的去匹配二叉树边上标记的0和1
从haffman树的根节点出发,遇到0时,向左,遇到1时向右,若干连续的0和1确定一条从根节点到某个叶子节点的路径.一旦到达一个叶子节点,便译出一个字符,
接着从S的下一个位开始继续寻找,然后在重新将指针指向根.
编码和解码操作
主要有三部分
1.对编码进行计数,然后通过键值对构造haffman树,这里主要使用了优先队列,也就是最小堆,需要内部节点类实现Comparable接口
2.通过树,返回叶节点的编码,这里我利用Map<Character, String>这样将字符和编码绑定到了一起,方便后面操作
3.根据输入的字符串,遍历,返回编码后的字符串,如下:
//toCode()返回一个Map键值对
Map<Character, String> codes = haff.toCode() ;
StringBuilder stringcode = new StringBuilder() ;
//遍历字符串,求其编码
for(char ch : text.toCharArray()) {
stringcode.append(codes.get(ch)) ;
}
return stringcode.toString() ;
下面是代码演示:
1 package com.jffx.util; 2 3 import java.util.*; 4 5 /* 6 本类负责逻辑处理, 7 负责串---->码的编码 8 吗---->串的解码 9 */ 10 public class HaffmanCode { 11 12 private Node root ; 13 14 15 //---------------------------------------- 下面是内部节点类 16 static class Node implements Comparable<Node> { 17 Integer weight ; 18 String code = "" ; //取一个字符串域表示每个节点的编码 19 Character data ; 20 21 Node parent ; 22 Node left ; 23 Node right ; 24 public Node(int w, Node left, Node right, Node parent) { 25 this.weight = w ; 26 this.left = left ; 27 this.parent = parent ; 28 this.right = right ; 29 } 30 31 @Override 32 public int compareTo(Node o) { 33 return this.weight - o.weight ; 34 } 35 36 @Override 37 public String toString() { 38 return "[" + this.data + ", " + this.weight + "]" ; 39 } 40 } 41 42 43 /** 44 * 构造haffman树 45 * 先将strs这样的键值对转化为节点, 然后加入优先队列 46 * @param strs 字符--频率这样的键值对 47 */ 48 public HaffmanCode(Map<Character, Integer> strs) { 49 Queue<Node> queue = new PriorityQueue<>() ; 50 51 Set<Character> keys = strs.keySet() ; 52 for(Character key : keys) { 53 Node newNode = new Node(strs.get(key), null, null, null) ; 54 newNode.data = key ; 55 newNode.weight = strs.get(key) ; 56 57 //入堆 -- 覆写CompareTo 58 queue.add(newNode) ; 59 } 60 61 while(queue.size() > 1) { 62 Node n1 = queue.poll() ; 63 Node n2 = queue.poll() ; 64 65 Node sumNode = new Node(n1.weight + n2.weight , n1, n2, null) ; 66 67 n1.parent = sumNode ; 68 n2.parent = sumNode ; 69 70 queue.add(sumNode) ; 71 } 72 73 this.root = queue.poll() ; 74 } 75 76 /** 77 * 将树的叶节点的编码返回, 返回的键值是 字符 -- 编码 这样的形式 78 * @return 79 */ 80 public Map<Character, String> toCode() { 81 Map<Character, String> map = new HashMap<>() ; 82 83 preTraverse() ; 84 85 //前序遍历.将叶节点的属性保存到map中 86 List<Node> stack = new LinkedList<>() ; 87 Node p = this.root ; 88 while(p != null || !stack.isEmpty()) { 89 while(p != null) { 90 stack.add(p) ; 91 p = p.left ; 92 } 93 //如果栈不空,去栈顶元素,然后向右 94 if(!stack.isEmpty()) { 95 p = ((LinkedList<Node>) stack).pop() ; 96 if(isLeafNode(p)) { 97 map.put(p.data, p.code) ; 98 } 99 p = p.right ; 100 } 101 } 102 return map ; 103 } 104 105 /** 106 * 输入一串编码,然后返回解码后的字符串 107 * @return 108 */ 109 public String deCode(String codes) { 110 StringBuilder stringBuilder = new StringBuilder() ; 111 112 Node p = this.root ; //从根出发 113 for(int i = 0 ; i < codes.length() ; ++i) { 114 if(codes.charAt(i) == '0') { 115 p = p.left ; 116 } else { 117 p = p.right ; 118 } 119 if(isLeafNode(p)) { //如果是叶子 120 stringBuilder.append(p.data) ; 121 p = this.root ; 122 } 123 } 124 return stringBuilder.toString() ; 125 } 126 127 private void preTraverse() { 128 preTraverse(this.root) ; 129 } 130 private void preTraverse(Node root) { 131 if(root != null) { 132 if(root != this.root) { //如果不是根节点 133 if(root == root.parent.left) { //左孩子 134 root.code = root.parent.code + "0" ; 135 } else { 136 root.code = root.parent.code + "1" ; 137 } 138 } 139 //System.out.println(root) ; 140 preTraverse(root.left) ; 141 preTraverse(root.right) ; 142 } 143 } 144 145 public boolean isLeafNode(Node node) { 146 return node.left == null && node.right == null ; 147 } 148 149 150 151 152 153 154 155 156 157 /** 158 * 测试 159 * @param args 160 */ 161 public static void main(String[] args) { 162 String str = "today is Saturday." ; 163 164 char[] strCh = str.toCharArray() ; 165 Map<Character, Integer> map = new HashMap<>() ; 166 167 for(int i = 0 ; i < strCh.length ; ++i) { 168 if(map.containsKey(strCh[i])) { //如果存在键 169 map.put(strCh[i], map.get(strCh[i]) + 1) ; 170 } else { 171 map.put(strCh[i], 1) ; 172 } 173 } 174 175 HaffmanCode haff = new HaffmanCode(map) ; 176 177 String code = getTextCode(haff, str) ; 178 String decode = haff.deCode(code) ; 179 System.out.println(decode) ; 180 } 181 182 /** 183 * 获取一个字符串的编码 184 * @param text 需要压缩的字符串 185 * @return 186 */ 187 public static String getTextCode(HaffmanCode haff, String text) { 188 189 Map<Character, String> codes = haff.toCode() ; 190 StringBuilder stringcode = new StringBuilder() ; 191 for(char ch : text.toCharArray()) { 192 stringcode.append(codes.get(ch)) ; 193 } 194 return stringcode.toString() ; 195 } 196 }
这里你会发现,你压缩后的数据比你没压缩前还大,额--------
学无止境