数据结构与算法(赫夫曼树,赫夫曼编码)
赫夫曼樹
?
基本介紹:
(1)給定n個權值作為n給葉子節點,構造一棵二叉樹,若該樹的帶權路徑長度(wpl)達到最小,稱這樣的二叉樹為最優二叉樹,也稱哈夫曼樹(HuffmanTree),還有的樹翻譯為霍夫曼樹.
(2)赫夫曼樹是帶權路徑長度最短的樹,權值較大的節點離根較近.
赫夫曼樹幾個重要概念和舉例說明:
(1)路徑和路徑長度,在一顆樹中,從一個節點往下可以達到的孩子或孫子節點之間的通路,稱為路徑.通路中分支的數目稱為路徑長度,若規定根節點的層數為1,則從根節點到第L層的節點路徑長度為L-1
(2)節點的權及帶權路徑長度:若將樹中節點賦給一個有著某種含義的數值,則這個數值稱為該節點的權.節點的帶權路徑長度為:從根節點到該節點之前的路徑長度與該節點的權的乘積.
(3)樹的帶權路徑長度:樹的帶權路徑長度規定為所有葉子節點的帶權路徑長度之和,記為WPL(weighted path length),權值越大的節點離根節點越近的二叉樹才是最優二叉樹.
(4)WPL最小的介紹赫夫曼樹
赫夫曼樹創建思路圖解
給你一個數列{13,7,8,3,29,6,1},要求轉成一顆赫夫曼樹
思路分析
構建赫夫曼樹的步驟:
(1)從小到大進行排序,將每一個數據,每個數據都是一個節點,每個節點可以看成是一顆最簡單的二叉樹
(2)取出根節點權值最小的兩顆二叉樹
(3)組成一顆新的二叉樹,該新的二叉樹的根節點的權值是前面兩顆二叉樹根節點權值的和
(4)再將這顆新的二叉樹,以根節點的權值大小再次排序,不斷重復1-2-3-4的步驟,直到數列中,所有的數據都被處理,就得到一顆赫夫曼樹
(5)圖解
?代碼實現
package huffmantree;import java.util.ArrayList; import java.util.Collections; import java.util.List;/*** 赫夫曼樹*/ public class HuffmanTree {public static void main(String[] args) {int arr[] = {13, 7, 8, 3, 29, 6, 1};Node root = createHuffmanTree(arr);//測試preOrder(root);}//編寫一個前序遍歷方法public static void preOrder(Node root){if (root != null){root.preOrder();}else {System.out.println("該赫夫曼是空樹");}}//創建赫夫曼樹的方法/**** @param arr 需要創建成赫夫曼樹的數組* @return 創建和后的赫夫曼樹的root節點*/public static Node createHuffmanTree(int[] arr) {//第一步為了方便操作//1.遍歷arr數組//2.將arr的每個元素構成一個Node//3.將Node 放入到ArrayList中List<Node> nodes = new ArrayList<>();for (int value : arr) {nodes.add(new Node(value));}//我們處理的過程是循環的過程while (nodes.size() > 1) {//排序從小到大Collections.sort(nodes);//取出根節點權值最小的兩顆 二 叉樹//(1)取出權值最小的節點(二叉樹)Node leftNode = nodes.get(0);//(2)取出權值第二小的節點(二叉樹)Node rightNode = nodes.get(1);//(3)構建一顆新的二叉樹Node parent = new Node(leftNode.value + rightNode.value);parent.left = leftNode;parent.right = rightNode;//(4)從ArrayList刪除處理過的二叉樹nodes.remove(leftNode);nodes.remove(rightNode);//(5)將parent加入到nodesnodes.add(parent);// Collections.sort(nodes);}//返回赫夫曼樹的root節點return nodes.get(0);} }//創建節點類 //為了讓Node 對象持續排序Collections集合排序 //讓Node 實現Comparable接口 class Node implements Comparable<Node>{int value;//節點權值Node left; //指向左子節點Node right;//指向右字節點//寫一個前序遍歷public void preOrder(){System.out.println(this);if (this.left != null){this.left.preOrder();}if (this.right != null){this.right.preOrder();}}public Node(int value){this.value = value;}@Overridepublic String toString() {return "Node{" +"value=" + value +'}';}@Overridepublic int compareTo(Node o) {//從小到大排序return this.value - o.value;} }赫夫曼編碼
基本介紹
(1)赫夫曼編碼也翻譯為哈夫曼編碼(Huffman Coding),又稱霍夫曼編碼,是一種編碼方式,屬于一種程序算法
(2)赫夫曼編碼是赫夫曼樹在電訊通信中的經典應用之一
(3)赫夫曼編碼廣泛地用于數據文件壓縮.其壓縮率通常在20%~90%之間
(4)赫夫曼編碼是可變字長編碼(VLC)的一種.Huffman于1952年提出一種編碼方法,稱之為最佳編碼
原理剖析
?通信領域中信息的處理方式3-赫夫曼編碼
?傳輸的字符串
(1)illike like like java do you like a java
(2) d:1y:1 u:1 j:2 v:2 o:2 l:4 k:4 e:4 i:5 a:5 :9|/各個字符對應的個數
(3)按照上面字符出現的次數構建一顆赫夫曼樹,次數作為權值
構成赫夫曼樹的步騷:
(1)從小到大進行排序,將每一個數據,每個數據都是一個節點,每個節點可以看成是一顆最簡單的二叉樹2)取出根節點權值最小的兩顆二叉樹
(3)組成一顆新的二叉樹,該新的二叉樹的根節點的權值是前面兩顆二叉樹根節點權值的和
(4)再將這顆新的二叉樹,以根節點的權值大小再次排序,不斷重復1-2-3-4的步驟,直到數列中,所有的數據都被處理,就得到-顆赫夫曼樹
(4)根據赫夫曼樹,給各個字符,規定編碼(前綴編碼),向左的路徑為o向右的路徑為1,編碼如下:o: 1000 u: 10010 d: 100110 y: 100111 i: 101
a : 110k: 1110 e: 1111j: 0000v: 0001l: 001:01
(5)按照上面的赫夫曼編碼,我們的"ilike like like java do you like a java”字符串對應的編碼為(注意這里我們使用的無損壓縮)
1010100110111101111010011011110111101001101111011110100001100001110011001111000011001111000100100100110111101111011100100001100001110通過赫夫曼編碼處理長度為133
(6)長度為:133
說明:
原來長度是359,壓縮了(359-133)/359=62.9%
此編碼滿足前綴編碼,即字符的編碼都不能是其他字符編碼的前綴。不會造成匹配的多義性
?注意事項
? ? 注意:這個赫夫曼樹根據排序方法不同,也可能不太一樣,這樣對應的赫夫曼編碼也不完全一樣,但是wpl是一樣的,都是最小的,最后生成的赫夫曼編碼的長度是一樣的,比如:我們讓每次生成的新的二叉樹總是排在權值相同的二叉樹的最后一個,則生成的二叉樹為:
數據壓縮(創建赫夫曼樹)
將給出的一段文本,比如 "ilike like like java do you like a java”,根據前面的講的赫夫曼編碼原理,對其進行數據壓縮處理形式
"10101001101111011010011011011101001101111011110100001100001110011001111000011001111000100100100110111101111011100100001100001110
步驟1:根據赫夫曼編碼壓縮數據的原理,需要創建"i like like like java do youlike a java”對應的赫夫曼樹.
創建赫夫曼數核心代碼:
//通過list創建赫夫曼樹private static Node createHuffmanTree(List<Node> nodes){while(nodes.size() >1){//排序,從小到大Collections.sort(nodes);//取出第一顆最小的二叉樹Node leftNode = nodes.get(0);//取出第二輪最小的二叉樹Node rightNode = nodes.get(1);//創建一顆新的二叉樹,它的根節點沒有date ,只有權值Node parent = new Node(null,leftNode.weight + rightNode.weight);parent.left = leftNode;parent.right = rightNode;//將已經處理的兩顆二叉樹從nodes刪除nodes.remove(leftNode);nodes.remove(rightNode);//將新的二叉樹,加入到nodesnodes.add(parent);}//nodes 最后的節點,就是赫夫曼樹的根節點return nodes.get(0);}數據壓縮(生成赫夫曼編碼和赫夫曼編碼后的數據)
最佳實踐-數據壓縮(生成赫夫曼編碼和赫夫曼編碼后的數據)
我們已經生成了赫夫曼樹,下面我們繼續完成任務
(1)生成赫夫曼樹對應的赫夫曼編碼,如下表:
?=O1 a=100 d=11000 u=11001 e=1110 v=11011 i=101 y=11010 j=0010 k=1111 l=O0(o=0011
(2)使用赫夫曼編碼來生成赫夫曼編碼數據,即按照上面的赫夫曼編碼,將"i like
like like java do you like a java”字符串生成對應的編碼數據,形式如下.1010100010111111110010001011111111001000101111111100100101001101110001110000011011101000111100101000101111111100110001001010011011100
思路:前面已經分析過了,而且我們講過了生成赫夫曼編碼的具體實現。
核心代碼實現:
/*** 為了方便調用,我們重載getCodes* @param root 根節點* @return huffmanCodes*/private static Map<Byte,String>getCods(Node root){if (root == null){return null;}//處理root的左子樹getCods(root.left,"0",stringBuilder);//處理root右子樹getCods(root.right,"1",stringBuilder);return huffmanCodes;}/*** 功能:將傳入的node節點的所有葉子節點的赫夫曼編碼得到,并放入到huffmanCodes集合中* @param node 傳入節點* @param code 路徑 :左子節點0,右子節點1* @param stringBuilder 用于拼接路徑*/private static void getCods(Node node, String code ,StringBuilder stringBuilder) {StringBuilder stringBuilder2 = new StringBuilder(stringBuilder);//將cods 加入到stringBuffer2stringBuilder2.append(code);if (node != null) { //如果node ==null不處理//判斷當前node 是葉子節點還是非葉子節點if (node.date == null) {//非葉子節點//遞歸處理//向左遞歸getCods(node.left, "0", stringBuilder2);//向右遞歸getCods(node.right, "1", stringBuilder2);} else { //說明是一個葉子節點//就表示找到了某個葉子節點的最后huffmanCodes.put(node.date, stringBuilder2.toString());}}}數據解壓(使用赫夫曼編碼解壓)
使用赫夫曼編碼來解碼數據,具體要求是
(1)前面我們得到了赫夫曼編碼和對應的編碼
byte[],即:[-88,-6念,-56,-65,-56,-65,-55,77,-57,6,-24,-14,-117,-4,-60,-90,28]
(2)現在要求使用赫夫曼編碼,進行解碼,又
重新得到原來的字符串"i like like like
java do you like a java"
思路:解碼過程,就是編碼的一個逆向操作。
解壓核心代碼:
/*** 將一個byte 轉成一個二進制的字符串* @param b 傳入的byte* @param flag 標識是否需要補高位如果是true,表示需要補高位,如果是false表示不補* @return 是該b 對應的二進制的字符串(注意是按補碼返回)*/private static String byteToBitString(boolean flag, byte b){//使用變量保存bint temp = b;//將b轉成int//如果是正數我們還存在補高位問題if (flag) {temp |= 256; //按位與256 1 0000 0000 0000 0001 =>1 0000 0001}String str = Integer.toBinaryString(temp); //返回的是temp對應的二進制的補碼if (flag){return str.substring(str.length() - 8);}else{return str;}}/***編寫一個方法,完成對壓縮數據的解碼* @param huffmanCodes 赫夫曼編碼表map* @param huffmanBytes 赫夫曼編碼得到字節數組* @return 就是原來字符串對應的數組* 如何將數據進行解壓(解碼)* 思路* 1.將huffmanCodeBytes[-88, -65, -56, -65, -56, -65, -55, 77, -57, 6, -24, -14, -117, -4, -60, -90, 28]* 重寫先轉成赫夫曼編碼對應的二進制的字符串"10101000010111...."* 2.赫夫曼編碼對應的二進制的字符串"1010100010111..=>對照赫夫曼編碼=>i like like like java do you like a java"*/private static byte[] decode(Map<Byte,String> huffmanCodes, byte[] huffmanBytes){//1.先得到huffmanBytes 對應的二進制的字符串,StringBuilder stringBuilder = new StringBuilder();//將byte數組轉成二進制的字符串for (int i = 0 ; i < huffmanBytes.length; i++){byte b = huffmanBytes[i];//判斷是不是最后一個字節boolean flag = (i == huffmanBytes.length - 1);stringBuilder.append(byteToBitString(!flag,b));}//把字符串按照指定的赫夫曼編碼進行解碼//把赫夫曼編碼表進行調換,因為要反向查詢 97->100 100->1Map<String,Byte> map = new HashMap<>();for (Map.Entry<Byte,String> entry: huffmanCodes.entrySet()){map.put(entry.getValue(),entry.getKey());}//創建一個集合,存放byteList<Byte> list = new ArrayList<>();//i 可以理解成就是索引,掃描stringBuilderfor (int i = 0 ;i < stringBuilder.length();){int count = 1;//小的計數器boolean flag = true;Byte b = null;while (flag){//取出一個'1' '0' 遞增的取出key 1String key = stringBuilder.substring(i,i+count);//i不動,讓count移動,匹配到指定一個字符b = map.get(key);if (b == null){//說明沒有匹配到count++;}else {//匹配到flag = false;}}list.add(b);i +=count;//i直接移動到count}//當for循環結束后,我們list中就存放了所有的字符"i like like like java do you like a java"//把list中的數據放入到byte[]并返回byte b[] = new byte[list.size()];for (int i = 0; i < b.length; i++){b[i] = list.get(i);}return b;}文件壓縮
我們學習了通過赫夫曼編碼對-個字符串進行編碼和解碼,下面我們來完成對文件的壓縮和解壓,具體要求:
給你一個圖片文件,要求對其進行無損壓縮,看看壓縮效果如何。
1)思路:讀取文件>得到赫夫曼編碼表>完成壓縮
2)代碼實現:?
代碼實現
/*** 編寫一個方法,將一個文件進行壓縮* @param srcFile 你傳入希望壓縮的文件的全路徑* @param dstFile 我們壓縮后將文件放到那個目錄*/public static void zipFile(String srcFile,String dstFile){//創建輸出流OutputStream os = null;ObjectOutputStream oos = null;//創建文件輸入流FileInputStream is = null;try {//創建一個和源文件大小一樣的byte[]is = new FileInputStream(srcFile);byte[] b = new byte[is.available()];//讀取文件is.read(b);//直接對源文件進行壓縮byte[] huffmanBytes = huffmanZip(b);//創建文件的輸出流,存放壓縮文件os = new FileOutputStream(dstFile);//創建一個和文件輸出流關聯的ObjectOutputStreamoos = new ObjectOutputStream(os);//把赫夫曼編碼后的字節數組寫入壓縮文件oos.writeObject(huffmanBytes);//這里我們以對象流的方式寫入赫夫曼編碼,是為了我們以后恢復源文件時使用//注意,一定要把赫夫曼編碼寫入壓縮文件oos.writeObject(huffmanCodes);}catch (Exception e){System.out.println(e.getMessage());}finally {try {is.close();oos.close();os.close();}catch (Exception e){System.out.println(e.getMessage());}}}文件解壓(文件恢復)
具體要求:將前面壓縮的文件,重新恢復成原來的文件
思路:讀取壓縮文件(數據和和赫夫曼編碼表)->完成解壓
代碼實現:
/*** 編寫一個方法,完成對壓縮文件進行解壓* @param zipFile 準備解壓的文件* @param dstFile 將文件解壓到那個路徑*/public static void unZipFile(String zipFile ,String dstFile){//定義文件輸入流InputStream is = null;//定義對象輸入流ObjectInputStream ois = null;//定義文件輸出流OutputStream os = null;try{//創建文件輸入流is = new FileInputStream(zipFile);//創建一個和is關聯的對象輸入流ois = new ObjectInputStream(is);//讀取byte數組 huffmanBytebyte [] huffmanBytes = (byte[])ois.readObject();//讀取赫夫曼編碼表Map<Byte,String> huffmanCodes = (Map<Byte, String>)ois.readObject();//解碼byte[] bytes = decode(huffmanCodes,huffmanBytes);//將bytes寫入目標文件os = new FileOutputStream(dstFile);//寫數據到dstFile 文件os.write(bytes);}catch (Exception e){System.out.println(e.getMessage());}finally {try {os.close();ois.close();is.close();}catch (Exception e2){System.out.println(e2.getMessage());}}}赫夫曼編碼壓縮文件注意事項
(1)如果文件本身就是經過壓縮處理的,那么使用赫夫曼編碼再壓縮效率不會有明顯變化,比如視頻,ppt等文件
(2)赫夫曼編碼是按字節來處理的,因此可以處理所有文件(二進制,文本文件)
(3)如果一個文件中的內容,重復數據不多,壓縮效果也不會很明顯
代碼匯總
前面所有的代碼
package huffmanCode;import java.io.*; import java.util.*;/*** 赫夫曼編碼 壓縮和解碼*/ public class HuffmanCode {public static void main(String[] args) {/*//測試壓縮文件String srcFile = "D:\\新建 文本文檔.txt";String dstFile ="D:\\dst2.zip" ;zipFile(srcFile,dstFile);System.out.println("壓縮文件ok~~~");*///測試解壓文件String zipFile ="D:\\dst2.zip" ;String dstFile = "D:\\111111111112.jpg";unZipFile(zipFile,dstFile);System.out.println("解壓成功");/*String content = "i like like like java do you like a java";byte[] contentBytes = content.getBytes();System.out.println(contentBytes.length); //40byte[] huffmanCodesBytes = huffmanZip(contentBytes);System.out.println("壓縮后的結果=" + Arrays.toString(huffmanCodesBytes) + "長度=" + huffmanCodesBytes.length);//測試byteToBitString方法//System.out.println(byteToBitString((byte)1));byte[] sourceBytes = decode(huffmanCodes,huffmanCodesBytes);System.out.println("原來的字符串="+ new String(sourceBytes));*///分步過程/*List<Node> nodes = getNodes(contentBytes);System.out.println(nodes);//測試一次,創建的二叉樹System.out.println("赫夫曼樹");Node huffmanTreeRoot = createHuffmanTree(nodes);System.out.println("前序遍歷");huffmanTreeRoot.preOrder();//測試是否生成了對應的赫夫曼編碼Map<Byte,String> huffmanCodes = getCods(huffmanTreeRoot);System.out.println("~生成的赫夫曼編碼表"+huffmanCodes);//17//測試byte[] huffmanCodeBytes = zip(contentBytes,huffmanCodes);System.out.println("huffmanCodeBytes="+Arrays.toString(huffmanCodeBytes));//發生huffmanCodeBytes 數組*/}/*** 編寫一個方法,將一個文件進行壓縮* @param srcFile 你傳入希望壓縮的文件的全路徑* @param dstFile 我們壓縮后將文件放到那個目錄*/public static void zipFile(String srcFile,String dstFile){//創建輸出流OutputStream os = null;ObjectOutputStream oos = null;//創建文件輸入流FileInputStream is = null;try {//創建一個和源文件大小一樣的byte[]is = new FileInputStream(srcFile);byte[] b = new byte[is.available()];//讀取文件is.read(b);//直接對源文件進行壓縮byte[] huffmanBytes = huffmanZip(b);//創建文件的輸出流,存放壓縮文件os = new FileOutputStream(dstFile);//創建一個和文件輸出流關聯的ObjectOutputStreamoos = new ObjectOutputStream(os);//把赫夫曼編碼后的字節數組寫入壓縮文件oos.writeObject(huffmanBytes);//這里我們以對象流的方式寫入赫夫曼編碼,是為了我們以后恢復源文件時使用//注意,一定要把赫夫曼編碼寫入壓縮文件oos.writeObject(huffmanCodes);}catch (Exception e){System.out.println(e.getMessage());}finally {try {is.close();oos.close();os.close();}catch (Exception e){System.out.println(e.getMessage());}}}/*** 編寫一個方法,完成對壓縮文件進行解壓* @param zipFile 準備解壓的文件* @param dstFile 將文件解壓到那個路徑*/public static void unZipFile(String zipFile ,String dstFile){//定義文件輸入流InputStream is = null;//定義對象輸入流ObjectInputStream ois = null;//定義文件輸出流OutputStream os = null;try{//創建文件輸入流is = new FileInputStream(zipFile);//創建一個和is關聯的對象輸入流ois = new ObjectInputStream(is);//讀取byte數組 huffmanBytebyte [] huffmanBytes = (byte[])ois.readObject();//讀取赫夫曼編碼表Map<Byte,String> huffmanCodes = (Map<Byte, String>)ois.readObject();//解碼byte[] bytes = decode(huffmanCodes,huffmanBytes);//將bytes寫入目標文件os = new FileOutputStream(dstFile);//寫數據到dstFile 文件os.write(bytes);}catch (Exception e){System.out.println(e.getMessage());}finally {try {os.close();ois.close();is.close();}catch (Exception e2){System.out.println(e2.getMessage());}}}/***編寫一個方法,完成對壓縮數據的解碼* @param huffmanCodes 赫夫曼編碼表map* @param huffmanBytes 赫夫曼編碼得到字節數組* @return 就是原來字符串對應的數組* 如何將數據進行解壓(解碼)* 思路* 1.將huffmanCodeBytes[-88, -65, -56, -65, -56, -65, -55, 77, -57, 6, -24, -14, -117, -4, -60, -90, 28]* 重寫先轉成赫夫曼編碼對應的二進制的字符串"10101000010111...."* 2.赫夫曼編碼對應的二進制的字符串"1010100010111..=>對照赫夫曼編碼=>i like like like java do you like a java"*/private static byte[] decode(Map<Byte,String> huffmanCodes, byte[] huffmanBytes){//1.先得到huffmanBytes 對應的二進制的字符串,StringBuilder stringBuilder = new StringBuilder();//將byte數組轉成二進制的字符串for (int i = 0 ; i < huffmanBytes.length; i++){byte b = huffmanBytes[i];//判斷是不是最后一個字節boolean flag = (i == huffmanBytes.length - 1);stringBuilder.append(byteToBitString(!flag,b));}//把字符串按照指定的赫夫曼編碼進行解碼//把赫夫曼編碼表進行調換,因為要反向查詢 97->100 100->1Map<String,Byte> map = new HashMap<>();for (Map.Entry<Byte,String> entry: huffmanCodes.entrySet()){map.put(entry.getValue(),entry.getKey());}//創建一個集合,存放byteList<Byte> list = new ArrayList<>();//i 可以理解成就是索引,掃描stringBuilderfor (int i = 0 ;i < stringBuilder.length();){int count = 1;//小的計數器boolean flag = true;Byte b = null;while (flag){//取出一個'1' '0' 遞增的取出key 1String key = stringBuilder.substring(i,i+count);//i不動,讓count移動,匹配到指定一個字符b = map.get(key);if (b == null){//說明沒有匹配到count++;}else {//匹配到flag = false;}}list.add(b);i +=count;//i直接移動到count}//當for循環結束后,我們list中就存放了所有的字符"i like like like java do you like a java"//把list中的數據放入到byte[]并返回byte b[] = new byte[list.size()];for (int i = 0; i < b.length; i++){b[i] = list.get(i);}return b;}/*** 將一個byte 轉成一個二進制的字符串* @param b 傳入的byte* @param flag 標識是否需要補高位如果是true,表示需要補高位,如果是false表示不補* @return 是該b 對應的二進制的字符串(注意是按補碼返回)*/private static String byteToBitString(boolean flag, byte b){//使用變量保存bint temp = b;//將b轉成int//如果是正數我們還存在補高位問題if (flag) {temp |= 256; //按位與256 1 0000 0000 0000 0001 =>1 0000 0001}String str = Integer.toBinaryString(temp); //返回的是temp對應的二進制的補碼if (flag){return str.substring(str.length() - 8);}else{return str;}}/***使用給一個方法,將前面的方法封裝起來,便于我們調用* @param bytes 原始的字符串對應的字節數組* @return 是經過 赫夫曼編碼壓縮后的字節數組*/private static byte[] huffmanZip(byte[] bytes){List<Node> nodes = getNodes(bytes);//根據nodes創建赫夫曼樹Node huffmanTreeRoot = createHuffmanTree(nodes);//生成對應的赫夫曼編碼Map<Byte,String> huffmanCodes = getCods(huffmanTreeRoot);//根據生產的赫夫曼編碼壓縮,得到壓縮后的赫夫曼編碼字節數組byte[] huffmanCodeBytes = zip(bytes,huffmanCodes);return huffmanCodeBytes;}/***編寫一個方法,將字符串對應的byte[]數組,通過生成的赫夫曼編碼表,返回一個赫夫曼編碼壓縮后的byte[]* @param bytes 中時原始的字符串對應的byte[]* @param huffmanCodes 生產的赫夫曼編碼map* @return 返回赫夫曼編碼處理后的byte[]**/private static byte[] zip(byte[] bytes, Map<Byte,String> huffmanCodes){//1.利用huffmanCodes 將bytes 轉成赫夫曼編碼的對應字符串StringBuilder stringBuilder = new StringBuilder();//遍歷bytes 數組for (byte b : bytes){stringBuilder.append(huffmanCodes.get(b));}//將 對應的字符串,轉成byte[]//統計返回的byte[]huffmanCodeBytes 長度//一句話 int len =(stingBuilder.length()+7)/8;int len;if (stringBuilder.length() % 8 ==0){len =stringBuilder.length() / 8;}else {len = stringBuilder.length() / 8 + 1;}//創建存儲壓縮后的byte數組byte [] huffmanCodeBytes = new byte[len];int index = 0; //記錄是第一個bytefor (int i = 0; i < stringBuilder.length(); i += 8){//因為是每8位對應一個byte,所有步長是+8String strByte;if (i + 8 > stringBuilder.length()){ //不夠八位strByte = stringBuilder.substring(i);}else {strByte = stringBuilder.substring(i, i + 8);}//將strByte 轉成一個byte,放入huffmanCodeByteshuffmanCodeBytes[index] = (byte)Integer.parseInt(strByte,2);index++;}return huffmanCodeBytes;}/*** 生成赫夫曼樹對應的編碼表* 思路分析:* 1.將赫夫曼編碼表存放在Map<Byte,String>形式* 2.在生成赫夫曼編碼表示,需要去拼接路徑,定義一個StingBuilder 儲存某個葉子節點的路徑*/static Map<Byte,String> huffmanCodes = new HashMap<>();static StringBuilder stringBuilder =new StringBuilder();/*** 為了方便調用,我們重載getCodes* @param root 根節點* @return huffmanCodes*/private static Map<Byte,String>getCods(Node root){if (root == null){return null;}//處理root的左子樹getCods(root.left,"0",stringBuilder);//處理root右子樹getCods(root.right,"1",stringBuilder);return huffmanCodes;}/*** 功能:將傳入的node節點的所有葉子節點的赫夫曼編碼得到,并放入到huffmanCodes集合中* @param node 傳入節點* @param code 路徑 :左子節點0,右子節點1* @param stringBuilder 用于拼接路徑*/private static void getCods(Node node, String code ,StringBuilder stringBuilder) {StringBuilder stringBuilder2 = new StringBuilder(stringBuilder);//將cods 加入到stringBuffer2stringBuilder2.append(code);if (node != null) { //如果node ==null不處理//判斷當前node 是葉子節點還是非葉子節點if (node.date == null) {//非葉子節點//遞歸處理//向左遞歸getCods(node.left, "0", stringBuilder2);//向右遞歸getCods(node.right, "1", stringBuilder2);} else { //說明是一個葉子節點//就表示找到了某個葉子節點的最后huffmanCodes.put(node.date, stringBuilder2.toString());}}}//前序遍歷的方法private static void preOrder(Node root){if (root != null){root.preOrder();}else {System.out.println("該赫夫曼是空樹");}}/*** 將字節數組存放到list中* @param bytes 接受字節數組* @return 返回的就是List 形式 [Node[date=97] ,weight = 5]*/private static List<Node> getNodes(byte[] bytes) {//創建一個ArrayListArrayList<Node> nodes = new ArrayList<>();//遍歷bytes ,統計每一個byte出現的次數->map[key ,value]Map<Byte, Integer> counts = new HashMap<>();for (byte b : bytes) {Integer count = counts.get(b);if (count == null) { //Map還沒有這個字符數據,第一次counts.put(b, 1);} else {counts.put(b, count + 1);}}//把每個鍵值對轉成一個Node 對象,并加入到nodes集合//遍歷mapfor (Map.Entry<Byte,Integer> entry : counts.entrySet()){nodes.add(new Node(entry.getKey(),entry.getValue()));}return nodes;}//通過list創建赫夫曼樹private static Node createHuffmanTree(List<Node> nodes){while(nodes.size() >1){//排序,從小到大Collections.sort(nodes);//取出第一顆最小的二叉樹Node leftNode = nodes.get(0);//取出第二輪最小的二叉樹Node rightNode = nodes.get(1);//創建一顆新的二叉樹,它的根節點沒有date ,只有權值Node parent = new Node(null,leftNode.weight + rightNode.weight);parent.left = leftNode;parent.right = rightNode;//將已經處理的兩顆二叉樹從nodes刪除nodes.remove(leftNode);nodes.remove(rightNode);//將新的二叉樹,加入到nodesnodes.add(parent);}//nodes 最后的節點,就是赫夫曼樹的根節點return nodes.get(0);} }//創建Node ,待數據和權值 class Node implements Comparable<Node>{Byte date; //存放數據int weight;//權值,表示字符出現的次數Node left;Node right;public Node(Byte date, int weight) {this.date = date;this.weight = weight;}@Overridepublic int compareTo(Node o) {//從小到大排序return this.weight -o.weight;}@Overridepublic String toString() {return "Node{" +"date=" + date +", weight=" + weight +'}';}//前序遍歷public void preOrder(){System.out.println(this);if (this.left != null){this.left.preOrder();}if (this.right != null){this.right.preOrder();}} }
?
總結
以上是生活随笔為你收集整理的数据结构与算法(赫夫曼树,赫夫曼编码)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 计算机网络原理自考真题2013,最全全国
- 下一篇: 基于python/django的图书管理