20172327-哈夫曼編碼測試

20172327-哈夫曼編碼測試

哈夫曼編碼與哈夫曼樹

  • 哈夫曼編碼:又稱霍夫曼編碼,是一種編碼方式,哈夫曼編碼是可變字長編碼(VLC)的一種。Huffman於1952年提出一種編碼方法,該方法徹底依據字符出現機率來構造異字頭的平均長度最短的碼字,有時稱之爲最佳編碼,通常就叫作Huffman編碼(有時也稱爲霍夫曼編碼)。
  • 哈夫曼樹:給定n個權值做爲n個葉子結點,構造一棵二叉樹,若帶權路徑長度達到最小,稱這樣的二叉樹爲最優二叉樹,也稱爲哈夫曼樹(Huffman Tree)。哈夫曼樹是帶權路徑長度最短的樹,權值較大的結點離根較近。

哈夫曼編碼步驟

下面咱們以【五、八、四、十一、九、13】爲例來畫出哈夫曼樹(數字大小表明權重大小,越大的權重越大)html

  • 第一步:按從小到大排序。

【五、八、四、十一、九、13】→【四、五、八、九、十一、13】node

  • 第二步:選最小兩個數畫出一個樹,最小數爲4和5。

給定的四、五、八、九、十一、13爲白色, 紅色的9爲4+5,與給定的白9無關,新序列爲:【紅9(含子節點四、5)、八、九、十一、13】
git

  • 以後一直重複第1、第二步:排序而後取兩個最小值。實際就是一個遞歸過程

排序:
算法

  • 取兩個最小數8和9:
    數組

  • 排序:
    數據結構

  • 取兩個最小數9和11:
    測試

  • 排序,而後取兩個最小數13和17:
    優化

取兩個最小數20和30:
ui

哈夫曼編碼主要用途

哈夫曼研究這種最優樹的目的是爲了解決當年遠距離通訊(主要是電報)的數據傳輸的最優化問題。編碼

好比咱們有一段文字「BADCADFEED」,顯然用二進制數字(0和1)表示是很天然的想法。

這樣真正傳輸的數據就是「001000011010000011101100100011」,對方接收時一樣按照3位一組解碼。若是一篇文章很長,這樣的二進制串也很是的可怕。並且事實上,每一個字母或者漢子的出現頻率是不一樣的。

假設六個字母的頻率爲A 27,B 8, C 15, D 15 , E 30, F 5,合起來正好是100%,那就意味着咱們徹底能夠用哈夫曼樹來規劃它們。

左圖爲構造哈夫曼樹的過程的權值顯示。右圖爲將權值左分支改成0,右分支改成1後的哈夫曼樹。

咱們對這六個字母用其從樹根到葉子所通過的路徑的0或1來編碼,能夠獲得下表:

也就是說咱們的數據被壓縮了,節約了大概17%的存儲或傳輸成本。隨着字符的增長和多字符權重的不一樣,這種壓縮會更顯出優點來。

實驗內容

哈夫曼編碼測試
設有字符集: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)把實驗結果截圖上傳到雲班課
滿分:6分。
酌情打分。

重點代碼

構造哈夫曼樹:

