詳解Huffman編碼算法之Java實現(xiàn)
Huffman編碼介紹
Huffman編碼處理的是字符以及字符對應(yīng)的二進制的編碼配對問題,分為編碼和解碼,目的是壓縮字符對應(yīng)的二進制數(shù)據(jù)長度。我們知道字符存貯和傳輸?shù)臅r候都是二進制的(計算機只認識0/1),那么就有字符與二進制之間的mapping關(guān)系。字符屬于字符集(Charset), 字符需要通過編碼(encode)為二進制進行存貯和傳輸,顯示的時候需要解碼(decode)回字符,字符集與編碼方法是一對多關(guān)系(Unicode可以用UTF-8,UTF-16等編碼)。理解了字符集,編碼以及解碼,滿天飛的亂碼問題也就游刃而解了。以英文字母小寫a為例, ASCII編碼中,十進制為97,二進制為01100001。ASCII的每一個字符都用8個Bit(1Byte)編碼,假如有1000個字符要傳輸,那么就要傳輸8000個Bit。問題來了,英文中字母e的使用頻率為12.702%,而z為0.074%,前者是后者的100多倍,但是確使用相同位數(shù)的二進制。可以做得更好,方法就是可變長度編碼,指導(dǎo)原則就是頻率高的用較短的位數(shù)編碼,頻率低的用較長位數(shù)編碼。Huffman編碼算法就是處理這樣的問題。
Huffman編碼Java實現(xiàn)
Huffman編碼算法主要用到的數(shù)據(jù)結(jié)構(gòu)是完全二叉樹(full binary tree)和優(yōu)先級隊列。后者用的是Java.util.PriorityQueue,前者自己實現(xiàn)(都為內(nèi)部類),代碼如下:
static class Tree { private Node root; public Node getRoot() { return root; } public void setRoot(Node root) { this.root = root; } } static class Node implements Comparable<Node> { private String chars = ""; private int frequence = 0; private Node parent; private Node leftNode; private Node rightNode; @Override public int compareTo(Node n) { return frequence - n.frequence; } public boolean isLeaf() { return chars.length() == 1; } public boolean isRoot() { return parent == null; } public boolean isLeftChild() { return parent != null && this == parent.leftNode; } public int getFrequence() { return frequence; } public void setFrequence(int frequence) { this.frequence = frequence; } public String getChars() { return chars; } public void setChars(String chars) { this.chars = chars; } public Node getParent() { return parent; } public void setParent(Node parent) { this.parent = parent; } public Node getLeftNode() { return leftNode; } public void setLeftNode(Node leftNode) { this.leftNode = leftNode; } public Node getRightNode() { return rightNode; } public void setRightNode(Node rightNode) { this.rightNode = rightNode; } }
統(tǒng)計數(shù)據(jù)
既然要按頻率來安排編碼表,那么首先當然得獲得頻率的統(tǒng)計信息。我實現(xiàn)了一個方法處理這樣的問題。如果已經(jīng)有統(tǒng)計信息,那么轉(zhuǎn)為Map<Character,Integer>即可。如果你得到的信息是百分比,乘以100或1000,或10000??偸强梢赞D(zhuǎn)為整數(shù)。比如12.702%乘以1000為12702,Huffman編碼只關(guān)心大小問題。統(tǒng)計方法實現(xiàn)如下:
public static Map<Character, Integer> statistics(char[] charArray) { Map<Character, Integer> map = new HashMap<Character, Integer>(); for (char c : charArray) { Character character = new Character(c); if (map.containsKey(character)) { map.put(character, map.get(character) + 1); } else { map.put(character, 1); } } return map; }
構(gòu)建樹
構(gòu)建樹是Huffman編碼算法的核心步驟。思想是把所有的字符掛到一顆完全二叉樹的葉子節(jié)點,任何一個非頁子節(jié)點的左節(jié)點出現(xiàn)頻率不大于右節(jié)點。算法為把統(tǒng)計信息轉(zhuǎn)為Node存放到一個優(yōu)先級隊列里面,每一次從隊列里面彈出兩個最小頻率的節(jié)點,構(gòu)建一個新的父Node(非葉子節(jié)點), 字符內(nèi)容剛彈出來的兩個節(jié)點字符內(nèi)容之和,頻率也是它們的和,最開始的彈出來的作為左子節(jié)點,后面一個作為右子節(jié)點,并且把剛構(gòu)建的父節(jié)點放到隊列里面。重復(fù)以上的動作N-1次,N為不同字符的個數(shù)(每一次隊列里面?zhèn)€數(shù)減1)。結(jié)束以上步驟,隊列里面剩一個節(jié)點,彈出作為樹的根節(jié)點。代碼如下:
private static Tree buildTree(Map<Character, Integer> statistics, List<Node> leafs) { Character[] keys = statistics.keySet().toArray(new Character[0]); PriorityQueue<Node> priorityQueue = new PriorityQueue<Node>(); for (Character character : keys) { Node node = new Node(); node.chars = character.toString(); node.frequence = statistics.get(character); priorityQueue.add(node); leafs.add(node); } int size = priorityQueue.size(); for (int i = 1; i <= size - 1; i++) { Node node1 = priorityQueue.poll(); Node node2 = priorityQueue.poll(); Node sumNode = new Node(); sumNode.chars = node1.chars + node2.chars; sumNode.frequence = node1.frequence + node2.frequence; sumNode.leftNode = node1; sumNode.rightNode = node2; node1.parent = sumNode; node2.parent = sumNode; priorityQueue.add(sumNode); } Tree tree = new Tree(); tree.root = priorityQueue.poll(); return tree; }
編碼
某個字符對應(yīng)的編碼為,從該字符所在的葉子節(jié)點向上搜索,如果該字符節(jié)點是父節(jié)點的左節(jié)點,編碼字符之前加0,反之如果是右節(jié)點,加1,直到根節(jié)點。只要獲取了字符和二進制碼之間的mapping關(guān)系,編碼就非常簡單。代碼如下:
public static String encode(String originalStr, Map<Character, Integer> statistics) { if (originalStr == null || originalStr.equals("")) { return ""; } char[] charArray = originalStr.toCharArray(); List<Node> leafNodes = new ArrayList<Node>(); buildTree(statistics, leafNodes); Map<Character, String> encodInfo = buildEncodingInfo(leafNodes); StringBuffer buffer = new StringBuffer(); for (char c : charArray) { Character character = new Character(c); buffer.append(encodInfo.get(character)); } return buffer.toString(); } private static Map<Character, String> buildEncodingInfo(List<Node> leafNodes) { Map<Character, String> codewords = new HashMap<Character, String>(); for (Node leafNode : leafNodes) { Character character = new Character(leafNode.getChars().charAt(0)); String codeword = ""; Node currentNode = leafNode; do { if (currentNode.isLeftChild()) { codeword = "0" + codeword; } else { codeword = "1" + codeword; } currentNode = currentNode.parent; } while (currentNode.parent != null); codewords.put(character, codeword); } return codewords; }
解碼
因為Huffman編碼算法能夠保證任何的二進制碼都不會是另外一個碼的前綴,解碼非常簡單,依次取出二進制的每一位,從樹根向下搜索,1向右,0向左,到了葉子節(jié)點(命中),退回根節(jié)點繼續(xù)重復(fù)以上動作。代碼如下:
public static String decode(String binaryStr, Map<Character, Integer> statistics) { if (binaryStr == null || binaryStr.equals("")) { return ""; } char[] binaryCharArray = binaryStr.toCharArray(); LinkedList<Character> binaryList = new LinkedList<Character>(); int size = binaryCharArray.length; for (int i = 0; i < size; i++) { binaryList.addLast(new Character(binaryCharArray[i])); } List<Node> leafNodes = new ArrayList<Node>(); Tree tree = buildTree(statistics, leafNodes); StringBuffer buffer = new StringBuffer(); while (binaryList.size() > 0) { Node node = tree.root; do { Character c = binaryList.removeFirst(); if (c.charValue() == '0') { node = node.leftNode; } else { node = node.rightNode; } } while (!node.isLeaf()); buffer.append(node.chars); } return buffer.toString(); }
測試以及比較
以下測試Huffman編碼的正確性(先編碼,后解碼,包括中文),以及Huffman編碼與常見的字符編碼的二進制字符串比較。代碼如下:
public static void main(String[] args) { String oriStr = "Huffman codes compress data very effectively: savings of 20% to 90% are typical, " + "depending on the characteristics of the data being compressed. 中華崛起"; Map<Character, Integer> statistics = statistics(oriStr.toCharArray()); String encodedBinariStr = encode(oriStr, statistics); String decodedStr = decode(encodedBinariStr, statistics); System.out.println("Original sstring: " + oriStr); System.out.println("Huffman encoed binary string: " + encodedBinariStr); System.out.println("decoded string from binariy string: " + decodedStr); System.out.println("binary string of UTF-8: " + getStringOfByte(oriStr, Charset.forName("UTF-8"))); System.out.println("binary string of UTF-16: " + getStringOfByte(oriStr, Charset.forName("UTF-16"))); System.out.println("binary string of US-ASCII: " + getStringOfByte(oriStr, Charset.forName("US-ASCII"))); System.out.println("binary string of GB2312: " + getStringOfByte(oriStr, Charset.forName("GB2312"))); } public static String getStringOfByte(String str, Charset charset) { if (str == null || str.equals("")) { return ""; } byte[] byteArray = str.getBytes(charset); int size = byteArray.length; StringBuffer buffer = new StringBuffer(); for (int i = 0; i < size; i++) { byte temp = byteArray[i]; buffer.append(getStringOfByte(temp)); } return buffer.toString(); } public static String getStringOfByte(byte b) { StringBuffer buffer = new StringBuffer(); for (int i = 7; i >= 0; i--) { byte temp = (byte) ((b >> i) & 0x1); buffer.append(String.valueOf(temp)); } return buffer.toString(); }
以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
jeefast和Mybatis實現(xiàn)三級聯(lián)動的示例代碼
這篇文章主要介紹了jeefast和Mybatis實現(xiàn)三級聯(lián)動的示例代碼,代碼簡單易懂,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-10-10SpringBoot+MyBatis-Plus實現(xiàn)分頁功能
在SpringBoot項目中,結(jié)合MyBatis-Plus(簡稱MP)可以非常方便地實現(xiàn)分頁功能,MP為開發(fā)者提供了分頁插件PaginationInterceptor,只需簡單配置即可使用,本文給大家介紹了SpringBoot+MyBatis-Plus實現(xiàn)分頁功能,文中通過代碼示例給大家介紹的非常詳細,需要的朋友可以參考下2024-01-01Spring Security的持久化用戶和授權(quán)實現(xiàn)方式
文章介紹了如何使用JdbcUserDetailsManager實現(xiàn)數(shù)據(jù)庫讀取用戶,并展示了如何配置SpringSecurity進行授權(quán)管理,通過創(chuàng)建數(shù)據(jù)庫表、配置數(shù)據(jù)庫連接和修改SecurityConfig,實現(xiàn)了用戶權(quán)限的控制2025-02-02webservice實現(xiàn)springboot項目間接口調(diào)用與對象傳遞示例
本文主要介紹了webservice實現(xiàn)springboot項目間接口調(diào)用與對象傳遞示例,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2022-07-07分析Java中ArrayList與LinkedList列表結(jié)構(gòu)的源碼
這篇文章主要介紹了Java中ArrayList與LinkedList列表結(jié)構(gòu)的源碼,文章最后對LinkedList和ArrayList以及Vector的特性有一個對比總結(jié),需要的朋友可以參考下2016-05-05