huffman编码
字符编码
编码分为定长编码和不定长编码。定长编码实现简单,效率高。不定长编码是为了压缩数据而提出的编码方式:给使用频率高的字符短的编码。那么到底如何给字符编码,而使平均长度最短呢?huffman就是解决这个问题的。
不定长编码
先介绍一下前缀编码:
在一个编码系统中,任何一个编码都不是其他编码的前缀,则称该编码系统的编码是前缀码。例如: 01, 10, 110, 111, 101 就不是前缀编码,因为 10 是 101 的前缀,如果去掉 10或101就是前缀编码。当在一个编码系统中采用定长编码时,可以不需要分隔符;如果采用不定长编码时,必须使用前缀编码或分隔符,否则在解码时会产生歧义。所谓解码就是由二进制位串还原字符数据的过程。而使用分隔符会加大编码长度,因此一般采用前缀编码。
前缀编码可以通过二叉树来实现:
使用二叉树对字符集中的字符进行编码的方法是,将字符集中的所有字符作为二叉树的叶子结点;在二叉树中,每一个“父亲—左孩子”关系对应一位二进制位 0,每一个“父亲—右孩子”关系对应一位二进制位 1;于是从根结点通往每个叶子结点的路径,就对应于相应字符的二进制编码。每个字符编码的长度 L 等于对应路径的长度,也等于该叶子结点的层次数。
huffman编码
而如何使得二叉树得到的编码是最优的,也就是平均编码长度,也可以说树的叶子结点的平均深度最小?huffman编码解决了这一问题。其编码步骤如下:
1. 根据给定的 n 个权值,构造 n 棵只有一个根结点的二叉树, n 个权值分别是这些二叉树根结点的权, F 是由这 n 棵二叉树构成的集合;
2. 在 F 中选取两棵根结点树值最小的树作为左、右子树,构造一颗新的二叉树,置新二叉树根的权值=左子树根结点权值+右子树根结点权值;
3. 从 F 中删除这两颗树,并将新树加入 F;
4. 重复以上步骤,直到只剩下最后一颗树。
如下图所示(图截自java数据结构与算法 作者:周鹏):
huffman编码正确性证明
个人理解:
通过huffman编码构造的树,每一步都满足这样一个性质:权值最小的节点层数最大。由这个性质可以推断,无论如何,都无法构造出一颗总权值更小的树。所以huffman编码是最优的。
java代码实现
二叉树节点:
package dataStructureAndAlgorithms;
public class HuffmanTreeNode implements Comparable<HuffmanTreeNode>{
public int getWeight() {
return weight;
}
public void setWeight(int weight) {
this.weight = weight;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public HuffmanTreeNode getLeftChild() {
return leftChild;
}
public void setLeftChild(HuffmanTreeNode leftChild) {
this.leftChild = leftChild;
}
public HuffmanTreeNode getRightChild() {
return rightChild;
}
public void setRightChild(HuffmanTreeNode rightChild) {
this.rightChild = rightChild;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
private String content = null;
private int weight;
private String code = "";
private HuffmanTreeNode leftChild = null;
private HuffmanTreeNode rightChild = null;
public HuffmanTreeNode(int weight){
this.weight = weight;
}
//Returns:a negative integer, zero, or a positive integer as this object is
//less than, equal to, or greater than the specified object.
public int compareTo(HuffmanTreeNode o) {
if(weight<o.getWeight()){
return -1;
}else if(weight > o.getWeight()){
return 1;
}else{
return 0;
}
}
}
哈夫曼编码:
package dataStructureAndAlgorithms;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
public class HuffmanCode {
public static HuffmanTreeNode buildHuffmanTree(List<HuffmanTreeNode> nodes){
int n = nodes.size();
//n-1次
for(int i=1;i<n;i++){
Collections.sort(nodes);
HuffmanTreeNode min1 = nodes.remove(0);
HuffmanTreeNode min2 = nodes.remove(0);
HuffmanTreeNode newNode = new HuffmanTreeNode(min1.getWeight()+min2.getWeight());
newNode.setLeftChild(min1);
newNode.setRightChild(min2);
nodes.add(newNode);
}
return nodes.get(0);
}
public static void generateHuffmanCode(HuffmanTreeNode root){
if(root.getLeftChild() != null){
root.getLeftChild().setCode(root.getCode() + "0");
generateHuffmanCode(root.getLeftChild());
}
if(root.getRightChild() != null){
root.getRightChild().setCode(root.getCode() + "1");
generateHuffmanCode(root.getRightChild());
}
}
public static void main(String[] args) {
HuffmanTreeNode node1 = new HuffmanTreeNode(3);
node1.setContent("A");
HuffmanTreeNode node2 = new HuffmanTreeNode(1);
node2.setContent("B");
HuffmanTreeNode node3 = new HuffmanTreeNode(2);
node3.setContent("C");
HuffmanTreeNode node4 = new HuffmanTreeNode(1);
node4.setContent("D");
List<HuffmanTreeNode>nodes = new LinkedList<HuffmanTreeNode>();
nodes.add(node1);
nodes.add(node2);
nodes.add(node3);
nodes.add(node4);
List<HuffmanTreeNode>nodeCopy = new ArrayList<HuffmanTreeNode>();
nodeCopy.addAll(nodes);
HuffmanTreeNode root = buildHuffmanTree(nodes);
generateHuffmanCode(root);
for(HuffmanTreeNode node:nodeCopy){
System.out.println(node.getContent() + "->" + node.getCode());
}
}
}
结果:
A->0
B->110
C->10
D->111