20192313 2020-2021-1 《資料結構與面向物件程式設計》 哈夫曼編碼實踐報告
實踐內容
設有字符集: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)把實驗結果截圖上傳到雲班課
哈夫曼樹相關知識
概念
-
路徑:在一棵樹中從一個結點往下到孩子或孫子結點之間的通路
-
結點的路徑長度:從根節點到該節點的路徑上分支的數目
-
樹的路徑長度:樹中每個結點的路徑長度之和
-
結點的權:給樹中的結點賦予一個某種含義的值,則該值為該節點的權
-
結點的帶權路徑長度:結點的路徑長度乘以結點的權
-
樹的帶權路徑長度(WPL):樹中所有葉子結點的帶權路徑長度 (Weight Path Length)
-
最優二叉樹(哈夫曼樹):帶權路徑長度最小的二叉樹
構造哈夫曼樹
- 給定n個權值{w1,w2,…wn},則構造出的哈夫曼樹有n個葉子結點,構造過程如下:
1.將w1,w2…wn按從小到大排序,並將他們看做n棵只有一個結點的樹組成的森林;
2.選出兩個根節點權值最小的樹合併,作為新樹的左右子樹,新樹的根節點權值是左右子樹根節點權值之和
3.從森林中刪除選取的兩棵樹,將新樹加入森林
4.重複2,3,直到只剩一棵樹,所得即為最優二叉樹
相關程式碼
所需實現程式碼的方法和要實現Comparable介面,比較權重好確定放的位置,編碼是0還是1
public class HuffmanNode<T> implements Comparable<HuffmanNode<T>>{ private T name; private double length; private HuffmanNode<T> left; private HuffmanNode<T> right; String code; public HuffmanNode(T name, double length){ this.name = name; this.length = length; code = ""; } public T getName() { return name; } public void setName(T name) { this.name = name; } public double getLength() { return length; } public void setLength(double length) { this.length = length; } public HuffmanNode<T> getLeft() { return left; } public void setLeft(HuffmanNode<T> left) { this.left = left; } public HuffmanNode<T> getRight() { return right; } public void setRight(HuffmanNode<T> right) { this.right = right; } public String getCode(){ return code; } public void setCode(String str){ code = str; } @Override public String toString(){ return "name:"+this.name+";length:"+this.length+";編碼為: "+this.code; } @Override //確定位置 public int compareTo(HuffmanNode<T> other) { if(other.getLength() > this.getLength()){ return 1; } if(other.getLength() < this.getLength()){ return -1; } return 0; } }
第二步,建立樹。當還有結點時,對結點進行排序,然後左孩子為陣列中的個數-2的結點,右孩子為陣列中的個數-1的結點(用陣列實現樹的那一章說過左右孩子在陣列中的索引),賦予左孩子的編碼為0,右孩子的編碼為1,雙親結點則為左右孩子相加的權重(也就是左右孩子的概率和),把雙親結點加入連結串列中,從連結串列中把舊的左右孩子刪除,直至連結串列中的結點只剩一個(也就是根結點)
public HuffmanNode<T> createTree(List<HuffmanNode<T>> nodes) {
while (nodes.size() > 1) {
Collections.sort(nodes);
HuffmanNode<T> left = nodes.get(nodes.size() - 2);
left.setCode(0 + "");
HuffmanNode<T> right = nodes.get(nodes.size() - 1);
right.setCode(1 + "");
HuffmanNode<T> parent = new HuffmanNode<T>(null, left.getLength() + right.getLength());
parent.setLeft(left);
parent.setRight(right);
nodes.remove(left);
nodes.remove(right);
nodes.add(parent);
}
return nodes.get(0);
}
確定相應字元的編碼值
public List<HuffmanNode> breadth(HuffmanNode root) {
List<HuffmanNode> list = new ArrayList<HuffmanNode>();
Queue<HuffmanNode> queue = new ArrayDeque<HuffmanNode>();
if (root != null) {
queue.offer(root);
root.getLeft().setCode(root.getCode() + "0");
root.getRight().setCode(root.getCode() + "1");
}
while (!queue.isEmpty()) {
list.add(queue.peek());
HuffmanNode node = queue.poll();
if (node.getLeft() != null)
node.getLeft().setCode(node.getCode() + "0");
if (node.getRight() != null)
node.getRight().setCode(node.getCode() + "1");
if (node.getLeft() != null) {
queue.offer(node.getLeft());
}
if (node.getRight() != null) {
queue.offer(node.getRight());
}
}
return list;
}
建立編碼檔案,將要編碼的字串中的字元逐一與預先生成哈夫曼樹時儲存的字元編碼對照表進行比較,找到以後,將字元的編碼寫入程式碼檔案,直至所有的字元處理完為止
public static void main(String[] args) throws IOException {
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:\\developer\\Huffman", "cyf.txt");
if (!file.exists()) {
file.createNewFile();
}
BufferedReader br = new BufferedReader(new FileReader(file));
String s;
String message = "";
while((s = br.readLine()) != null){
message += s;
}
得到每個字元(包括空格)出現的次數(類似於累加的形式)
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);
String[] name = new String[number.length];
String[] code = new String[number.length];
File file1 = new File("編碼檔案.txt");
File file2 = new File("解碼檔案.txt");
FileWriter fileWriter1 = new FileWriter(file1);
FileWriter fileWriter2 = new FileWriter(file2);
int temp = 0;
for(int e = 0;e<inlist.size();e++){
if(inlist.get(e).getName() != null){
System.out.println(inlist.get(e).getName()+"的編碼為"+ inlist.get(e).getCode()+" ");
name[temp] = (String) inlist.get(e).getName();
code[temp] = inlist.get(e).getCode();
temp++;
}
}
String res = "";
for(int f = 0; f < sum; f++){
for(int j = 0;j<name.length;j++){
if(message.charAt(f) == name[j].charAt(0))
res += code[j];
}
}
System.out.println("編碼後:"+ res);
List<String> putlist = new ArrayList<String>();
for(int i = 0;i < res.length();i++){
putlist.add(res.charAt(i)+"");
}
String string1 = "";
String string2 = "";
for(int h = putlist.size(); h > 0; h--){
string1 = string1 + putlist.get(0);
putlist.remove(0);
for(int i=0;i<code.length;i++){
if (string1.equals(code[i])) {
string2 = string2+""+ name[i];
string1 = "";
}
}
}
System.out.println("解碼後:" + string2);
fileWriter1.write(res);
fileWriter2.write(string2);
fileWriter1.close();
fileWriter2.close();
}
private static int getFileLineCount(File file) {
int cnt = 0;
InputStream is = null;
try {
is = new BufferedInputStream(new FileInputStream(file));
byte[] c = new byte[1024];
int readChars = 0;
while ((readChars = is.read(c)) != -1) {
for (int i = 0; i < readChars; ++i) {
if (c[i] == '\n') {
++cnt;
}
}
}
} catch (Exception ex) {
cnt = -1;
ex.printStackTrace();
} finally {
try {
is.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
return cnt;
}
測試結果
文字為
hello
nice to meet you
huffman is yyds
測試截圖