public static HuffmanNode createTree(List<HuffmanNode> nodes) {

        // 只要nodes數組中還有2個以上的節點
        while (nodes.size() > 1)
        {
            quickSort(nodes);

            //獲取權值最小的兩個節點
            HuffmanNode left = nodes.get(nodes.size()-1);
            left.setCodeNumber(0+"");
            HuffmanNode right = nodes.get(nodes.size()-2);
            right.setCodeNumber(1+"");

            //生成新節點,新節點的權值爲兩個子節點的權值之和
            HuffmanNode parent = new HuffmanNode(null, left.weight + right.weight);

            //讓新節點做爲兩個權值最小節點的父節點
            parent.leftChild = left;
            parent.rightChild = right;

            //刪除權值最小的兩個節點
            nodes.remove(nodes.size()-1);
            nodes.remove(nodes.size()-1);

            //將新節點加入到集合中
            nodes.add(parent);
        }
        return nodes.get(0);
    }

    /**
     * 將指定集合中的i和j索引處的元素交換
     *
     * @param nodes
     * @param i
     * @param j
     */
    private static void swap(List<HuffmanNode> nodes, int i, int j) {

        HuffmanNode tmp;
        tmp = nodes.get(i);

        nodes.set(i, nodes.get(j));
        nodes.set(j, tmp);

    }

    /**
     * 實現快速排序算法,用於對節點進行排序
     * @param nodes
     * @param start
     * @param end
     */
    private static void subSort(List<HuffmanNode> nodes, int start, int end)
    {

        if (start < end)
        {
            // 以第一個元素做爲分界值
            HuffmanNode base = nodes.get(start);
            // i從左邊搜索,搜索大於分界值的元素的索引
            int i = start;
            // j從右邊開始搜索,搜索小於分界值的元素的索引
            int j = end + 1;
            while (true)
            {
                // 找到大於分界值的元素的索引,或者i已經到了end處
                while (i < end && nodes.get(++i).weight >= base.weight);
                // 找到小於分界值的元素的索引,或者j已經到了start處
                while (j > start && nodes.get(--j).weight <= base.weight);
                if (i < j)
                {
                    swap(nodes, i, j);
                }
                else
                    break;
            }

            swap(nodes, start, j);

            //遞歸左邊子序列
            subSort(nodes, start, j - 1);
            //遞歸右邊子序列
            subSort(nodes, j + 1, end);
        }
    }

    public static void quickSort(List<HuffmanNode> nodes)
    {
        subSort(nodes, 0, nodes.size()-1);
    }

    //層序遍歷
    public static List<HuffmanNode> levelTraversal(HuffmanNode root)
    {
        Queue<HuffmanNode> queue = new ArrayDeque<HuffmanNode>();
        List<HuffmanNode> list = new ArrayList<HuffmanNode>();

        if(root!=null)
        {
            //將根元素加入「隊列」
            queue.offer(root);
            root.leftChild.setCodeNumber(root.getCodeNumber()+"0");
            root.rightChild.setCodeNumber(root.getCodeNumber()+"1");
        }

        while(!queue.isEmpty())
        {
            //將該隊列的「隊尾」元素加入到list中
            list.add(queue.peek());

            HuffmanNode tree = queue.poll();
            //若是左子節點不爲null,將它加入到隊列
            if(tree.leftChild != null)
            {
                queue.offer(tree.leftChild);
                tree.leftChild.setCodeNumber(tree.getCodeNumber()+"0");
            }

            //若是右子節點不爲null,將它加入到隊列
            if(tree.rightChild != null)
            {
                queue.offer(tree.rightChild);
                tree.rightChild.setCodeNumber(tree.getCodeNumber()+"1");
            }
        }

        return list;

    }

計算字母出現次數:

//層序遍歷顯示構建的哈弗曼樹
        char[] chars = new char[a];
        int[] times = new int[a];
        Iterator<Character> pl2 = counter.keySet().iterator();

        for (int i=0;i<=a;i++ )
        {
            if (pl2.hasNext())
            {
                chars[i] = pl2.next();
                times[i] = counter.get(chars[i]);
            }

        }
        List<HuffmanNode> list = new ArrayList<HuffmanNode>();

        for(int i = 0;i<a;i++)
        {
            System.out.print(chars[i]+"出現次數爲:"+times[i]+"   \n");
            list.add(new HuffmanNode(chars[i]+"",times[i]));
        }

讀取文件:

File file = new File("E:\\IDES_Project\\JSSD\\src\\HaffmanCoding\\test.txt");

        if (!file.exists()) {
            throw new Exception("文件不存在");
        }

        BufferedReader fin = new BufferedReader(new FileReader(file));
        String line;


        Map<Character, Integer> counter = new HashMap<Character, Integer>();

        int total=0;


        while ((line = fin.readLine()) != null)
        {
            int len = line.length();
            for (int i = 0; i < len; i++)
            {
                char c = line.charAt(i);
                if (( (c >= 'a' && c <= 'z'&& c == ' ')))
                {
                    continue;
                }
                if (counter.containsKey(c))
                {
                    counter.put(c, counter.get(c) + 1);
                }
                else
                {
                    counter.put(c, 1);
                }
            }
        }

        fin.close();

實驗結果截圖

碼雲連接

感悟

此次實驗給人的感受有點難,不是很懂,因此寫起來很迷。

參考資料

哈夫曼編碼
哈夫曼編碼與哈夫曼樹
【數據結構】哈夫曼樹及哈夫曼編碼

相關文章
相關標籤/搜索