1. 程式人生 > >演算法#19--霍夫曼壓縮(資料壓縮)

演算法#19--霍夫曼壓縮(資料壓縮)

定義

我們現在來學習一種能夠大幅壓縮自然語言檔案空間(以及許多其他型別檔案)的資料壓縮技術。

它的主要思想是放棄文字檔案的普通儲存方式:不在使用7位或8位二進位制數表示每一個字元,而是用較少的位元表示出現頻率高的字元,用較多的位元表示出現頻率低的字元。

簡而言之,不在用ASCII編碼表示,而是用較短的字首碼錶示。

字首碼

什麼是字首碼?如果所有字元編碼都不會成為其他字元編碼的字首,符合這種規則的叫字首碼。

比如,如果A的編碼為0,R的編碼為00,那麼A的編碼是R的字首,這不屬於字首碼。那麼字首碼的例子是什麼樣的呢?如下:

所有的字首碼的解碼方式都和它一樣,是唯一的。因此字首碼被廣泛應用於實際生產中。注意,像7位ASCII編碼編碼這樣的定長編碼也是字首碼。

霍夫曼單詞查詢樹

表示字首碼的一種簡便方法就是使用單詞查詢樹。

任意含有M個空連結的單詞查詢樹都沒M個字元定義了一種字首碼方法:我們將空連結替換為指向葉子結點(含有兩個空連結的結點)的連結,每個葉子結點都含有一個需要編碼的字元。這樣,沒個字元的編碼都是從根結點到該結點的路徑表示的位元字串,其中左連結表示0,右連結表示1.

如果構造這樣一棵樹?

首先,樹的結點包含left和right,和一個字元頻率變數freq,以及字元ch。以下為構造一顆霍夫曼單詞查詢樹的過程:

  1. 將需要被編碼的字元放在葉子結點中並在每個結點中維護了一個名為freq的例項變數來表示以它為根結點的子樹種的所有字元出現的頻率。
  2. 建立一片由許多隻有一個結點(即葉子結點)的樹所組成的森林。每棵樹都表示輸入流的一個字元,每個結點中的freq表示它在輸入流中的出現頻率。
  3. 找到兩個頻率最小的結點,然後建立一個以二者為子結點的新結點(新結點的頻率為它的兩個子結點的頻率之和)。
  4. 不斷重複第3過程,最終所有的結點會被合併為一顆單獨的單詞查詢樹。

特點:

  • 樹的葉子結點包含freq和字元。
  • 頻率高的離根結點最近,頻率低的在樹的底層。
  • 根結點頻率值等於輸入中的字元數量。
  • 該樹表示的編碼壓縮比其他樹更多,是一種最優的字首碼

壓縮

對於任意單詞查詢樹,都能產生一張將樹中的字元和位元字串(用由0和1組成的String字串表示)相對應的編譯表

。其實就是字元和它的位元字串的符號表。在這裡我們用st[]數字表示。在構造符號表時buildCode()遞迴遍歷整棵樹,併為每個結點維護一條從根結點到它的路徑所對應的二進位制字串(左連結表示0,右連結表示1)。到達一個葉子結點後,就將結點的編碼設為該二進位制字串。如下圖的編譯表:

然後,壓縮就很簡單了,只需要在其中找到輸入字元所對應的編碼即可。

上圖字串ABRACADABRA!的編碼為:首先是0(A的編碼),然後是111(B的編碼),然後是110(R的編碼),最後得到完整編碼為0111110010110100011111001010.

解壓

首先readTrie()將霍夫曼單詞查詢樹編碼為的位元流構造為霍夫曼查詢樹。然後讀取霍夫曼壓縮碼,根據該編碼從根結點向下移動(讀取一個位元,為0移動到左結點,為1移動到右結點)。當遇到葉子結點後,輸出該結點的字元並重新回到根結點。

例如壓縮ABRACADABRA!後的編碼為:0111110010110100011111001010,其單詞查詢樹的位元流為:01010000010010100010001001000011010000110101010010101000010。

首先由樹的位元流構造霍夫曼查詢樹(得如下樹),然後解碼編碼。第一個為0,所以移動到左子結點,輸出A;回到根,然後連續三個1,即向右移動3次,輸出B;回到根,然後兩個1,一個0,即向右移動兩次,向左移動一次,輸出R。如此重複,最後得到ABRACADABRA!

實現程式碼


/**
 * 霍夫曼壓縮
 * @author nicholas.tang
 *
 */
