一.Huffman樹
定義: 給定n個權值作為n個葉子結點,構造一棵二叉樹,若該樹的帶權路徑達到最小,這樣的二叉樹稱為最優二叉樹,也稱為霍夫曼樹(Huffman樹).
特點: Huffman樹是帶權路徑長度最短的樹,權值較大的節點離根節點較近
權值 = 當前節點的值 * 層數,wpl最小的值,就是Huffman樹
創建步驟 舉例 {13,7,8,3,29,6,1}
1.從小到大進行排序,將每一個數據視為一個節點,每一個節點都可視為一個二叉樹
2.取出根節點權值兩個最小的二叉樹
3.組成一個新的二叉樹,新的二叉樹根節點的權值是前面兩顆二叉樹節點權值之和
4.再將這顆二叉樹以根節點的權值大小進行再排序,不斷重復1,2,3,4步,直到所有的數據都被處理,就得到一個Huffman樹
class Node implements Comparable<Node> { // 實現Comparable接口,可以使用Collections工具類進行排序 public int value; public Node left; public Node right; public Node(int value) { this.value = value; } public Node() { } /*用於測試Huffman樹是否正確*/ public void preOrder(){ System.out.println(this); if (this.left != null){ this.left.preOrder(); } if (this.right != null){ this.right.preOrder(); } } @Override public String toString() { return "Node{" + "value=" + value + '}'; } @Override public int compareTo(Node o) { // 從小到大進行排序 return this.value - o.value; } }
/** * 創建霍夫曼樹 * @param array 原數組 * @return 創建好Huffman樹的root節點 */ public static Node createHuffmanTree(int[] array){ if (array.length == 0 || array == null){ System.out.println("數組為空,無法創建"); return null; } /*遍歷數組中的每一個元素,構造成Node,放入到List中*/ List<Node> nodes = new ArrayList<>(); for (int item : array) { nodes.add(new Node(item)); } while (nodes.size() > 1){ /*只要List中有元素,就一直進行權值計算*/ /*對Node進行排序*/ Collections.sort(nodes); /*取出根節點兩個權值最小的二叉樹*/ Node leftNode = nodes.get(0); Node rightNode = nodes.get(1); /*構建一個新的二叉樹*/ Node parent = new Node(leftNode.value + rightNode.value); parent.left = leftNode; parent.right = rightNode; /*從List中刪除使用過的節點*/ nodes.remove(leftNode); nodes.remove(rightNode); /*將新的節點加入到List中*/ nodes.add(parent); } /*返回Huffman樹的root節點*/ return nodes.get(0); } }
測試,如果生成的Huffman樹是正確的,那么前序遍歷的結果也是正確的
public static void main(String[] args) { int[] array = {13,7,8,3,29,6,1}; preOrder(createHuffmanTree(array)); } public static void preOrder(Node root){ if (root != null){ root.preOrder(); }else { System.out.println("該樹為空,不能遍歷"); return; } }
二.Huffman編碼
定義: Huffman編碼是一種通信的編碼,是在電通信領域的基本編碼之一
作用: Huffman編碼廣泛的應用於數據文件的壓縮,而且它是前綴編碼,可以有效的節省傳輸的帶寬
編碼的步驟: 舉例 String content = 'i like like like java do you like a java oh oh oh';
1.生成節點
/*定義節點,data用於存放數據,weight用於存放權值*/ class HuffmanNode implements Comparable<HuffmanNode>{ public Byte data; public int weight; public HuffmanNode left; public HuffmanNode right; public HuffmanNode(Byte data, int weight) { this.data = data; this.weight = weight; } public HuffmanNode() { } @Override public int compareTo(HuffmanNode o) { return this.weight - o.weight; } }
2.統計字符串中每一個字符出現的次數
/*統計字符串中每個字符出現的次數,放在List中進行返回*/ /*List存儲格式 [Node[date=97 ,weight = 5], Node[date=32,weight = 9]......]*/ public static List<HuffmanNode> getNodes(byte[] bytes){ if (bytes.length == 0 || bytes == null){ System.out.println("字符串為空,無法進行編碼"); return null; } List<HuffmanNode> nodes = new ArrayList<>(); Map<Byte,Integer> counts = new HashMap<>(); /*遍歷bytes ,統計每一個byte出現的次數*/ for (byte item : bytes) { Integer count = counts.get(item); if (count == null){ // Map中沒有這個字符,說明是第一次 counts.put(item,1); }else { counts.put(item,count+1); } } /*遍歷Map,將鍵值對轉換為Node對象進行存放到List中*/ for (Map.Entry<Byte,Integer> node:counts.entrySet()){ nodes.add(new HuffmanNode(node.getKey(),node.getValue())); } return nodes; }
3.根據List集合,創建Huffm樹
public static HuffmanNode createHuffmanTree(List<HuffmanNode> nodes){ if (nodes.size() == 0 || nodes == null){ System.out.println("生成的List為空,不能生成霍夫曼樹"); return null; } while (nodes.size() > 1){ Collections.sort(nodes); HuffmanNode leftNode = nodes.get(0); HuffmanNode rightNode = nodes.get(1); HuffmanNode parent = new HuffmanNode(null,leftNode.weight+rightNode.weight); parent.left = leftNode; parent.right = rightNode; nodes.remove(leftNode); nodes.remove(rightNode); nodes.add(parent); } return nodes.get(0); }
4.將傳入的Huffman樹進行Huffman編碼
/*將傳入所有節點的Node節點的Huffman編碼得到*/ /*node 傳入的節點*/ /*code 路徑,向左為0,向右為1*/ /*StringBuild 用於拼接路徑,生成編碼*/ public static void getCode(HuffmanNode node,String code,StringBuilder stringBuilder){ StringBuilder stringBuilder2 = new StringBuilder(stringBuilder); /*將code加入到stringBuilder2 中*/ stringBuilder2.append(code); if (node!=null){ // 如果node是null,則不進行處理 if (node.data == null){ // 是非葉子節點 //向左遞歸 getCode(node.left,"0",stringBuilder2); //向右遞歸 getCode(node.right,"1",stringBuilder2); }else { /*此時表明是葉子結點,說明找到了一條路徑的最后*/ huffmanCode.put(node.data,stringBuilder2.toString()); } } } /*方便調用,重載此方法*/ public static Map<Byte,String> getCode(HuffmanNode root){ if (root == null){ System.out.println("沒有生成霍夫曼樹"); return null; }else { /*處理root左子樹*/ getCode(root.left,"0",stringBuilder); /*處理root右子樹*/ getCode(root.right,"1",stringBuilder); } return huffmanCode; }
5.使用Huffman編碼進行壓縮
/*將字符串對應的byte數組,通過生成的Huffman編碼表,返回一個Huffman編碼壓縮后的byte數組*/ /*bytes 原始字符串對應的字節數組*/ /*huffmanCode 生成的Huffman編碼表*/ /* 返回Huffman編碼處理后的字節數組*/ public static byte[] zip(byte[] bytes,Map<Byte,String> huffmanCode){ if (bytes.length == 0 || bytes == null){ System.out.println("字符串為空,無法進行編碼"); return null; } /*1.根據HuffmanCode獲取原始的字節數組的二進制的字符串*/ StringBuilder stb = new StringBuilder(); for (byte b : bytes) { stb.append(huffmanCode.get(b)); } /*2.創建存儲壓縮后的字節數組*/ int index = 0; //記錄第幾個byte int len = 0; // 確定霍夫曼編碼的長度 if (stb.length() % 8 == 0){ len = stb.length() / 8; }else { len = stb.length() / 8 + 1; } byte[] huffmanCodeBytes = new byte[len]; /*每8位對應一個byte,所以步長+8*/ for (int i = 0; i < stb.length();i+=8){ String strByte = null; if (i+8 > stb.length()){ // 不夠8位,直接從當前截取到末尾 strByte = stb.substring(i); }else { strByte = stb.substring(i,i+8); //否則按照每8位進行拼接 } /*將strByte 轉換成一個個byte,放在要返回的字節數組中,進行返回*/ huffmanCodeBytes[index++] = (byte)Integer.parseInt(strByte,2); } return huffmanCodeBytes; }
查看編碼的結果: 壓縮率 (49-21) / 49 = 57.14%
壓縮之后的字節數組是:
將上述Huffman編碼的步驟封裝
public static byte[] getZip(byte[] bytes){ List<HuffmanNode> nodes = getNodes(bytes); // 根據nodes創建赫夫曼樹 HuffmanNode root = createHuffmanTree(nodes); // 根據root節點生成霍夫曼編碼 huffmanCode = getCode(root); // 根據霍夫曼編碼,對數據進行壓縮,得到字節數組 byte[] huffmanCodeBytes = zip(bytes,huffmanCode); // System.out.println(Arrays.toString(huffmanCodeBytes)); return huffmanCodeBytes; }
三.使用Huffman進行解碼
1.將一個二進制的byte,裝換為二進制的字符串
/** * 將一個byte轉換成二進制的字符串 * @param flag 表示是否要進行補高位,如果是true則需要補高位,false則不需要補位,如果是最后一個字節不需要補高位 * @param b * @return 是該byte對應的二進制字符串(補碼返回) */ public static String byteToBitString(boolean flag,byte b){ int temp = b; /* 使用臨時變量,將byte轉換為int*/ if (flag){ /*如果是一個正數,需要進行補位操作*/ temp |= 256; /*按位與操作*/ } String str = Integer.toBinaryString(temp); /*返回temp對應的二進制補碼*/ if (flag){ // 如果有8位,則按照8位來返回,否則直接返回字符串 return str.substring(str.length()-8); }else { return str; } }
2.解碼操作
/** * * @param huffmanCode 對應霍夫曼編碼表 * @param huffmanBytes 霍夫曼編碼得到的字節數組 * @return 原先字符串對應的字節數組 */ public static byte[] decode(Map<Byte,String> huffmanCode,byte[] huffmanBytes){ /*1.先得到HuffmanBytes對應的二進制的字符串*/ StringBuilder sbt = new StringBuilder(); //將byte字節轉換為二進制字符串 for (int i = 0; i < huffmanBytes.length; i++) { byte b = huffmanBytes[i]; // 判斷是否是最后一個字節 boolean flag = (i == huffmanBytes.length-1); sbt.append(byteToBitString(!flag,b)); } /*2.把字符串按照指定的方式進行霍夫曼解碼*/ /*把Huffman碼表進行調換,因為是反向查詢*/ Map<String,Byte> map = new HashMap<>(); for (Map.Entry<Byte,String> entry:huffmanCode.entrySet()){ map.put(entry.getValue(),entry.getKey()); } /*3.創建集合,存放解碼后的byte*/ List<Byte> byteList = new ArrayList<>(); /*使用索引不停的掃描stb*/ for (int k = 0; k < sbt.length();){ int count = 1; /*小的計數器,用於判斷是否字符串是否在Huffman的碼標中*/ Byte b = null; /*用於存放編碼后的字節*/ boolean loop = true; while (loop){ /*k不動,讓count進行移動,指定匹配到一個字符*/ String key = sbt.substring(k,k+count); b = map.get(key); if (b == null){ //沒有匹配到 count++; }else { //匹配到就退出循環 loop = false; } } byteList.add(b); k += count; //k直接移動到count在進行下一次遍歷 } /*4.當for循環結束后,將list中存放的數據放入到byte數組中返回即可*/ byte[] decodeByteCodes = new byte[byteList.size()]; for (int j = 0; j < decodeByteCodes.length; j++) { decodeByteCodes[j] = byteList.get(j); } return decodeByteCodes; }
查看解碼后的結果:
完整代碼
package data.structer.tree; import java.util.*; public class HuffmanCodeDemo { static Map<Byte,String> huffmanCode = new HashMap<>(); static StringBuilder stringBuilder = new StringBuilder(); public static void main(String[] args) { String content = "i like like like java do you like a java oh oh oh"; //System.out.println("原始的長度是:"+content.length()); byte[] bytes = getZip(content.getBytes()); // System.out.println("Huffman編碼后的字符串長度是:"+bytes.length); System.out.println("解碼后的字符串是:"+new String(decode(huffmanCode,bytes))); } // 解碼 /** * * @param huffmanCode 對應霍夫曼編碼表 * @param huffmanBytes 霍夫曼編碼得到的字節數組 * @return */ public static byte[] decode(Map<Byte,String> huffmanCode,byte[] huffmanBytes){ StringBuilder sbt = new StringBuilder(); //將byte字節轉換為二進制字符串 for (int i = 0; i < huffmanBytes.length; i++) { byte b = huffmanBytes[i]; // 判斷是否是最后一個字節 boolean flag = (i == huffmanBytes.length-1); sbt.append(byteToBitString(!flag,b)); } //把字符串按照指定的方式進行霍夫曼解碼 Map<String,Byte> map = new HashMap<>(); for (Map.Entry<Byte,String> entry:huffmanCode.entrySet()){ map.put(entry.getValue(),entry.getKey()); } // 創建集合,存放byte List<Byte> byteList = new ArrayList<>(); for (int k = 0; k < sbt.length();){ int count = 1; Byte b = null; boolean loop = true; while (loop){ String key = sbt.substring(k,k+count); b = map.get(key); if (b == null){ //沒有匹配到 count++; }else { loop = false; } } byteList.add(b); k += count; } byte[] decodeByteCodes = new byte[byteList.size()]; for (int j = 0; j < decodeByteCodes.length; j++) { decodeByteCodes[j] = byteList.get(j); } return decodeByteCodes; } /** * 將一個byte轉換成二進制的字符串 * @param flag 表示是否要進行補高位,如果是true則需要補高位,false則不需要補位,如果是最后一個字節不需要補高位 * @param b * @return 是該byte對應的二進制字符串(補碼返回) */ public static String byteToBitString(boolean flag,byte b){ int temp = b; if (flag){ temp |= 256; } String str = Integer.toBinaryString(temp); if (flag){ return str.substring(str.length()-8); }else { return str; } } public static byte[] getZip(byte[] bytes){ List<HuffmanNode> nodes = getNodes(bytes); // 根據nodes創建赫夫曼樹 HuffmanNode root = createHuffmanTree(nodes); // 根據root節點生成霍夫曼編碼 huffmanCode = getCode(root); // 根據霍夫曼編碼,對數據進行壓縮,得到字節數組 byte[] huffmanCodeBytes = zip(bytes,huffmanCode); // System.out.println(Arrays.toString(huffmanCodeBytes)); return huffmanCodeBytes; } /** * 統計字符串中每個字符出現的次數,添加到List中進行返回 * @param bytes * @return */ public static List<HuffmanNode> getNodes(byte[] bytes){ if (bytes.length == 0 || bytes == null){ System.out.println("字符串為空,無法進行編碼"); return null; } List<HuffmanNode> nodes = new ArrayList<>(); Map<Byte,Integer> counts = new HashMap<>(); for (byte item : bytes) { Integer count = counts.get(item); if (count == null){ // 說明是第一次 counts.put(item,1); }else { counts.put(item,count+1); } } for (Map.Entry<Byte,Integer> node:counts.entrySet()){ nodes.add(new HuffmanNode(node.getKey(),node.getValue())); } return nodes; } /** * 使用霍夫曼編碼進行壓縮 * @param bytes * @param huffmanCode * @return */ public static byte[] zip(byte[] bytes,Map<Byte,String> huffmanCode){ if (bytes.length == 0 || bytes == null){ System.out.println("字符串為空,無法進行編碼"); return null; } StringBuilder stb = new StringBuilder(); for (byte b : bytes) { stb.append(huffmanCode.get(b)); } int index = 0; int len = 0; // 確定霍夫曼編碼的長度 if (stb.length() % 8 == 0){ len = stb.length() / 8; }else { len = stb.length() / 8 + 1; } byte[] huffmanCodeBytes = new byte[len]; for (int i = 0; i < stb.length();i+=8){ String strByte = null; if (i+8 > stb.length()){ // 不夠8位 strByte = stb.substring(i); }else { strByte = stb.substring(i,i+8); } huffmanCodeBytes[index] = (byte)Integer.parseInt(strByte,2); index++; } return huffmanCodeBytes; } public static void getCode(HuffmanNode node,String code,StringBuilder stringBuilder){ StringBuilder stringBuilder2 = new StringBuilder(stringBuilder); stringBuilder2.append(code); if (node!=null){ if (node.data == null){ // 是非葉子節點 //向左遞歸 getCode(node.left,"0",stringBuilder2); //向右遞歸 getCode(node.right,"1",stringBuilder2); }else { huffmanCode.put(node.data,stringBuilder2.toString()); } } } public static Map<Byte,String> getCode(HuffmanNode root){ if (root == null){ System.out.println("沒有生成霍夫曼樹"); return null; }else { getCode(root.left,"0",stringBuilder); getCode(root.right,"1",stringBuilder); } return huffmanCode; } /** * 生成霍夫曼樹 * @param nodes * @return */ public static HuffmanNode createHuffmanTree(List<HuffmanNode> nodes){ if (nodes.size() == 0 || nodes == null){ System.out.println("生成的List為空,不能生成霍夫曼樹"); return null; } while (nodes.size() > 1){ Collections.sort(nodes); HuffmanNode leftNode = nodes.get(0); HuffmanNode rightNode = nodes.get(1); HuffmanNode parent = new HuffmanNode(null,leftNode.weight+rightNode.weight); parent.left = leftNode; parent.right = rightNode; nodes.remove(leftNode); nodes.remove(rightNode); nodes.add(parent); } return nodes.get(0); } } class HuffmanNode implements Comparable<HuffmanNode>{ public Byte data; public int weight; public HuffmanNode left; public HuffmanNode right; public HuffmanNode(Byte data, int weight) { this.data = data; this.weight = weight; } public HuffmanNode() { } @Override public String toString() { return "HuffmanNode{" + "data=" + data + ", weight=" + weight + '}'; } @Override public int compareTo(HuffmanNode o) { return this.weight - o.weight; } }