下面咱們以【五、八、四、十一、九、13】爲例來畫出哈夫曼樹(數字大小表明權重大小,越大的權重越大)html
【五、八、四、十一、九、13】→【四、五、八、九、十一、13】node
給定的四、五、八、九、十一、13爲白色, 紅色的9爲4+5,與給定的白9無關,新序列爲:【紅9(含子節點四、5)、八、九、十一、13】
git
排序:
算法
取兩個最小數8和9:
數組
排序:
數據結構
取兩個最小數9和11:
測試
排序,而後取兩個最小數13和17:
優化
取兩個最小數20和30:
ui
哈夫曼研究這種最優樹的目的是爲了解決當年遠距離通訊(主要是電報)的數據傳輸的最優化問題。編碼
好比咱們有一段文字「BADCADFEED」,顯然用二進制數字(0和1)表示是很天然的想法。
這樣真正傳輸的數據就是「001000011010000011101100100011」,對方接收時一樣按照3位一組解碼。若是一篇文章很長,這樣的二進制串也很是的可怕。並且事實上,每一個字母或者漢子的出現頻率是不一樣的。
假設六個字母的頻率爲A 27,B 8, C 15, D 15 , E 30, F 5,合起來正好是100%,那就意味着咱們徹底能夠用哈夫曼樹來規劃它們。
左圖爲構造哈夫曼樹的過程的權值顯示。右圖爲將權值左分支改成0,右分支改成1後的哈夫曼樹。
咱們對這六個字母用其從樹根到葉子所通過的路徑的0或1來編碼,能夠獲得下表:
也就是說咱們的數據被壓縮了,節約了大概17%的存儲或傳輸成本。隨着字符的增長和多字符權重的不一樣,這種壓縮會更顯出優點來。
哈夫曼編碼測試
設有字符集:S={a,b,c,d,e,f,g,h,i,j,k,l,m,n.o.p.q,r,s,t,u,v,w,x,y,z}。
給定一個包含26個英文字母的文件,統計每一個字符出現的機率,根據計算的機率構造一顆哈夫曼樹。
並完成對英文文件的編碼和解碼。
要求:
(1)準備一個包含26個英文字母的英文文件(能夠不包含標點符號等),統計各個字符的機率
(2)構造哈夫曼樹
(3)對英文文件進行編碼,輸出一個編碼後的文件
(4)對編碼文件進行解碼,輸出一個解碼後的文件
(5)撰寫博客記錄實驗的設計和實現過程,並將源代碼傳到碼雲
(6)把實驗結果截圖上傳到雲班課
滿分:6分。
酌情打分。
構造哈夫曼樹:
public static HuffmanNode createTree(List<HuffmanNode> nodes) { // 只要nodes數組中還有2個以上的節點 while (nodes.size() > 1) { quickSort(nodes); //獲取權值最小的兩個節點 HuffmanNode left = nodes.get(nodes.size()-1); left.setCodeNumber(0+""); HuffmanNode right = nodes.get(nodes.size()-2); right.setCodeNumber(1+""); //生成新節點,新節點的權值爲兩個子節點的權值之和 HuffmanNode parent = new HuffmanNode(null, left.weight + right.weight); //讓新節點做爲兩個權值最小節點的父節點 parent.leftChild = left; parent.rightChild = right; //刪除權值最小的兩個節點 nodes.remove(nodes.size()-1); nodes.remove(nodes.size()-1); //將新節點加入到集合中 nodes.add(parent); } return nodes.get(0); } /** * 將指定集合中的i和j索引處的元素交換 * * @param nodes * @param i * @param j */ private static void swap(List<HuffmanNode> nodes, int i, int j) { HuffmanNode tmp; tmp = nodes.get(i); nodes.set(i, nodes.get(j)); nodes.set(j, tmp); } /** * 實現快速排序算法,用於對節點進行排序 * @param nodes * @param start * @param end */ private static void subSort(List<HuffmanNode> nodes, int start, int end) { if (start < end) { // 以第一個元素做爲分界值 HuffmanNode base = nodes.get(start); // i從左邊搜索,搜索大於分界值的元素的索引 int i = start; // j從右邊開始搜索,搜索小於分界值的元素的索引 int j = end + 1; while (true) { // 找到大於分界值的元素的索引,或者i已經到了end處 while (i < end && nodes.get(++i).weight >= base.weight); // 找到小於分界值的元素的索引,或者j已經到了start處 while (j > start && nodes.get(--j).weight <= base.weight); if (i < j) { swap(nodes, i, j); } else break; } swap(nodes, start, j); //遞歸左邊子序列 subSort(nodes, start, j - 1); //遞歸右邊子序列 subSort(nodes, j + 1, end); } } public static void quickSort(List<HuffmanNode> nodes) { subSort(nodes, 0, nodes.size()-1); } //層序遍歷 public static List<HuffmanNode> levelTraversal(HuffmanNode root) { Queue<HuffmanNode> queue = new ArrayDeque<HuffmanNode>(); List<HuffmanNode> list = new ArrayList<HuffmanNode>(); if(root!=null) { //將根元素加入「隊列」 queue.offer(root); root.leftChild.setCodeNumber(root.getCodeNumber()+"0"); root.rightChild.setCodeNumber(root.getCodeNumber()+"1"); } while(!queue.isEmpty()) { //將該隊列的「隊尾」元素加入到list中 list.add(queue.peek()); HuffmanNode tree = queue.poll(); //若是左子節點不爲null,將它加入到隊列 if(tree.leftChild != null) { queue.offer(tree.leftChild); tree.leftChild.setCodeNumber(tree.getCodeNumber()+"0"); } //若是右子節點不爲null,將它加入到隊列 if(tree.rightChild != null) { queue.offer(tree.rightChild); tree.rightChild.setCodeNumber(tree.getCodeNumber()+"1"); } } return list; }
計算字母出現次數:
//層序遍歷顯示構建的哈弗曼樹 char[] chars = new char[a]; int[] times = new int[a]; Iterator<Character> pl2 = counter.keySet().iterator(); for (int i=0;i<=a;i++ ) { if (pl2.hasNext()) { chars[i] = pl2.next(); times[i] = counter.get(chars[i]); } } List<HuffmanNode> list = new ArrayList<HuffmanNode>(); for(int i = 0;i<a;i++) { System.out.print(chars[i]+"出現次數爲:"+times[i]+" \n"); list.add(new HuffmanNode(chars[i]+"",times[i])); }
讀取文件:
File file = new File("E:\\IDES_Project\\JSSD\\src\\HaffmanCoding\\test.txt"); if (!file.exists()) { throw new Exception("文件不存在"); } BufferedReader fin = new BufferedReader(new FileReader(file)); String line; Map<Character, Integer> counter = new HashMap<Character, Integer>(); int total=0; while ((line = fin.readLine()) != null) { int len = line.length(); for (int i = 0; i < len; i++) { char c = line.charAt(i); if (( (c >= 'a' && c <= 'z'&& c == ' '))) { continue; } if (counter.containsKey(c)) { counter.put(c, counter.get(c) + 1); } else { counter.put(c, 1); } } } fin.close();
此次實驗給人的感受有點難,不是很懂,因此寫起來很迷。