public class Huffman 
{
    public static int R = 256;
    public static final int asciiLength = 8;//ascii碼,一個字元等於8個bit
    public static String bitStreamOfTrie = "";//使用前序遍歷將霍夫曼單詞查詢樹編碼為位元流
    public static int lengthOfText = 0;//要壓縮文字的長度

    private static String next = "";//讀取霍夫曼單詞查詢樹用到的next字串,它指向下一個位元流的子字串,用了遍歷位元流

    /**
     * 霍夫曼單詞查詢樹中的結點
     * @author nicholas.tang
     *
     */
    private static class Node implements Comparable<Node>
    {
        private char ch;    //內部結點不會使用該變數
        private int freq;   //展開過程不會使用該變數
        private final Node left, right;

        Node(char ch, int freq, Node left, Node right)
        {
            this.ch = ch;
            this.freq = freq;
            this.left = left;
            this.right = right;
        }

        public boolean isLeaf()
        {
            return left == null && right == null;
        }

        public int compareTo(Node that)
        {
            return this.freq - that.freq;
        }
    }

    /**
     * 解壓
     * @param bitSteam 霍夫曼單詞查詢樹編碼為的位元流
     * @param length 文字長度
     * @param huffmanCode 霍夫曼編碼
     * @return 解壓後的文字
     */
    public static String expand(String bitSteam, int length, String huffmanCode)
    {   
        Node root = null;
        if(bitSteam == "")
        {
            return "";
        }
        else
        {
            root = readTrie(bitSteam);  
        }

        int j = 0;
        String text = "";
        for(int i = 0; i < length; i++)
        {
            Node x = root;
            while(!x.isLeaf())
            {
                if(huffmanCode.substring(j, j+1).equals("1"))
                {
                    x = x.right;
                }
                else
                {
                    x = x.left;
                }
                j++;
            }
            text +=x.ch;
        }   
        return text;
    }

    /**
     * 壓縮
     * @param s 要壓縮的文字
     * @return 壓縮後,反饋的霍夫曼編碼
     */
    public static String compress(String s)
    {
        //讀取輸入
        char[] input = s.toCharArray();

        //統計頻率
        int[] freq = new int[R];
        for(int i = 0; i < input.length; i++)
        {
            freq[input[i]]++;
        }

        //構造霍夫曼編碼樹
        Node root = buildTrie(freq);

        //遞迴地構造編譯表
        String[] st = new String[R];
        buildCode(st, root, "");

        //遞迴地列印解碼用的單詞查詢樹,即位元流
        writeTrie(root);

        //列印字元總數
        lengthOfText = input.length;

        //使用霍夫曼編碼處理輸入
        String codeOfHuffman = "";
        for(int i = 0; i < input.length; i ++)
        {
            String code = st[input[i]];         
            for(int j = 0; j < code.length(); j++)
            {
                if(code.charAt(j) == '1')
                {
                    codeOfHuffman += '1';
                }
                else
                {
                    codeOfHuffman += '0';
                }
            }
        }
        return codeOfHuffman;//返回霍夫曼編碼
    }

    /**
     * 構建霍夫曼單詞查詢樹
     * @param freq 字元在文字出現的頻率
     * @return 霍夫曼單詞查詢樹
     */
    private static Node buildTrie(int[] freq)
    {
        MinPQ<Node> pq = new MinPQ<Node>(R);
        for(char c = 0; c < R; c++)
        {
            if(freq[c] > 0)
            {
                pq.insert(new Node(c, freq[c], null, null));
            }
        }
        while(pq.size() > 1)
        {//合併兩顆頻率最小的樹
            Node x = pq.delMin();
            Node y = pq.delMin();
            Node parent = new Node('\0', x.freq + y.freq, x, y);
            pq.insert(parent);
        }
        return pq.delMin();
    }

    /**
     * 構造編譯表
     * @param st 編譯表
     * @param x 霍夫曼單詞查詢樹中的結點
     * @param s 編譯表內容
     */
    private static void buildCode(String[] st, Node x, String s)
    {
        if(x.isLeaf())
        {
            st[x.ch] = s;
            return;
        }
        buildCode(st, x.left, s + '0');
        buildCode(st, x.right, s + '1');
    }

    /**
     * 使用前序遍歷將霍夫曼單詞查詢樹編碼為位元流
     * @param x 霍夫曼單詞查詢樹
     */
    private static void writeTrie(Node x)
    {//輸出單詞查詢樹的位元字串
        if(x.isLeaf())
        {
            bitStreamOfTrie += '1';
            String temp = Integer.toBinaryString(x.ch);
            int n = asciiLength - temp.length();
            temp = repeatStrings("0", n) + temp;
            bitStreamOfTrie += temp;
            return ;
        }
        bitStreamOfTrie += '0';
        writeTrie(x.left);
        writeTrie(x.right);
    }   

