赫夫曼编码
1)赫夫曼编码也翻译为_哈夫曼编码(Huffman Coding),又称霍夫曼编码,是一种编码方式,属于一种程序算法
2)赫夫曼编码是赫哈夫曼树在电讯通信中的经典的应用之一。
3)赫夫曼编码广泛地用于数据文件压缩。其压缩率通常在20%~90%之间
4)赫夫曼码是可变字长编码(VLC)的一种。Huffman于1952年提出一种编码方法,称之为最佳编码
实例分析
1)i like like like java do you like a java
2) d:1 y:1 u:1 j:2 v:2 o:2 l:4 k:4 e:4 i:5 a:5 :9//各个字符对应的个数
3)按照上面字符出现的次数构建一颗赫夫曼树,次数作为权值
构成赫夫曼树的步骤:
1.从小到大进行排序, 将每一个数据, 每个数据都是一个节点, 每个节点可以看成是一颗最简单的 二叉树
2.取出根节 点权值最小的两颗二叉树
3.组成- -颗新的二叉树,该新的二叉树的根节点的权值是前面两颗二 叉树根节点权值的和
4.再将这颗新的二叉树, 以根节点的权值大小再次排序,不断重复1-2-3-4 的步骤,直到数列中,所有的数据都被处理,就得到一-颗赫夫曼树
4)根据赫夫曼树,给各个字符,规定编码,向左的路径为0向右的路径为1,编码如下:
5)按照上面的赫夫曼编码,我们的"i like like like java do you like a java”字符串对应的编码为(注意这里我们使用的无损压缩)
10101001101111011110100110111101111010011011110111101000011000111001100111100001100111000100100100110111101111011100100001100001110通过赫夫曼编码处理长度为133
6)长度为:133
原来长度是359,压缩了(359-133)/359=62.9%
此编码满足前缀编码,即字符的编码都不能是其他字符编码的前缀。不会造成匹配的多义性
压缩代码如下
创建节点
class Node implements Comparable<Node>{
Byte data; //存放数据本身比如 ‘a’ = 97 ‘ ’ = 32
int weight; //权值出现的次数
Node left;
Node right;
public Node(Byte data, int weight) {
this.data = data;
this.weight = weight;
}
@Override
public int compareTo(Node o) {
//从小到大排序
return this.weight - o.weight;
}
@Override
public String toString() {
return "Node{" +
"data=" + data +
", weight=" + weight +
'}';
}
//前序遍历
public void preOrder(){
System.out.println(this);
if (this.left != null){
this.left.preOrder();
}
if (this.right != null){
this.right.preOrder();
}
}
}
将每一字母符号存入list
private static List<Node> getNodes(byte[] bytes){
//创建list
ArrayList<Node> nodes = new ArrayList<>();
//遍历byte,统计每一个byte出现的次数
Map<Byte, Integer> counts = new HashMap<>();
for (byte b : bytes){
Integer count = counts.get(b);
if (count == null){
counts.put(b, 1);
} else {
counts.put(b, count + 1);
}
}
//把每一个键值转化成Node对象,加入nodes集合
for (Map.Entry<Byte, Integer> entry: counts.entrySet()){
nodes.add(new Node(entry.getKey(), entry.getValue()));
}
return nodes;
}
创建赫夫曼树
private static Node createHuffmanTree(List<Node> nodes){
while (nodes.size() > 1){
Collections.sort(nodes);
//取出最小的
Node leftNode = nodes.get(0);
Node rightNode = nodes.get(1);
Node parent = new Node(null, leftNode.weight + rightNode.weight);
parent.left = leftNode;
parent.right = rightNode;
nodes.remove(leftNode);
nodes.remove(rightNode);
nodes.add(parent);
}
return nodes.get(0);
}
得到赫夫曼编码
//getCodes方法的重载
private static Map<Byte, String> getCodes(Node root){
if (root == null){
return null;
} else {
//先处理左子节点
getCodes(root.left, "0", stringBuilder);
//处理右子节点
getCodes(root.right, "1", stringBuilder);
return huffmanCodes;
}
}
//生成哈夫曼树对应的哈夫曼编码
//将哈夫曼编码表存放在map<byte,string>
static Map<Byte, String> huffmanCodes = new HashMap<>();
//生成的哈夫曼编码需要去拼接路径,定义一个StringBuilder存储叶子节点路径
static StringBuilder stringBuilder = new StringBuilder();
/*
* 将传入的node节点所有的叶子节点的哈夫曼树编码得到,并放入到huffmantree集合
* node 传入节点
* code 路径:左节点是0, 右节点1
* stringBuilder 用于拼接路径
* */
private static void getCodes(Node node, String code, StringBuilder stringBuilder){
StringBuilder stringBuilder2 = new StringBuilder(stringBuilder);
stringBuilder2.append(code);
if (node != null){
//判断当前node是叶子节点还是非叶子节点
if (node.data == null){
//先处理左子节点
getCodes(node.left, "0", stringBuilder2);
//处理右子节点
getCodes(node.right, "1", stringBuilder2);
} else {
huffmanCodes.put(node.data, stringBuilder2.toString());
}
}
}
压缩字节数组
//编写一个方法,将字符串对应的byte[]数组,通过生成的哈夫曼树编码表,返回一个哈夫曼树编码压缩后的byte[]
private static byte[] zip(byte[] bytes, Map<Byte, String> huffmanCodes){
StringBuilder stringBuilder = new StringBuilder();
for (byte b: bytes) {
stringBuilder.append(huffmanCodes.get(b));
}
//将二进制转为10进制保存在byte[]
int len;
if (stringBuilder.length() % 8 == 0){
len = stringBuilder.length() / 8;
} else {
len = stringBuilder.length() / 8 + 1;
}
//创建存储压缩byte数组
byte[] by = new byte[len];
int index = 0;
for (int i = 0; i < stringBuilder.length(); i += 8) {
String strByte;
if (i + 8 > stringBuilder.length()){
strByte = stringBuilder.substring(i);
} else {
strByte = stringBuilder.substring(i, i + 8);
}
by[index] = (byte)Integer.parseInt(strByte, 2);
index++;
}
return by;
}
封装压缩代码
//封装压缩代码
private static byte[] huffmanZip(byte[] contentBytes){
//创建NodeList
List<Node> nodes = getNodes(contentBytes);
//创建huffmanTree
Node nodeHead = createHuffmanTree(nodes);
//生成huffmanTree编码表
Map<Byte, String> huffmanCodes = getCodes(nodeHead);
//压缩成相应的字节码文件
byte[] huffmanCodeByte = zip(contentBytes, huffmanCodes);
return huffmanCodeByte;
}
解码代码如下
将一个byte转成二进制字符串
private static String byteToBitString(boolean flag, byte b){
//使用变量保存b
int temp = b; //将b转成int
//如果是正数我们还存在补高位
if (flag){
temp |= 256;//按位与256
}
String str = Integer.toBinaryString(temp); //返回的是temp对应的二进制补码
//按照补码返回的
if (flag){
return str.substring(str.length() - 8);
} else {
return str;
}
}
解码
//编写一个方法,完成对压缩数据的解码
/*
*huffmanCodes 哈夫曼编码表
*huffmanBytes 需要解码的哈夫曼字节数组
* */
private static byte[] decode(Map<Byte,String> huffmanCodes, byte[] huffmanBytes){
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < huffmanBytes.length; i++) {
byte b = huffmanBytes[i];
boolean flag = (i == huffmanBytes.length - 1);
stringBuilder.append(byteToBitString(!flag, b));
}
//把字符串安装指定的哈夫曼树进行解码
//把哈夫曼树表进行调换,a=100 变成 100=a
Map<String, Byte> map = new HashMap<>();
for (Map.Entry<Byte, String> entry: huffmanCodes.entrySet()) {
map.put(entry.getValue(), entry.getKey());
}
//创建要给集合,存放byte
List<Byte> list = new ArrayList<>();
for (int i = 0; i < stringBuilder.length();) {
int count = 1;
boolean flag = true;
Byte b = null;
while (flag){
String key = stringBuilder.substring(i, i + count);
b = map.get(key);
if (b == null){
count++;
} else {
flag = false;
}
}
list.add(b);
i += count;
}
byte[] b = new byte[list.size()];
for (int i = 0; i < list.size(); i++) {
b[i] = list.get(i);
}
return b;
}
ps:【源码自取,点击即可】
ps:以上笔记均来自尚硅谷韩顺平老师《Java数据结构与java算法》