課程:《程序設計與數據結構》
班級: 1723
姓名: 王禹涵
學號: 20172323
實驗教師:王志強老師
測試日期:2018年12月10日
必修/選修: 必修java
哈夫曼編碼測試
設有字符集: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)把實驗結果截圖上傳到雲班課node
哈夫曼樹:給定n個權值做爲n個葉子結點,構造一棵二叉樹,若帶權路徑長度達到最小,稱這樣的二叉樹爲最優二叉樹,也稱爲哈夫曼樹(Huffman Tree)。哈夫曼樹是帶權路徑長度最短的樹,權值較大的結點離根較近。數組
路徑: 樹中一個結點到另外一個結點之間的分支構成這兩個結點之間的路徑。數據結構
路徑長度:路徑上的分枝數目稱做路徑長度。測試
樹的路徑長度:從樹根到每個結點的路徑長度之和。this
結點的帶權路徑長度:在一棵樹中,若是其結點上附帶有一個權值,一般把該結點的路徑長度與該結點上的權值之積稱爲該結點的帶權路徑長度(weighted path length)編碼
樹的帶權路徑長度:若是樹中每一個葉子上都帶有一個權值,則把樹中全部葉子的帶權路徑長度之和稱爲樹的帶權路徑長度。.net
protected HuffmanNode left; protected HuffmanNode right; protected String name; protected double weight; protected String code; public HuffmanNode(String name, double weight){ this.name = name; this.weight = weight; code = ""; } public int compareTo(HuffmanNode node) { if (weight >= node.weight){ return 1; } else { return -1; } }
while(nodes.size() > 1){ Collections.sort(nodes); Node<T> left = nodes.get(nodes.size()-1); Node<T> right = nodes.get(nodes.size()-2); Node<T> parent = new Node<T>(null, left.getWeight()+right.getWeight()); parent.setLeft(left); parent.setRight(right); nodes.remove(left); nodes.remove(right); nodes.add(parent); } return nodes.get(0);
這裏將全部的哈夫曼樹的結點都存在了一個數組之中,判斷數組1內是否有元素,進入while循環以後先將數組元素進行排序,而後取出數組中的最小元素和次小元素(便是數組中的末兩位)分別做爲左右孩子,兩者之和做爲父結點元素放入數組之中從新進行排序,直至數組中元素爲一,哈夫曼樹構造完成。緊接着是哈夫曼樹的廣度優先遍歷方法設計
List<Node<T>> list = new ArrayList<Node<T>>(); Queue<Node<T>> queue = new ArrayDeque<Node<T>>(); if(root != null){ queue.offer(root); } while(!queue.isEmpty()){ list.add(queue.peek()); Node<T> node = queue.poll(); if(node.getLeft() != null){ queue.offer(node.getLeft()); } if(node.getRight() != null){ queue.offer(node.getRight()); } } return list;
基礎的哈夫曼樹構造好了,接着須要在此基礎上進行哈夫曼編碼。基本思路是,從根結點開始設二叉樹的左子樹編碼爲‘0’,右子樹的編碼爲‘1’,依次編碼下去直到葉結點,而後從根到每一個葉結點依次寫出葉結點的編碼--哈夫曼編碼,具體實現就是在構造哈夫曼樹的同時,加上以下代碼指針
left.setCode("0"); right.setCode("1");
由於這裏設置的是String型,因此要加上「」,表示字符串。同時遍歷方法也要相應加上「0」,「1」。當遍歷到左孩子時,加上1,遍歷到右孩子時,加上0.
File file = new File("此處填寫文件路徑"); BufferedReader br = new BufferedReader(new FileReader(file));
讀入文件內的信息。經過readline方法將信息成行讀入,並從新拼接成字符串,逐個字符進行比對,統計出現的個數及機率並進行輸出
List<String> list = new ArrayList<String>(); list.add("a"); list.add("b"); list.add("c"); list.add("d"); list.add("e"); list.add("f"); list.add("g"); list.add("h"); list.add("i"); list.add("j"); list.add("k"); list.add("l"); list.add("m"); list.add("n"); list.add("o"); list.add("p"); list.add("q"); list.add("r"); list.add("s"); list.add("t"); list.add("u"); list.add("v"); list.add("w"); list.add("x"); list.add("y"); list.add("z"); list.add(" "); int[] number = new int[]{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; File file = new File("C:\\Users\\10673\\Desktop\\input.txt"); BufferedReader br = new BufferedReader(new FileReader(file)); String s; String message = ""; while((s = br.readLine()) != null){ message += s; } String[] result = message.split(""); for (int n = 0;n < result.length; n++){ for (int i = 0; i < 27; i++){ if (result[n].equals(list.get(i))){ number[i] += 1; } } } List<HuffmanNode> nodeList = new ArrayList<HuffmanNode>(); DecimalFormat df = new DecimalFormat( "0.0000000"); double wei; double sum = result.length; for(int i = 0;i<27;i++){ wei = ((double) number[i]/sum); System.out.println(list.get(i) + "出現" + number[i] + "次,機率爲" + df.format(wei)); nodeList.add(new HuffmanNode(list.get(i),number[i])); } Collections.sort(nodeList); HuffmanTree huffmanTree = new HuffmanTree(); HuffmanNode node = huffmanTree.createTree(nodeList); List<HuffmanNode> inlist = new ArrayList<HuffmanNode>(); inlist = huffmanTree.breadth(node);
以上代碼並非特別的複雜,我構造了一個字符列表,用以與獲得的字符串進行比對,當某個字符比對成功以後,相應位置的數組的值加一,這樣就能夠統計到每一個字符出現的次數。再而後計算獲得每一個字符出現的機率,將這些字符及其權重存儲在HuffmanNode的列表中,經過此就能夠調用哈夫曼樹的構造方法。剩下的幾個方法,就是將哈夫曼樹的結點逐個進行編碼解碼,並輸出到指定的文件之中
運行結果如圖所示
[](https://img2018.cnblogs.com/blog/1332964/201812/1332964-20181211214511138-1894899771.png
問題1:運用除法計算字符出現的機率時,運算結果所有爲0,如圖
問題1解決方案:最開始覺得是顯示的只有小數點後一位,因此不少數據比較小,四捨五入以後就只剩下0.0,查閱資料從新控制double類型小數點後位數的方法
DecimalFormat df = new DecimalFormat( "0.00"); //設置double類型小數點後位數格式 double d1 = 2.1; System.out.println(df.format(d1)); //將輸出2.10
輸出以後發現機率所有都爲0.這裏運用到的方法是number[i]/sum
,sum是總的字符個數,number[i]依次是每一個字符的出現次數。
問題就出在這個除法上,由於這是兩個整型數相除,因此獲得的依然會是一個整型數,即使再把它轉換成double類型,最終出來的也是0.0,因此應該在相除以前就將兩個除數轉換爲double類型,這樣得出的結果就是正確的
- [哈夫曼樹的java實現](https://blog.csdn.net/jdhanhua/article/details/6621026)