前言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等。有興趣的朋友能夠參考下,看看國外的碼農是怎麼玩哈夫曼樹的。