20182307 哈夫曼編碼實踐


20182307 哈夫曼編碼實踐

任務詳情

  • 設有字符集: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個英文字母的文件,統計每個字符出現的概率,根據計算的概率構造一顆哈夫曼樹。
  • 完成對英文文件的編碼和解碼。
  • 要求:
    • 准備一個包含26個英文字母的英文文件(可以不包含標點符號等),統計各個字符的概率
    • 構造哈夫曼樹
    • 對英文文件進行編碼,輸出一個編碼后的文件
    • 對編碼文件進行解碼,輸出一個解碼后的文件
    • 撰寫博客記錄實驗的設計和實現過程,並將源代碼傳到碼雲
    • 把實驗結果截圖上傳到雲班課

實驗前的准備及思路構思

  • 首先是准備一個包含所有英文字母的文件,然后計算所有字母出現的概率。大致思路是用一個計數變量來給字母計數,然后定義一個數組儲存包含26個字母及空格在內的字符的出現概率。初步思路是設計一個雙重循環來達成計數與儲存的功能。
  • 第二是讀取和寫文件的操作
    • 先准備一個文件,讀入它的所有內容后將其存入一個足夠大的數組,或者根據實際的文本長度自動擴容
    • 編碼解碼時需要生成並寫出兩個文件,具體代碼是
        File file = new File("路徑\\文件名.txt");
        Writer writer = new FileWriter(file);
        writer.write(result);
        writer.close();
  • 代碼的大致意思是創建一個新的文件,然后讀取這個文件並將編碼或解碼后的文本存入文件
  • 接下來是最重要的構建哈夫曼樹的過程,首先要了解哈夫曼樹構建的理論過程,參考網絡資料:哈夫曼樹原理,及構造方法
    1
  • 編碼的思路就是遍歷整棵樹,當遍歷左子樹的時候編碼加上‘0’,當遍歷右子樹的時候編碼加上‘1’
  • 解碼時可能需要考慮字符與編碼的對應並輸出,解決時可能需要兩個甚至更多的數組

實驗過程

文件讀寫

  • 根據曾經學過的文件讀寫知識,創建文件並進行讀寫操作,代碼如下:
        File file = new File("路徑\\文件名.txt");
        if(!file.exists()){
            file.createNewFile();
        }

        Reader reader = new FileReader(file);
        BufferedReader bufferedReader = new BufferedReader(reader);
        String temp = bufferedReader.readLine();
        
        File file2 = new File("路徑\\文件名txt");
        Writer writer = new FileWriter(file2);
        writer.write(result1);
        writer.close();

嵌套循環計數

  • 兩層循環計數並存儲:外層循環存儲每個字母出現的頻率;內層循環遍歷文本比較,計數后計算頻率
for (int j = 97; j <= 122; j++) {
            int number = 0;//給字母計數
            for (int m = 0; m < characters.length; m++) {
                if (characters[m] == (char) j) {
                    number++;
                }
                frequency[j - 97] = (float) number / characters.length;
            }
        }

構建哈夫曼樹

  • 哈夫曼樹定義:給定n個權值作為n個葉子結點,構造一棵二叉樹,若該樹的帶權路徑長度達到最小,稱這樣的二叉樹為最優二叉樹,也稱為哈夫曼樹(Huffman Tree)。哈夫曼樹是帶權路徑長度最短的樹,權值較大的結點離根較近。
  • 構建規則:假設一組權值,一個權值是一個結點,並進行排序,例如:12 34 2 5 7 。在這其中找出兩個最小的權值,然后組成一個新的權值,排序。再次找出最小的權值結點。如圖:
  • 部分代碼:
