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編碼(有時也稱爲霍夫曼編碼)。

原理

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

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

具體實現哈夫曼樹

由於哈夫曼須要考慮到父結點的影響,因此定義了相關的左孩子右孩子的方法。node

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);
      }

    這個是構建哈夫曼樹的具體流程,由於須要在結點已經鏈接以後在進行比較因此就將全部出現的字符放在一個鏈表裏便於操做。具體的流程代碼有詳細介紹。git

    /**
       * 將出現的字符建立成單個的結點對象
       */
      private void creatNodes() {
    
          for (int i = 0; i < charList.size(); i++) {
              String data = charList.get(i).c + "";
              int count = charList.get(i).num;
    
              HNode node = new HNode(data, count); // 建立節點對象
              NodeList.add(node); // 加入到節點鏈表
          }
    
      }
    
      /**
       * 構建哈夫曼樹
       */
      private void creatTree() {
    
          while (NodeList.size() > 1) {// 當節點數目大於一時
              // 4.取出權值最小的兩個節點,生成一個新的父節點
              // 5.刪除權值最小的兩個節點,將父節點存放到列表中
              HNode left = NodeList.poll();
              HNode right = NodeList.poll();
    
              // 在構建哈夫曼樹時設置各個結點的哈夫曼編碼
              left.code = "0";
              right.code = "1";
              setCode(left);
              setCode(right);
    
              int parentWeight = left.count + right.count;// 父節點權值等於子節點權值之和
              HNode parent = new HNode(parentWeight, left, right);
    
              NodeList.addFirst(parent); // 將父節點置於首位
              Sort(NodeList); // 從新排序,避免新節點權值大於鏈表首個結點的權值
          }
      }

這個是對結點鏈表進行排序的類算法

/**
     * 升序排序
     *
     * @param nodelist
     */
    private void Sort(LinkedList<HNode> nodelist) {
        for (int i = 0; i < nodelist.size() - 1; i++) {
            for (int j = i + 1; j < nodelist.size(); j++) {
                HNode temp;
                if (nodelist.get(i).count > nodelist.get(j).count) {
                    temp = nodelist.get(i);
                    nodelist.set(i, nodelist.get(j));
                    nodelist.set(j, temp);
                }

            }
        }

    }

下面介紹一個類這個主要的做用是統計各個字符出現的次數,也就是老師所說的機率,而後再進行排序。測試

private void getCharNum(String str) {

        for (int i = 0; i < str.length(); i++) {
            char ch = str.charAt(i); // 從給定的字符串中取出字符
            flag = true;

            for (int j = 0; j < charList.size(); j++) {
                CharData data = charList.get(j);

                if(ch == data.c){
                    // 字符對象鏈表中有相同字符則將個數加1
                    data.num++;
                    flag = false;
                    break;
                }
            }

            if(flag){
                // 字符對象鏈表中沒有相同字符則建立新對象加如鏈表
                charList.add(new CharData(ch));
            }

        }

    }

這是輸出每一個結點編碼的類ui

private void output(HNode node) {

        if (node.lChild == null && node.rChild == null) {
            System.out.println(node.data + ": " + node.code);
        }
        if (node.lChild != null) {
            output(node.lChild);
        }
        if (node.rChild != null) {
            output(node.rChild);
        }
    }

    /**
     * 輸出結果字符的哈夫曼編碼
     */
    public void output() {
        output(root);
    }

這是編碼的過程,主要原理是經過經過一個查找的方法不斷地將傳進的字符串在哈夫曼樹中找到並返回他的哈夫曼編碼最後在輸出出來。this

/**
     * 編碼
     * @param str
     * @return
     */
    public String toHufmCode(String str) {

        for (int i = 0; i < str.length(); i++) {
            String c = str.charAt(i) + "";
            search(root, c);
        }

        return hfmCodeStr;
    }

    /**
     *
     * @param root 哈夫曼樹根節點
     * @param c 須要生成編碼的字符
     */
    private void search(HNode root, String c) {
        if (root.lChild == null && root.rChild == null) {
            if (c.equals(root.data)) {
                hfmCodeStr += root.code; // 找到字符,將其哈夫曼編碼拼接到最終返回二進制字符串的後面
            }
        }
        if (root.lChild != null) {
            search(root.lChild, c);
        }
        if (root.rChild != null) {
            search(root.rChild, c);
        }
    }

解碼部分與之相似。編碼

-     // 保存解碼的字符串
    String result="";
    boolean target = false; // 解碼標記
    /**
     * 解碼
     * @param codeStr
     * @return
     */
    public String CodeToString(String codeStr) {

        int start = 0;
        int end = 1;

        while(end <= codeStr.length()){
            target = false;
            String s = codeStr.substring(start, end);
            matchCode(root, s); // 解碼
            // 每解碼一個字符,start向後移
            if(target){
                start = end;
            }
            end++;
        }

        return result;
    }

    /**
     * 匹配字符哈夫曼編碼,找到對應的字符
     * @param root 哈夫曼樹根節點
     * @param code 須要解碼的二進制字符串
     */
    private void matchCode(HNode root, String code){
        if (root.lChild == null && root.rChild == null) {
            if (code.equals(root.code)) {
                result += root.data; // 找到對應的字符,拼接到解碼字符穿後
                target = true; // 標誌置爲true
            }
        }
        if (root.lChild != null) {
            matchCode(root.lChild, code);
        }
        if (root.rChild != null) {
            matchCode(root.rChild, code);
        }

    }

至於我上傳的測試類中的統計機率的方法是借鑑的李馨雨同窗的。
文件讀寫就太簡單了相信學長學姐們都懂。.net

碼雲連接

感悟

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

參考資料

相關文章
相關標籤/搜索