    /**
     * 用位元流構造霍夫曼單詞查詢樹
     * @param s 位元流
     * @return 霍夫曼單詞查詢樹
     */
    private static Node readTrie(String s)
    {           
        if(s.substring(0, 1).equals("1"))
        {  
            int value = Integer.parseInt(s.substring(1, 1 + asciiLength),2);
            next = s.substring(1 + asciiLength);
            return new Node((char)value, 0, null, null);            
        }
        else
        {           
            next = s.substring(1);
            return new Node('\0', 0, readTrie(next), readTrie(next));
        }       
    }

    /**
     * 重複字串
     * @param s 需要重複的字串
     * @param n 重複次數
     * @return 重複後的字串
     */
    private static String repeatStrings(String s , int n)
    {
          String temp = "";
          for(int i = 0; i < n;i++)
          {
              temp += s;
          }
          return temp;
    }

    public static void main(String[] args) 
    {
        String text = "ABRACADABRA!";
        System.out.println("Input text: " + text);

        String HuffmanCode = Huffman.compress(text);
        int bitsOfText = Huffman.lengthOfText * Huffman.asciiLength;
        String bitStream = Huffman.bitStreamOfTrie;
        double compressionRatio = 1.0 * HuffmanCode.length() / bitsOfText;

        System.out.println("Huffman Code: " + HuffmanCode);     
        System.out.println("BitStream: " + bitStream);
        System.out.println("Huffman Code length(bit): " + HuffmanCode.length());
        System.out.println("Length of text(bit): " + bitsOfText);
        System.out.println("Compression ratio: " + compressionRatio * 100 + "%");

        String expandText = Huffman.expand(bitStream, lengthOfText, HuffmanCode);
        System.out.println("Expand text: " + expandText);
    }
}

輸出:

Input text: ABRACADABRA!
Huffman Code: 0111110010110100011111001010
BitStream: 01010000010010100010001001000011010000110101010010101000010
Huffman Code length(bit): 28
Length of text(bit): 96
Compression ratio: 29.166666666666668%
Expand text: ABRACADABRA!

相關推薦

演算法#19--壓縮資料壓縮

定義 我們現在來學習一種能夠大幅壓縮自然語言檔案空間(以及許多其他型別檔案)的資料壓縮技術。 它的主要思想是放棄文字檔案的普通儲存方式:不在使用7位或8位二進位制數表示每一個字元,而是用較少的位元表示出現頻率高的字元,用較多的位元表示出現頻率低的字元。 簡

資料結構

設二叉樹具有n個帶權值的葉子節點,從根節點到葉子節點的路徑長度與對應葉子節點權值的乘積之和叫做二叉樹的“帶權路徑長度”。 對於一組帶有權值的葉子節點,帶權路徑長度最小的二叉樹叫做“最優二叉樹”(例如哈夫曼樹,哈夫曼樹是最優二叉樹,最優二叉樹不一定是哈夫曼樹)。

編碼Huffman Coding

霍夫曼編碼(Huffman Coding)是一種編碼方法,霍夫曼編碼是可變字長編碼(VLC)的一種。 霍夫曼編碼使用變長編碼表對源符號(如檔案中的一個字母)進行編碼,其中變長編碼表是通過一種評估來源符號出現機率的方法得到的,出現機率高的字母使用較短的編碼,反之出現機率低的則

資料結構與演算法 (七) 哈Huffman與哈編碼

1.演算法思想          哈夫曼樹又稱最優二叉樹,是一種帶權路徑長度最短的二叉樹。所謂樹的帶權路徑長度,就是樹中所有的葉結點的權值乘上其到根結點的路徑長度(若根結點為0層,葉結點到根結點的路徑長度為葉結點的層數)。樹的路徑長度是從樹根到每

資料結構實驗之二叉樹六:哈編碼SDUT 3345

題解:離散中的“最小生成樹(最優樹)”。 #include <bits/stdc++.h> using namespace std; void qusort(int l, int r, int a[]) { int x = a[l]; int i = l, j =

編碼C++ 優先佇列

霍夫曼編碼 一般採用字首編碼 -- -- 對字符集進行編碼時,要求字符集中任一字元的編碼都不是其它字元的編碼的字首,這種編碼稱為字首(編)碼。 演算法思想: 構造哈夫曼樹非常簡單,將所有的節點放到一個佇列中,用一個節點替換兩個頻率最低的節點,新節點的頻率就是這兩個節點的頻率之和。這