public static HuffNode createTree(List<HuffNode> nodes) {
        // 只要nodes數組中有2個以上的節點
        while (nodes.size() > 1) {
            //進行從大到小的排序
            Collections.sort(nodes);
            //獲取權值最小的兩個節點
            HuffNode left = nodes.get(nodes.size() - 1);
            HuffNode right = nodes.get(nodes.size() - 2);
            //生成新節點,新節點的權值為兩個子節點的權值之和
            HuffNode parent = new HuffNode('無', left.getWeight() + right.getWeight());
            //讓新節點作為兩個權值最小節點的父節點
            parent.setLeft(left);
            left.setCodenumber("0");
            parent.setRight(right);
            right.setCodenumber("1");
            //刪除權值最小的兩個節點
            nodes.remove(left);
            nodes.remove(right);
            //將新節點加入到集合中
            nodes.add(parent);
        }
        return nodes.get(0);
    }

給每個節點編碼

  • 遍歷整棵哈夫曼樹,左子樹編碼為‘0’,右子樹編碼為‘1’
  • 部分代碼:
public static  List<HuffNode> breadthFirstTraversal(HuffNode root) {
        List<HuffNode> list = new ArrayList<HuffNode>();
        Queue<HuffNode> queue = new ArrayDeque<HuffNode>();

        //將根元素加入“隊列
        if (root != null) {
            queue.offer(root);
            root.getLeft().setCodenumber(root.getCodenumber() + "0");
            root.getRight().setCodenumber(root.getCodenumber() + "1");
        }

        while (!queue.isEmpty()) {
            //將該隊列的“隊尾”元素加入到list中
            list.add(queue.peek());
            HuffNode node = queue.poll();

            //如果左子節點不為null,將它加入到隊列
            if (node.getLeft() != null) {
                queue.offer(node.getLeft());
                node.getLeft().setCodenumber(node.getCodenumber() + "0");
            }
            //如果右子節點不為null,將它加入到隊列
            if (node.getRight() != null) {
                queue.offer(node.getRight());
                node.getRight().setCodenumber(node.getCodenumber() + "1");
            }
        }
        return list;
    }

輸出編碼解碼文件

  • 正常的遍歷樹,將編碼寫入文件,再解碼,寫入一個新的文件
  • 解碼部分代碼:
        //將讀出的密文存在secretText列表中
        List<String> secretText = new ArrayList<String>();
        for (int i = 0; i < secretline.length(); i++) {
            secretText.add(secretline.charAt(i) + "");
        }

        //解密
        String result2 = "";//最后的解碼結果
        String current="";// 臨時的保存值
        while(secretText.size()>0) {
            current = current + "" + secretText.get(0);
            secretText.remove(0);
            for (int p = 0; p < newlist1.size(); p++) {
                if (current.equals(newlist1.get(p))) {
                    result2 = result2 + "" + newlist.get(p);
                    current="";
                }

            }
        }

實驗過程中遇到的問題

  • 問題1:程序排序后找出了最小的兩個權值,那如何把相加結果的節點添加到樹中呢?

    • 分析解決:無疑此時需要構造一個全新的節點來成為兩個相加節點的代替,具體流程如下:
      • 生成新節點,新節點的權值為兩個子節點的權值之和
      • 讓新節點作為兩個權值最小節點的父節點
      • 刪除權值最小的兩個節點
      • 將新節點加入到集合中
  • 問題2:如何將節點和編碼對應起來並輸出?

    • 分析解決: 首先構造樹的時候就定下了節點的順序,這時只要按照設計的程序為每個節點編碼即可,此時實際已經達成綁定,即一個節點包含的屬性有:值,編碼;而在最后輸出的時候,只需要定義一個列表數組,每個數組元素按遍歷順序獲得相應節點的編碼並存儲,最后輸出這個數組即可
    • 部分代碼:
       List<String> newlist1 = new ArrayList<>();
          for(int m=0;m < temp1.size();m++)
          {
              if(temp1.get(m).getData()!='無')
                  newlist1.add(String.valueOf(temp1.get(m).getCodenumber()));
          }
          System.out.println("\n對應編碼:"+newlist1);
    


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM