不同的文件各种信息出现的频率不同;
一种单一的编码方式可能只对某些文件而言是最优解;
霍夫曼编码:根据文件中各种信息出现的频率进行变长编码。
以英文文本进行举例说明(汉字、图像中的bit可以类推)。
霍夫曼编码
根据出现的频率对信息进行编码,如图例所示。
思路
给定一个字符串,计算每个符号出现的频率,从小到大排列;
使用哈夫曼编码的方式生成编码map和解码trie;
使用编码map将字符串压缩编码;
将编码后的bit和解码trie一起传输;
使用解码trie将字符串解码。
java代码
Counter.java
import java.util.*;
public class Counter {
/**
* 给定一个字符串,计算每个符号出现的频率,从小到大排列;
*/
private HashMap<Character, Integer> res;
private List<Pair> charPairs;
private String s;
Counter(String s) {
res = new HashMap<>();
this.s = s;
count();
}
private void count() {
for (int i = 0; i < s.length(); i ++) {
char curChar = s.charAt(i);
if (res.containsKey(curChar)) {
res.put(curChar, res.get(curChar) + 1);
}
res.putIfAbsent(curChar, 1);
}
charPairs = sortByValue(res);
}
public List<Pair> getCharPairs() {
return charPairs;
}
private class selfComparator implements Comparator<Pair> {
/* 自定义Comparator
*/
@Override
public int compare(Pair o1, Pair o2) {
if (o1.getTimes() < o2.getTimes()) {
return -1;
} else if (o1.getTimes() > o2.getTimes()) {
return 1;
}
return 0;
}
}
private List<Pair> sortByValue(HashMap<Character, Integer> input) {
Set entyrSet = input.entrySet();
List<Pair> res = new ArrayList<>();
for (Object e : entyrSet) {
Map.Entry ee = (Map.Entry) e;
Pair temp = new Pair((char)ee.getKey(), (Integer) ee.getValue());
res.add(temp);
}
Collections.sort(res, new selfComparator());
return res;
}
}
Huffman.java
import java.util.*;
public class Huffman {
/**
* 使用Huffman编码对输入的信息进行编码
* 生成编码Map和解码Trie
*/
private List<TrieNode> input;
private TrieNode root;
private HashMap<Character, String> map;
Huffman(List<Pair> input) {
this.input = new ArrayList<>();
for (Pair p : input) {
TrieNode t = new TrieNode(p);
this.input.add(t);
}
getTrie();
getMap();
}
public HashMap<Character, String> getHufMap() {
return map;
}
public class TrieNode {
/**
* 将input的Pair转换为Trie中的节点
* 初始时每个都存的一个节点
* 然后将最小的两个节点合并
* 更新数组
*/
private Pair p;
private TrieNode left;
private TrieNode right;
TrieNode(Pair p) {
this.p = p;
left = null;
right = null;
}
}
private class selfComparator implements Comparator<TrieNode> {
/* 自定义Comparator
*/
@Override
public int compare(TrieNode o1, TrieNode o2) {
if (o1.p.getTimes() < o2.p.getTimes()) {
return -1;
} else if (o1.p.getTimes() > o2.p.getTimes()) {
return 1;
}
return 0;
}
}
public void getTrie() {
while (input.size() != 1) {
TrieNode first = input.get(0);
TrieNode second = input.get(1);
int totalTimes = first.p.getTimes() + second.p.getTimes();
TrieNode mergeNode = new TrieNode(new Pair('\0', totalTimes));
mergeNode.left = first;
mergeNode.right = second;
deleteFirstTwo();
input.add(mergeNode);
Collections.sort(input, new selfComparator());
}
root = input.get(0);
}
/**
* 对Trie进行DFS,构造编码map
* 左1右0
*/
public void getMap() {
map = new HashMap<>();
getMapHelper(root, "");
}
private void getMapHelper(TrieNode curNode, String prevCode) {
if (isLeaf(curNode)) {
map.put(curNode.p.getChar(), prevCode);
return;
}
getMapHelper(curNode.left, prevCode + "1");
getMapHelper(curNode.right, prevCode + "0");
}
/**
* 将输入的String编码为二进制格式
* @param inputString 待编码字符串
* @return 编码后的二进制String
*/
public String enCodeString (String inputString) {
StringBuilder res = new StringBuilder();
for (int i = 0; i < inputString.length(); i++) {
char curChar = inputString.charAt(i);
res.append(map.get(curChar));
}
return res.toString();
}
/**
* 将编码后的二进制信息解码为String
* @param code 哈夫曼编码后的二进制信息
* @return 解码后的String
*/
public String deCodeToString (String code) {
return deCodeHelper(root, code, "");
}
private String deCodeHelper(TrieNode curNode, String code, String prev) {
if (code.equals("")) {
prev += curNode.p.getChar();
return prev;
} else {
if (isLeaf(curNode)) {
prev += curNode.p.getChar();
return deCodeHelper(root, code, prev);
} else {
char curChar = code.charAt(0);
if (curChar == '1') {
return deCodeHelper(curNode.left, code.substring(1), prev);
} else {
return deCodeHelper(curNode.right, code.substring(1), prev);
}
}
}
}
/**
* 判断当前节点是否为叶子节点
* @param curNode
* @return
*/
private boolean isLeaf(TrieNode curNode) {
return curNode.left == null &&
curNode.right == null;
}
/**
* 删除input的前两个节点
*/
private void deleteFirstTwo() {
input.remove(0);
input.remove(0);
}
}
Pair.java
public class Pair {
/**
* char和它出现的频次
*/
private char c;
private int times;
Pair(char c, int times) {
this.c = c;
this.times = times;
}
public char getChar() {
return c;
}
public int getTimes() {
return times;
}
}
Test.java
public class Test {
/**
* 对压缩和解压缩环节进行测试
*/
public static void main(String[] args) {
// 对Counter进行测试
String testString = "China will win this competition!";
Counter t = new Counter(testString);
System.out.println("编码前:" + testString);
// for (Pair p : t.getCharPairs()) {
// System.out.println(p.getChar() + " " + p.getTimes());
// }
// 对哈夫曼编码进行测试
Huffman huf = new Huffman(t.getCharPairs());
//System.out.println(huf.getHufMap());
// 对String进行编码
String code = huf.enCodeString(testString);
System.out.println("编码后:" + code);
// 对解码过程进行测试,同时测试前面的所有功能
String originMess = huf.deCodeToString(code);
System.out.println("解码后:" + originMess);
}
}