資料結構---哈詳解

main.cpp #include”HuffmanTree.h”int  main()  {      HuffmanTree HT;      int *w,i,n;   unsigned   in

c語言資料結構

for(i=1;i<=len;i++){if(ht[i].w<min2&&ht[i].p==0&&i!=*s1){min2=ht[i].w;*s2=i;}}//找到另一個最小的元素 } hfmsNode *createhfms(int n)//構造哈夫曼樹 {  

資料結構之哈c語言

 哈夫曼樹 利用靜態連結串列建立赫夫曼樹,建樹過程中要求左子樹權值小於右子樹權值,求各結點的編碼。要求:葉子結點的個數n及結點值由鍵盤錄入。本題給出程式程式碼,要求修改以滿足測試要求.  #include "stdio.h" #include "malloc.h" #in

貪心演算法之哈編碼C語言實現

如題 問題描述:現有一個文字檔案,其中包含的字元資料出現的次數各不相同,先要求對該文字中包含的字元進行編碼,使文字佔用的位數更小。 問題分析 我們知道檔案的儲存都是以二進位制數表示的,如:字元c可以表示為010101…之類的。因 為不同的作業

貪心演算法--哈編碼java實現

package org.orithmetic.greedySelector; public class Node<T> implements Comparable<Node<T>>{          private T data;   

貪心演算法實現編解碼

版權所有,轉載請註明出處! 霍夫曼編碼是一種被廣泛應用而且非常有效的資料壓縮技術,根據待壓縮資料的特徵,一個可壓縮掉20%~90%。這裡考慮的資料指的是字串序列。要理解霍夫曼編碼,先要理解霍夫曼樹,即最優二叉樹,是一類帶權路徑長度最短的樹。 路徑是指從樹中一個結點到另一

最優二叉樹簡介

一、霍夫曼編碼       說到霍夫曼樹,就不得不提霍夫曼編碼(Huffman Coding)。霍夫曼編碼是可變字長編碼(VLC)的一種。David.A.Huffman於1952年提出該編碼方法,即完

資料結構-哈python實現

好,前面我們介紹了一般二叉樹、完全二叉樹、滿二叉樹,這篇文章呢,我們要介紹的是哈夫曼樹。 哈夫曼樹也叫最優二叉樹,與哈夫曼樹相關的概念還有哈夫曼編碼,這兩者其實是相同的。哈夫曼編碼是哈夫曼在1952年提出的。現在哈夫曼編碼多應用在文字壓縮方面。接下來,我們就來介紹哈夫曼樹到底是個什麼東西?哈夫曼編碼又是什麼,

OpenCV變換系列前篇-經典線變換

前言:最近新來的的我的大學室友(現在也是我的學弟)在研究霍夫線變換,我之前只是知道這玩意可以拿來做直線檢測,並沒有深入研究,那既然提到了,還是按照我們的老規矩,原理,示例以及OpenCV這一套流程走下來。 菜鳥一枚,好多寫的不好,有點囉嗦,見諒 主要參考部落格:

Huffman-Tree的構造及應用

  本文以學習筆記的性質談一談哈夫曼樹較為嚴謹的貪心做法。 哈夫曼樹的構造   有這樣一棵k叉樹,它的葉子節點有權值,第i個葉子節點權值為wi(wi>0)wi(wi>0),他的深度為lili,要求最小化∑wi∗li∑wi∗li,這樣問題的

資料結構與演算法】 利用哈樹進行檔案壓縮 部分借鑑網上內容

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

c語言實現赫樹的構建以及生成赫編碼資料結構》演算法6.12

這個程式是根據《資料結構》演算法6.12用c語言實現的程式,赫夫曼樹就不多說了,直接看程式碼,程式碼上都有註釋。 下面程式碼: #include<stdio.h> #include<iostream> #include<stdlib.h>

GZIP壓縮原理分析32——第五章 Deflate演算法詳解五23 動態哈編碼分析12構建哈04

*構建literal/length樹 部落格http://www.cnblogs.com/esingchan/p/3958962.html中這樣說道:“ZIP之所以是通用壓縮,它實際上是針對位元組作為

GZIP壓縮原理分析29——第五章 Deflate演算法詳解五20 動態哈編碼分析09構建哈01

現在已經完成了對字串“As mentioned above,there are many kinds of wireless systems other than cellular.”進行壓縮的第一步