20172304 藍墨雲實驗哈夫曼樹

20172304 藍墨雲實驗哈夫曼樹

實驗要求

  • 設有字符集: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) 把實驗結果截圖上傳到雲班課

哈夫曼編碼

  • 哈夫曼編碼(Huffman Coding),又稱霍夫曼編碼,是一種編碼方式,哈夫曼編碼是可變字長編碼(VLC)的一種。Huffman於1952年提出一種編碼方法,該方法完全依據字符出現概率來構造異字頭的平均長度最短的碼字,有時稱之爲最佳編碼,一般就叫做Huffman編碼(有時也稱爲霍夫曼編碼)。

原理

這個老師上課說過。首先確定文件中各個字符的概率然後將其。按照概率大小。將概率小的放在離根節點較近的地方。

這裏用數字來代表字符的概率然後,首先將概率最小的兩個字符放到一起,將它們的概率合成父結點的概率,然後父結點參與比較再次在備選集中找到比較小的兩兩結合,然後再次合成父結點的概率。

具體實現哈夫曼樹

因爲哈夫曼需要考慮到父結點的影響,所以定義了相關的左孩子右孩子的方法。

package HuffmanTree;

public class HNode {
    /**
     * 節點類
     * @author LiRui
     *
     */
       public String code = "";// 節點的哈夫曼編碼       public String data = "";// 節點的數據
       public int count;// 節點的權值
       public HNode lChild;
       public HNode rChild;

        public HNode() {
        }

        public HNode(String data, int count) {
            this.data = data;
            this.count = count;
        }

        public HNode(int count, HNode lChild, HNode rChild) {
            this.count = count;
            this.lChild = lChild;
            this.rChild = rChild;
        }

