赫夫曼编码的原理剖析
基本介绍
1)赫夫曼编码也翻译为哈夫曼编码(Huffman Coding),又称霍夫曼编码,是一种编码方式。属于一种程序算法
2)赫夫曼编码是赫哈夫曼树在电讯通信中的经典的应用之一。
3)赫夫曼编码广泛地用于数据文件压缩。其压缩率通常在20%~90%之间
4)赫夫曼码是可变字长编码(VLC)的一种。Huffman于1952 年提出一种编码方法, 称之为最佳编码
传输的字符串
1)i like like like java do you like a java
2) d:1y:1u:1j:2 v:2 o:2 l:4 k:4 e:4i:5 a:5 :9 //各个字符对应的个数
3)按照上面字符出现的次数构建一颗赫夫曼树,次数作为权值
步骤:
构成赫夫曼树的步骤:
1)从小到大进行排序,每个数据都是一个节点,每个节点可以看成是一 颗最简单的二叉树
2)取出根节点权值最小的两颗二叉树
3)组成一颗新的二叉树,该新的二叉树的根节点的权值是前面两颗二叉树根节点权值的和
4)再将这颗新的二叉树,以根节点的权值大小再次排序,不断重复1-2-3-4的步骤,直到数列中,所有的数据都被处理,就得到一颗赫夫曼树
4)根据赫夫曼树,给各个字符,规定编码(前缀编码),向左的路径为0向右的路径为1,编码如下:
0: 1000 u: 10010 d: 100110 y: 100111 i:101
a:110 k:1110 e: 1111 j: 0000 v: 0001 l: 001 :01(空格)
5)按照上面的赫夫曼编码,我们的"i like like like java do you like a java"字符串对应的编码为(注意这里我们使用的无损压缩)
1010100110111101111010011011110111101001101111011110100001100001110011001111000011001111000100100100110111101111011100100001100001110通过赫夫曼编码处理长度为133
6) 长度为: 133
说明:
原来长度是359,压缩了(359-133)/359= 62.9%
此编码满足前缀编码,即字符的编码都不能是其他字符编码的前缀。不会造成匹配的多义性(若是该字符编码是其他字符前缀 则该字符对应的节点为其父节点 而赫夫曼树中所有数据都为叶子节点 所以避免了多义性)
赫夫曼编码是无损处理
注意事项
注意,这个赫夫曼树根据排序方法不同,也可能不太一样,这样对应的赫夫曼编码也不完全一样,但是wpl是一样的,都是最小的,比如:如果我们让每次生成的新的二又树总是排在权值相同的二叉树的最后一个,则生成的不同
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class HuffmanCode {
public static void main(String[] args) {
String content = "i like like like java do you like a java";
byte[] contentBytes = content.getBytes();
System.out.println(contentBytes.length);//40
List<Node> nodes = getNodes(contentBytes);
System.out.println(nodes);
System.out.println("赫夫曼树");
Node huffmanTreeRoot = creatHuffmanTree(nodes);
System.out.println("前序遍历");
preOrder(huffmanTreeRoot);
getCodes(huffmanTreeRoot);
System.out.println("赫夫曼编码表"+huffmanCodes);
}
//byte 接受字节数组
//返回List形式
private static List<Node> getNodes(byte[] bytes){
ArrayList<Node> nodes = new ArrayList<Node>();
//遍历bytes 统计每个Byte出现的次数 map【key,value】
Map<Byte,Integer> counts = new HashMap<>();
for(byte b :bytes) {
Integer count = counts.get(b);
if(count == null) {
//map没有该字符数据 第一次
counts.put(b,1);
}else {
counts.put(b,count+1);
}
}
//把每一个键值对转化成一个Node对象 加入到nodes集合
//遍历map
for(Map.Entry<Byte, Integer> entry:counts.entrySet()) {
nodes.add(new Node(entry.getKey(),entry.getValue()));
}
return nodes;
}
//通过list创建对应的赫夫曼树
private static Node creatHuffmanTree(List<Node> nodes) {
while(nodes.size() > 1) {
//从小到大
Collections.sort(nodes);
Node leftnode = nodes.get(0);
Node rightnode = nodes.get(1);
//创建新的二叉树 跟节点没有data只有权值
Node parent = new Node(null,leftnode.weight+rightnode.weight);
parent.left = leftnode;
parent.right = rightnode;
nodes.remove(leftnode);
nodes.remove(rightnode);
nodes.add(parent);
}
//nodes最后的节点就是赫夫曼树的根节点
return nodes.get(0);
}
private static void preOrder(Node root) {
if(root != null) {
root.preOrder();
}else {
System.out.println("赫夫曼树为空");
}
}
//生成赫夫曼树对应的赫夫曼编码
//思路1.将赫夫曼编码表放在map<byte,string>
static Map<Byte,String> huffmanCodes = new HashMap<Byte,String>();
//2.生成赫夫曼编码表 需要拼接路径定义一个StringBuilder存储某个叶子结点的路径
static StringBuilder stringBuilder = new StringBuilder();
//重载方法
private static Map<Byte,String> getCodes(Node root){
if(root == null) {
return null;
}
//处理左右子树
getCodes(root.left,"0",stringBuilder);
getCodes(root.right,"1",stringBuilder);
return huffmanCodes;
}
private static void getCodes(Node node,String code,StringBuilder stringBuilder) {
//将传入的node节点的所有叶子结点的赫夫曼编码 并放入到huffmanCodes集合
//node 传入节点
//code 路径 左子节点为0 右子节点为1
//StringBuilder 用于拼接路径
StringBuilder stringBuilder2= new StringBuilder(stringBuilder);
stringBuilder2.append(code);//将code加入stringBuilder2
if(node != null) {
//如果node == null 不处理
//判断node是否为叶子节点
if(node.data == null) {
//非叶子结点
//递归处理
//向左
getCodes(node.left,"0",stringBuilder2);
//向右
getCodes(node.right,"1",stringBuilder2);
}else {
//叶子节点 表示找到了最后
huffmanCodes.put(node.data, stringBuilder2.toString());
}
}
}
}
//创建Node 数据和权值
class Node implements Comparable<Node>{
Byte data;//存放数据(字符本身) 如'a'=>97 asc码
int weight;//权值 表示字符出现的次数
Node left;
Node right;
public Node(Byte data, int weight) {
this.data = data;
this.weight = weight;
}
public int compareTo(Node o) {
// 从小到大排序
return this.weight - o.weight;
}
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@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创建对应的赫夫曼树
}