前言php
哈夫曼编码(Huffman coding)是一种可变长的前缀码。哈夫曼编码使用的算法是David A. Huffman仍是在MIT的学生时提出的,而且在1952年发表了名为《A Method for the Construction of Minimum-Redundancy Codes》的文章。编码这种编码的过程叫作哈夫曼编码,它是一种广泛的熵编码技术,包括用于无损数据压缩领域。因为哈夫曼编码的运用普遍,本文将简要介绍:html
哈夫曼编码的编码(不包含解码)原理java
代码(java)实现过程node
1、哈弗曼编码原理python
哈夫曼编码使用一种特别的方法为信号源中的每一个符号设定二进制码。出现频率更大的符号将得到更短的比特,出现频率更小的符号将被分配更长的比特,以此来提升数据压缩率,提升传输效率。具体编码步骤主要为,c++
一、统计:算法
在开始编码时,一般都须要对信号源,也就是本文的一段文字,进行处理,计算出每一个符号出现的频率,获得信号源的基本状况。接下来就是对统计信息进行处理了编程
二、构造优先对列:小程序
把获得的符号添加到优先队列中,此优先队列的进出逻辑是频率低的先出,所以在设计优先队列时须要如此设计,若是不熟悉优先队列,请阅读相关书籍,在此不作过多概述。获得包含全部字符的优先队列后,就是处理优先队列中的数据了。c#
三、构造哈夫曼树:
哈夫曼树是带权值得二叉树,咱们使用的哈夫曼树的权值天然就是符号的频率了,咱们构建哈夫曼树是自底向上的,先构建叶子节点,而后逐步向上,最终完成整颗树。先把队列中的一个符号出列,也就是最小频率的符号,,而后再出列一个符号。这两个符号将做为哈夫曼树的节点,并且这两个节点将做为新节点,也就是它们父节点,的左右孩子节点。新节点的频率,即权值,为孩子节点的和。把这个新节点添加到队列中(队列会从新根据权值排序)。重复上面的步骤,两个符号出列,构造新的父节点,入列……直到队列最后只剩下一个节点,这个节点也就是哈夫曼树的根节点了。
四、为哈弗曼树编码:
哈夫曼树的来自信号源的符号都是叶子节点,须要知道下。树的根节点分配比特0,左子树分配0,右字数分配1。而后就能够获得符号的码值了。
2、示例(转自这的)
假如我有A,B,C,D,E五个字符,出现的频率(即权值)分别为5,4,3,2,1,那么咱们第一步先取两个最小权值做为左右子树构造一个新树,即取1,2构成新树,其结点为1+2=3,如图:
虚线为新生成的结点,第二步再把新生成的权值为3的结点放到剩下的集合中,因此集合变成{5,4,3,3},再根据第二步,取最小的两个权值构成新树,如图:
再依次创建哈夫曼树,以下图:
其中各个权值替换对应的字符即为下图:
因此各字符对应的编码为:A->11,B->10,C->00,D->011,E->010
以下图也能够加深你们的理解(图片来自于wikipedia)
下面的这个图片是互动示例的截图,来自http://www.hightechdreams.com/weaver.php?topic=huffmancoding,输入符号就会动态展现出树的构建,有兴趣的朋友能够去看看
3、 代码实现
先是设计一个用于节点比较的接口
1 package com.huffman; 2
3 //用来实现节点比较的接口
4 public interface Compare<T> { 5 //小于
6 boolean less(T t); 7 }
而后写一个哈夫曼树节点的类,主要是用于储存符号信息的,实现上面的接口,get、set方法已经省略了
1 package com.huffman; 2
3 public class Node implements Compare<Node>{ 4
5 //节点的优先级
6 private int nice; 7
8 //字符出现的频率(次数)
9 private int count; 10
11 //文本中出现的字符串
12 private String str; 13
14 //左孩子
15 private Node leftNode; 16
17 //右孩子
18 private Node rightNode; 19
20 //对应的二进制编码
21 private String code; 22
23 public Node(){ 24 } 25
26 public Node(int nice, String str, int count){ 27 this.nice = nice; 28 this.str = str; 29 this.count = count; 30 } 31
32 //把节点(权值,频率)相加,返回新的节点
33 public Node add(Node node){ 34 Node n = new Node(); 35 n.nice = this.nice + node.nice; 36 n.count = this.count + node.count; 37 return n; 38 } 39
40 public boolean less(Node node) { 41 return this.nice < node.nice; 42 } 43
44
45 public String toString(){ 46 return String.valueOf(this.nice); 47 }
设计一个优先队列
1 package com.huffman; 2
3 import java.util.List; 4
5 public class PriorityQueue<T extends Compare<T>> { 6
7 public List<T> list = null; 8
9 public PriorityQueue(List<T> list){ 10 this.list = list; 11 } 12
13 public boolean empty(){ 14 return list.size() == 0; 15 } 16
17 public int size(){ 18 return list.size(); 19 } 20
21 //移除指定索引的元素
22 public void remove(int number){ 23 int index = list.indexOf(number); 24 if (-1 == index){ 25 System.out.println("data do not exist!"); 26 return; 27 } 28 list.remove(index); 29 //每次删除一个元素都须要从新构建队列
30 buildHeap(); 31 } 32
33 //弹出队首元素,并把这个元素返回
34 public T pop(){ 35 //因为优先队列的特殊性,第一个元素(索引为0)是不使用的
36 if (list.size() == 1){ 37 return null; 38 } 39 T first = list.get(1); 40 list.remove(1); 41 buildHeap(); 42 return first; 43
44 } 45
46 //加入一个元素到队列中
47 public void add(T object){ 48 list.add(object); 49 buildHeap(); 50 } 51
52 //维护最小堆
53 private List<T> minHeap(List<T> list, int position, int heapSize){ 54 int left = 2 * position; //获得左孩子的位置
55 int right = left + 1; //获得右孩子的位置
56 int min = position; //min储存最小值的位置,暂时假定当前节点是最小节点 57 //寻找最小节点
58 if (left < heapSize && list.get(left).less(list.get(min))){ 59 min = left; 60 } 61 if (right < heapSize && list.get(right).less(list.get(min))){ 62 min = right; 63 } 64
65 if (min != position){ 66 exchange(list, min, position); //交换当前节点与最小节点的位置
67 minHeap(list, min, heapSize); //从新维护最小堆
68 } 69 return list; 70 } 71
72 //交换元素位置
73 private List<T> exchange(List<T> list, int former, int latter){ 74 T temp = list.get(former); 75 list.set(former, list.get(latter)); 76 list.set(latter, temp); 77 return list; 78 } 79
80 //构建最小堆
81 public List<T> buildHeap(){ 82 int i; 83 for (i = list.size() - 1; i > 0; i--){ 84 minHeap(list, i, list.size()); 85 } 86 return list; 87 } 88
89 }
最后是用一个类构建哈夫曼树
1 package com.huffman; 2
3 import java.util.ArrayList; 4 import java.util.HashMap; 5 import java.util.List; 6 import java.util.Map; 7
8 public class Huffman { 9 //优先队列
10 private PriorityQueue<Node> priorQueue; 11
12 //须要处理的文本
13 private String[] text; 14
15 //文本处理后的统计信息
16 private Map<String, Integer> statistics; 17
18 //huffman编码最终结果
19 private Map<String, String> result; 20
21 public Huffman(String text) { 22 this.text = text.split("\\W+"); 23 init(); 24 } 25
26 private void init() { 27 statistics = new HashMap<String, Integer>(); 28 result = new HashMap<String, String>(); 29 } 30
31 //获取字符串统计信息,获得如"abc":3,"love":12等形式map
32 private void getStatistics() { 33 int count; 34 for (String c : text) { 35 if (statistics.containsKey(c)) { 36 count = statistics.get(c); 37 count++; 38 statistics.put(c, count); 39 } else { 40 statistics.put(c, 1); 41 } 42 } 43 } 44
45 //构建huffman树
46 private void buildTree() { 47 List<Node> list = new ArrayList<Node>(); 48 list.add(new Node(2222, "123", 2222)); //由于优先队列的特殊性,添加这个不使用的节点 49 //把字符串信息储存到节点中,并把节点添加到arrayList中
50 for (String key : statistics.keySet()) { 51 Node leaf = new Node(statistics.get(key), key, statistics.get(key)); 52 list.add(leaf); 53 } 54 Node tree = null; //用于储存指向huffman树根节点的指针
55 priorQueue = new PriorityQueue<Node>(list); //以上面节点为元素,构建优先队列
56 priorQueue.buildHeap(); 57 Node first = null; 58 Node second = null; 59 Node newNode = null; 60 do { 61 first = priorQueue.pop(); //取出队首的元素,做为左孩子节点
62 second = priorQueue.pop(); //取出队首的元素,做为右孩子节点
63 newNode = first.add(second); //构建父节点
64 priorQueue.add(newNode); //把父节点添加到队列中
65 newNode.setLeftNode(first); 66 newNode.setRightNode(second); 67 tree = newNode; //把tree指向新节点
68 } while (priorQueue.size() > 2); //因为队列中有一个元素是不使用的,因此队列只剩最后一个元素(实际就是队列只有2个元素)时就该退出循环了。 69 //最后剩下一个节点是根节点,把它取出来,并拼装树
70 Node root = priorQueue.pop(); 71 root.setCode("0"); 72 root.setLeftNode(tree.getLeftNode()); 73 root.setRightNode(tree.getRightNode()); 74 tree = null; 75 setCodeNum(root); //遍历树,为每一个节点编码
76 System.out.println("----------------------------"); 77 System.out.println(result); 78 } 79
80 public void buildHuffman(){ 81 getStatistics(); //收集统计信息
82 buildTree(); 83 for (String c : statistics.keySet()) { 84 System.out.println(c + ":" + statistics.get(c)); 85 } 86 } 87
88 //编码
89 private void setCodeNum(Node tree){ 90 if(null == tree){ 91 return; 92 } 93 Node left = tree.getLeftNode(); 94 Node right = tree.getRightNode(); 95 if (left !=null){ 96 left.setCode("0" + tree.getCode()); //左孩子的码为0
97 if (statistics.containsKey(left.getStr())){ 98 //若是节点在统计表里,把它添加到result中
99 result.put(left.getStr(), left.getCode()); 100 } 101 } 102 if (right != null){ 103 right.setCode("1" + tree.getCode()); //右孩子的码为1
104 if (statistics.containsKey(right.getStr())){ 105 //若是节点在统计表里,把它添加到result中
106
107 result.put(right.getStr(), right.getCode()); 108 } 109 } 110 setCodeNum(left); //递归
111 setCodeNum(right); //递归
112
113 } 114
115 }
注意:代码实现的提供主要是为了概要介绍哈夫曼编码的实现过程,部分代码逻辑并为深思,效率也略低,请你们只作参考,并多参考其余人的代码。
参考文章:
http://people.cs.pitt.edu/~kirk/cs1501/animations/Huffman.html
http://www.hightechdreams.com/weaver.php?topic=huffmancoding
延伸阅读:
http://www.hightechdreams.com/weaver.php?topic=huffmancoding
这个网站是由一群用它们的话说是为“对编程狂热或者有兴趣的人创建的”,提供了不少算法有关的互动的例子,以及一些小程序
http://www.siggraph.org/education/materials/HyperGraph/video/mpeg/mpegfaq/huffman_tutorial.html
这是一个创建哈夫曼树的简明教程
http://rosettacode.org/wiki/Huffman_codes
提供了多语言的哈夫曼树实现,包括c, c++, java, c#, python, lisp等。有兴趣的朋友能够参考下,看看国外的码农是怎么玩哈夫曼树的。