        public HNode(String data, int count, HNode lChild, HNode rChild) {
            this.data = data;
            this.count = count;
            this.lChild = lChild;
            this.rChild = rChild;
        }

    }
  • 構造哈夫曼樹
    • 哈夫曼樹的構造,並不需要像創建二叉樹的那麼多的方法,滿足形成哈夫曼編碼的部分就好。分享的那篇博客有相關的內容,我們只需要在此基礎上添加每次創建的的編碼值就可以,左側0右側1就可以。
    • 創建樹,先將結點進行排序,利用結點類中實現的比較方法,分別將左孩子定義爲列表中的倒數第二個,因爲左側編碼爲0,所以讓該結點的編碼爲0;右孩子爲列表中的倒數第一個,因爲右側編碼爲1,所以讓該結點的編碼爲1,雙親結點根據所講內容爲左右結點權重相加之和,把雙親結點加入列表中,然後刪除倒數兩個結點並添加雙親結點,再次進行循環,排序,不斷從列表中把倒數兩個結點刪除,直至跳出循環,此時列表中的結點只剩一個,該結點的左右部分包含了所有按照編碼進行添加的元素內容。
    public HuffmanNode<T> createTree(List<HuffmanNode<T>> nodes) {
          while (nodes.size() > 1) {
              Collections.sort(nodes);
              HuffmanNode<T> left = nodes.get(nodes.size() - 2);//令其左孩子的編碼爲0
              left.setCode("0");
              HuffmanNode<T> right = nodes.get(nodes.size() - 1);//令其右孩子的編碼爲1
              right.setCode("1");
              HuffmanNode<T> parent = new HuffmanNode<T>(null, left.getWeight() + right.getWeight());
              parent.setlChild(left);
              parent.setrChild(right);
              nodes.remove(left);
              nodes.remove(right);
              nodes.add(parent);
          }
          return nodes.get(0);
      }
    • 輸出編碼,利用哈夫曼樹的createTree方法來實現編碼內容,然後輸出每一個結點的編碼值,按照每個分支的0/1進行,左側0右側1進行。
    public List<HuffmanNode<T>> breath(HuffmanNode<T> root) {
          List<HuffmanNode<T>> list = new ArrayList<HuffmanNode<T>>();
          Queue<HuffmanNode<T>> queue = new LinkedList<>();
          if (root != null) {
              queue.offer(root);
              root.getlChild().setCode(root.getCode() + "0");
              root.getrChild().setCode(root.getCode() + "1");
          }
    
          while (!queue.isEmpty()) {
              list.add(queue.peek());
              HuffmanNode<T> node = queue.poll();
              if (node.getlChild() != null)
                  node.getlChild().setCode(node.getCode() + "0");
              if (node.getrChild() != null)
                  node.getrChild().setCode(node.getCode() + "1");
              if (node.getlChild() != null)
                  queue.offer(node.getlChild());
              if (node.getrChild() != null)
                  queue.offer(node.getrChild());
    
          }
          return list;
      }
  • 編碼與解碼
    • 編碼部分,需要按照讀取的內容進行添加每一個的編碼值,變量result即爲編碼之後的內容,將這部分內容寫入一個文件。
     String result = "";
          for(int f = 0; f < sum; f++){
              for(int j = 0;j<letter.length;j++){
                  if(neirong.charAt(f) == letter[j].charAt(0))
                      result += code[j];
              }
          }

    • 解碼部分,需要每次調出編碼內容的一個0/1,然後每讀出一位就要判斷一次是否有對應的編碼值,如果有就輸出,如果沒有就不斷往下讀取內容,並刪除讀出的內容避免重複讀取,直至將編碼的內容全部讀完解碼完成。利用兩個循環,外層循環進行每一次的添加一個編碼位,內層循環將添加一位的內容進行對比,符合就爲編碼值,不符合就需要在添加一位。
    for(int h = list4.size(); h > 0; h--){
              string1 = string1 + list4.get(0);
              list4.remove(0);
              for(int i=0;i<code.length;i++){
                  if (string1.equals(code[i])) {
                      string2 = string2+""+letter[i];
                      string1 = "";
                  }
              }
          }

  • 計算概率和重複次數
    • 從文件中進行讀取,並按照英文字母的種類進行記錄每一個字母的出現次數,這裏我添加了一個記錄空格、逗號和句號,便於編寫文件內容。針對文件中的內容總不能針對每一個位置都要循環確定一遍是否含有那個字母,利用我在實驗室嘗試過的代碼,Collections.frequency();來實現,裏面的兩個形式參數,按照第二個形式參數表現的內容,記錄其在第一個列表內的重複次數,這樣只需要將文件內容一每個字符的形式存放在一個列表中就行,然後進行比對即可。總的字符數目就是每一個字母的重複次數。
    for(int a = 0; a <= getFileLineCount(file); a++){
              String tp = bufferedReader.readLine();
              neirong += tp;
              for(int b = 0; b < tp.length(); b++){
                      list2.add(String.valueOf(tp.charAt(b)));
              }
              Esum[0] += Collections.frequency(list2, list1.get(0));
              Esum[1] += Collections.frequency(list2, list1.get(1));
              Esum[2] += Collections.frequency(list2, list1.get(2));
              Esum[3] += Collections.frequency(list2, list1.get(3));
              Esum[4] += Collections.frequency(list2, list1.get(4));
              Esum[5] += Collections.frequency(list2, list1.get(5));
              Esum[6] += Collections.frequency(list2, list1.get(6));
              Esum[7] += Collections.frequency(list2, list1.get(7));
              Esum[8] += Collections.frequency(list2, list1.get(8));
              Esum[9] += Collections.frequency(list2, list1.get(9));
              Esum[10] += Collections.frequency(list2, list1.get(10));
              Esum[11] += Collections.frequency(list2, list1.get(11));
              Esum[12] += Collections.frequency(list2, list1.get(12));
              Esum[13] += Collections.frequency(list2, list1.get(13));
              Esum[14] += Collections.frequency(list2, list1.get(14));
              Esum[15] += Collections.frequency(list2, list1.get(15));
              Esum[16] += Collections.frequency(list2, list1.get(16));
              Esum[17] += Collections.frequency(list2, list1.get(17));
              Esum[18] += Collections.frequency(list2, list1.get(18));
              Esum[19] += Collections.frequency(list2, list1.get(19));
              Esum[20] += Collections.frequency(list2, list1.get(20));
              Esum[21] += Collections.frequency(list2, list1.get(21));
              Esum[22] += Collections.frequency(list2, list1.get(22));
              Esum[23] += Collections.frequency(list2, list1.get(23));
              Esum[24] += Collections.frequency(list2, list1.get(24));
              Esum[25] += Collections.frequency(list2, list1.get(25));
              Esum[26] += Collections.frequency(list2, list1.get(26));
              Esum[27] += Collections.frequency(list2, list1.get(27));
              Esum[28] += Collections.frequency(list2, list1.get(28));
          }

    • 總個數
    for(int c = 0; c < Esum.length; c++)
              sum += Esum[c];
          System.out.println("總字母個數:" + sum);

    在此部分我用了一個可以確定文件內容行數的方法,便於當文件出現多行的時候的讀寫和編碼、解碼的相關操作。

  • 讀寫文件
    • 讀寫文件的部分只需要調用File相關類進行編寫即可,我直接用了字符流直接將String類型的內容進行添加。
          File file = new File("英文文件.txt");
          File file1 = new File("編碼文件.txt");
          File file2 = new File("解碼文件.txt");
    
          if (!file1.exists() && !file2.exists())
          {
              file1.createNewFile();
              file2.createNewFile();
          }
          FileReader fileReader = new FileReader(file);
          BufferedReader bufferedReader = new BufferedReader(fileReader);
          FileWriter fileWriter1 = new FileWriter(file1);
          FileWriter fileWriter2 = new FileWriter(file2);
          fileWriter1.write(result);
          fileWriter2.write(string2);
          fileWriter1.close();
          fileWriter2.close();

碼雲鏈接

感悟

 哈夫曼是一種用於壓縮的算法,是一種很實用的算法,但可能是個人能力的限制,在具體實現過程中遇見了很多困難,在具體的代碼實現中,借鑑了很多網頁和同學的思路。

參考資料

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章