摘要:哈夫曼编码作为一种编码方式,已经在生活中得到了实际的运用,下面我们以java实现的哈夫曼编码与解码为核心来讲述它的编码方式及程序等。
哈夫曼编码定义
哈夫曼编码(Huffman Coding),又称霍夫曼编码,是一种编码方式,哈夫曼编码是可变字长编码(VLC)的一种。Huffman于1952年提出一种编码方法,该方法完全依据字符出现概率来构造异字头的平均长度最短的码字,有时称之为最佳编码,一般就叫做Huffman编码(有时也称为霍夫曼编码)。
哈夫曼编码原理
设某信源产生有五种符号u1、u2、u3、u4和u5,对应概率P1=0.4,P2=0.1,P3=P4=0.2,P5=0.1。首先,将符号按照概率由大到小排队,如图所示。编码时,从最小概率的两个符号开始,可选其中一个支路为0,另一支路为1。这里,我们选上支路为0,下支路为1。再将已编码的两支路的概率合并,并重新排队。多次重复使用上述方法直至合并概率归一时为止。从图(a)和(b)可以看出,两者虽平均码长相等,但同一符号可以有不同的码长,即编码方法并不唯一,其原因是两支路概率合并后重新排队时,可能出现几个支路概率相等,造成排队方法不唯一。一般,若将新合并后的支路排到等概率的最上支路,将有利于缩短码长方差,且编出的码更接近于等长码。这里图(a)的编码比(b)好。
赫夫曼码的码字(各符号的代码)是异前置码字,即任一码字不会是另一码字的前面部分,这使各码字可以连在一起传送,中间不需另加隔离符号,只要传送时不出错,收端仍可分离各个码字,不致混淆。
实际应用中,除采用定时清洗以消除误差扩散和采用缓冲存储以解决速率匹配以外,主要问题是解决小符号集合的统计匹配,例如黑(1)、白(0)传真信源的统计匹配,采用0和1不同长度游程组成扩大的符号集合信源。游程,指相同码元的长度(如二进码中连续的一串0或一串1的长度或个数)。按照CCITT标准,需要统计2×1728种游程(长度),这样,实现时的存储量太大。事实上长游程的概率很小,故CCITT还规定:若l表示游程长度,则l=64q+r。其中q称主码,r为基码。编码时,不小于64的游程长度由主码和基码组成。而当l为64的整数倍时,只用主码的代码,已不存在基码的代码。
长游程的主码和基码均用赫夫曼规则进行编码,这称为修正赫夫曼码,其结果有表可查。该方法已广泛应用于文件传真机中。
Java实现哈夫曼编码和解码
将一个字符串进行哈夫曼编码;编码过程中,会得到每个字符的编码,通过已知的每个字符的编码对之前的编码进行解码。
分析:首先是哈夫曼编码算法,引用李泽年写的《多媒体技术教程》中对哈夫曼编码算法的描述:
•Initialization: Put all symbols on a list sorted according to their frequency counts.
•Repeat until the list has only one symbol left:
–From the list pick two symbols with the lowest frequency counts. Form a Huffman subtree that has these two symbols as child nodes and create a parent node.
–Assign the sum of the children‘s frequency counts to the parent and insert it into the list such that the order is maintained.
–Delete the children from the list.
•Assign a code word for each leaf based on the path from the root.
我的代码是基于这段算法描述实现的。实际上,我看的是中文版,但是没有找到该书的中文电子版,只好把英文版粘过来了。不过,好在英文版的也不复杂。
接下来是解码。虽然解码过程很简单,但是却是本文存在的理由。我在网上看了一些文章,都忽略一个问题:编码和解码过程中都有的东西是什么?也就是,依靠什么东西来解码?本文的答案是“每个字符的编码”,它在编码的过程中生成,和字符串编码一起传到解码端用于解码。你也可以说是“每个字符出现的次数”或者“哈夫曼树”,不管是“每个字符出现的次数”还是“哈夫曼树”,你都需要通过他们得到“每个字符的编码”之后才能进行解码。
下面是Java代码:
[java] view plain copypackage com.liyuncong.algorithms.algorithms_huffman;
/**
* 哈夫曼树的节点
* @author yuncong
*
*/
public class Node implements Comparable《Node》{
private Node leftChild = null;
private Data data = null;
private Node rightChild = null;
public Node getLeftChild() {
return leftChild;
}
public void setLeftChild(Node leftChild) {
this.leftChild = leftChild;
}
public Data getData() {
return data;
}
public void setData(Data data) {
this.data = data;
}
public Node getRightChild() {
return rightChild;
}
public void setRightChild(Node rightChild) {
this.rightChild = rightChild;
}
@Override
public String toString() {
return “Node [leftChild=” + leftChild + “, data=” + data
+ “, rightChild=” + rightChild + “]”;
}
@Override
public int compareTo(Node o) {
return this.data.compareTo(o.getData());
}
}
[java] view plain copypackage com.liyuncong.algorithms.algorithms_huffman;
/**
* Data用于存储一个字符及其出现的次数
* @author yuncong
*
*/
public class Data implements Comparable《Data》{
// 字符
private char c = 0;
// 字符出现的次数
private int frequency = 0;
public char getC() {
return c;
}
public void setC(char c) {
this.c = c;
}
public int getFrequency() {
return frequency;
}
public void setFrequency(int frequency) {
this.frequency = frequency;
}
@Override
public String toString() {
return “Data [c=” + c + “, frequency=” + frequency + “]”;
}
@Override
public int compareTo(Data o) {
if (this.frequency 《 o.getFrequency()) {
return -1;
} else if (this.frequency 》 o.getFrequency()) {
return 1;
} else {
return 0;
}
}
}
[java] view plain copypackage com.liyuncong.algorithms.algorithms_huffman;
import java.util.Map;
/**
* 对字符串编码后的结果:包括编码后的字符串和字符/编码对
* @author yuncong
*
*/
public class EncodeResult {
// 字符串编码后的结果
private String encode;
// 字符编码对
private Map《Character, String》 letterCode;
public EncodeResult(String encode, Map《Character, String》 letterCode) {
super();
this.encode = encode;
this.letterCode = letterCode;
}
public String getEncode() {
return encode;
}
public Map《Character, String》 getLetterCode() {
return letterCode;
}
}
[java] view plain copypackage com.liyuncong.algorithms.algorithms_huffman;
public interface HuffmanAlgorithm {
/**
* 编码字符串。
* @param str 指定的需要编码的字符串
* @return 编码结果
*/
public EncodeResult encode(String str);
/**
* 根据编码结果返回原来的字符串。
* @param decodeResult 原来字符串的编码结果。
* @return 解码出来的字符串。
*/
public String decode(EncodeResult encodeResult);
}
[java] view plain copypackage com.liyuncong.algorithms.algorithms_huffman;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import com.liyuncong.application.commontools.FileTools;
public abstract class HuffmanAlgorithmAbstract implements HuffmanAlgorithm {
@Override
public EncodeResult encode(String str) {
ArrayList《Node》 letterList = toList(str);
Node rootNode = createTree(letterList);
Map《Character, String》 letterCode = getLetterCode(rootNode);
EncodeResult result = encode(letterCode, str);
return result;
}
/**
* 把一个字符串转化为节点列表
* @param letters
* @return
*/
private ArrayList《Node》 toList(String letters) {
ArrayList《Node》 letterList = new ArrayList《Node》();
Map《Character, Integer》 ci = new HashMap《Character, Integer》();
for (int i = 0; i 《 letters.length(); i++) {
Character character = letters.charAt(i);
if (!ci.keySet().contains(character)) {
ci.put(character, 1);
} else {
Integer oldValue = ci.get(character);
ci.put(character, oldValue + 1);
}
}
Set《Character》 keys = ci.keySet();
for (Character key : keys) {
Node node = new Node();
Data data = new Data();
data.setC(key);
data.setFrequency(ci.get(key));
node.setData(data);
letterList.add(node);
}
return letterList;
}
protected abstract Node createTree(ArrayList《Node》 letterList);
/**
* 编码字符串。
* @param letterCode 字符/编码对集合。
* @param letters 指定的需要编码的字符串。
* @return 编码结果
*/
private EncodeResult encode(Map《Character, String》 letterCode, String letters) {
StringBuilder encode = new StringBuilder();
for (int i = 0, length = letters.length(); i 《 length; i++) {
Character character = letters.charAt(i);
encode.append(letterCode.get(character));
}
EncodeResult result = new EncodeResult(encode.toString(), letterCode);
return result;
}
/**
* 获得所有字符编码对
*
* @param rootNode哈夫曼树的根节点
* @return 所有字符编码对
*/
private Map《Character, String》 getLetterCode(Node rootNode) {
Map《Character, String》 letterCode = new HashMap《Character, String》();
// 处理只有一个节点的情况
if (rootNode.getLeftChild() == null && rootNode.getRightChild() == null) {
letterCode.put(rootNode.getData().getC(), “1”);
return letterCode;
}
getLetterCode(rootNode, “”, letterCode);
return letterCode;
}
/**
* 先序遍历哈夫曼树,获得所有字符编码对。
*
* @param rooNode 哈夫曼树根结点
* @param suffix 编码前缀,也就是编码这个字符时,之前路径上的所有编码
* @param letterCode 用于保存字符编码结果
*/
private void getLetterCode(Node rooNode, String suffix,
Map《Character, String》 letterCode) {
if (rooNode != null) {
if (rooNode.getLeftChild() == null
&& rooNode.getRightChild() == null) {
Character character = rooNode.getData().getC();
letterCode.put(character, suffix);
}
getLetterCode(rooNode.getLeftChild(), suffix + “0”, letterCode);
getLetterCode(rooNode.getRightChild(), suffix + “1”, letterCode);
}
}
public String decode(EncodeResult decodeResult) {
// 解码得到的字符串
StringBuffer decodeStr = new StringBuffer();
// 获得解码器
Map《String, Character》 decodeMap = getDecoder(decodeResult
.getLetterCode());
// 解码器键集合
Set《String》 keys = decodeMap.keySet();
// 待解码的(被编码的)字符串
String encode = decodeResult.getEncode();
// 从最短的开始匹配之所以能够成功,是因为哈夫曼编码的唯一前缀性质
// 临时的可能的键值
String temp = “”;
// 改变temp值大小的游标
int i = 1;
while (encode.length() 》 0) {
temp = encode.substring(0, i);
if (keys.contains(temp)) {
Character character = decodeMap.get(temp);
decodeStr.append(character);
encode = encode.substring(i);
i = 1;
} else {
i++;
}
}
return decodeStr.toString();
}
/**
* 获得解码器,也就是通过字母/编码对得到编码/字符对。
*
* @param letterCode
* @return
*/
private Map《String, Character》 getDecoder(Map《Character, String》 letterCode) {
Map《String, Character》 decodeMap = new HashMap《String, Character》();
Set《Character》 keys = letterCode.keySet();
for (Character key : keys) {
String value = letterCode.get(key);
decodeMap.put(value, key);
}
return decodeMap;
}
}
[java] view plain copypackage com.liyuncong.algorithms.algorithms_huffman;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
* 算法实现参考《多媒体技术教程》
* @author yuncong
*
*/
public class HuffmanAlgorithmImpl1 extends HuffmanAlgorithmAbstract {
/*
* 创建哈夫曼树; 丢失了letterList中的数据,深拷贝letterList是需要完善的地方
*/
@Override
protected Node createTree(ArrayList《Node》 letterList) {
init(letterList);
while (letterList.size() != 1) {
int size = letterList.size();
// 小的节点放在右边(眼睛看到的左边)
Node nodeLeft = letterList.get(size - 1);
Node nodeRight = letterList.get(size - 2);
Node nodeParent = new Node();
nodeParent.setLeftChild(nodeLeft);
nodeParent.setRightChild(nodeRight);
Data data = new Data();
data.setFrequency(nodeRight.getData().getFrequency()
+ nodeLeft.getData().getFrequency());
nodeParent.setData(data);
letterList.set(size - 2, nodeParent);
letterList.remove(size - 1);
sort(letterList);
}
Node rootNode = letterList.get(0);
return rootNode;
}
/**
* 初始化 让节点列表有序
*/
private void init(ArrayList《Node》 letterList) {
sort(letterList);
}
/**
* 冒泡排序,把小的放在最后
*/
private void sort(ArrayList《Node》 letterList) {
int size = letterList.size();
// 处理只有一个元素的情况,也就是说,不需要排序
if (size == 1) {
return;
}
for (int i = 0; i 《 size; i++) {
for (int j = 0; j 《 size - 1 - i; j++) {
if (letterList.get(j).getData().getFrequency() 《 letterList
.get(j + 1).getData().getFrequency()) {
Node tempNode = letterList.get(j);
letterList.set(j, letterList.get(j + 1));
letterList.set(j + 1, tempNode);
}
}
}
}
}
[java] view plain copypackage com.liyuncong.algorithms.algorithms_huffman;
import static org.junit.Assert.*;
import org.junit.Test;
public class HuffmanAlgorithmImpl1Test {
@Test
public void testEncodeString() {
HuffmanAlgorithmImpl1 huffmanImpl1 = new HuffmanAlgorithmImpl1();
EncodeResult result = huffmanImpl1.encode(“abcdda”);
System.out.println(result.getEncode());
}
@Test
public void testDecode() {
HuffmanAlgorithmImpl1 huffmanImpl1 = new HuffmanAlgorithmImpl1();
EncodeResult result = huffmanImpl1.encode(“abcdda”);
String decode = huffmanImpl1.decode(result);
System.out.println(decode);
}
}
评论
查看更多