[Python&DS]- Python實現Huffman編碼壓縮和解壓縮文件

本文主要介紹Huffman編碼、Huffman樹、和如何借助Python實現Huffman編碼樹對文件進行壓縮和解壓縮。下文目錄:

  1. 什么是Huffman編碼;
  2. 如何通過Huffman樹創建Huffman編碼;
  3. Python實現Huffman編碼對文件進行壓縮和解壓縮

一、什么是Huffman編碼



百科給的定義如下:

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

如上,Huffman編碼就是一種效率很高的編碼方式,在理解Huffman編碼之前,我們先來了解一下下面兩種編碼方式:
1.定長編碼方式
例如ASCII碼,8-bit定長編碼,使用8位(一個字節)代表一個字符,比如tea就一定得需要 3x8 = 24位去表示該自字符串,一個含有n個字符的字符串就得需要 nx8 位去表示該字符串;
這樣的編碼沒有考慮到一些字符出現的頻率會高于一些其它的字符,比如在英文26個字母e的出現頻率最高,而z出現的頻率最低,此時我們使用較短位數的編碼來表示e,代價是表示z字符可能需要稍長的編碼,但是這并不妨礙我們達到壓縮的效果:
例如: 使用7位的編碼表示e,9位的編碼表示z,其它字符的編碼不變,由于e出現的頻率比z的高,假設一篇文章當中e出現了10000次,而z出現的次數是10次,那么相比于定長編碼,就節省了 10000 - 10 = 9990位的空間。(這個例子主要展示定長碼的缺點,以及變長碼可以給我們帶來的好處)

2.變長編碼方式
上面提到了,變長編碼方式可以給我帶來節省空間的好處,但是對于使用變長碼編碼的文件,如何去解析該文件得到原文件內容呢,又可能會出現什么問題呢?
舉個例子:
使用 0 表示 e,1 表示 a,01表示t,那么 tea就被編碼成0101,但是我們解析的時候,0101就可以解析成為 tt,eat,eaea,為了解決這個問題,科學家們又引入一個新的概念,那就是前綴碼(Prefix codes 是屬于變長編碼范圍的)

前綴碼定義:任何一個字符的編碼都不能是其它任何字符的編碼的前綴

對于上面的例子,使用前綴碼的意思就是 e 不能用0去表示,應為 0是 t對應的編碼 01 的前綴,如果我們使用 001 去表示e1 表示 a,01表示t,那么 tea就被編碼成為 010011,并且在解析的時候,我們就只能解析成為 tea。

Huffman編碼就是一種能夠使用最短的位數來編碼被編碼文件的前綴碼



如何構造這樣的前綴碼?

二、借助Huffman樹創建Huffman編碼



Huffman編碼將給字母分配編碼。每個字母的編碼的長度取決于在被壓縮文件中對應字母的出現頻率,我們稱之為權重(weight)。每個字母的Huffman編碼是從稱為Huffman編碼樹滿二叉樹(所有節點要么有左右兩個子孩子,要么就沒有子孩子)中得到的。Huffman編碼樹的每一個葉節點對應于一個字母,葉節點的權重 (weight)就是它對應的字母出現的頻率。使用權重的目的是建立的Huffman編碼樹最小外部路徑權重
下圖1將給大家解釋一下什么是最小外部路徑權重,并且Huffman編碼的過程:

圖1 外部路徑權重解釋

那么接下來,我們將解釋圖1 中的第一步構建Huffman編碼樹的過程:

  1. 創建n個初始化的Huffman樹,每個樹只包含單一的葉節點,葉節點紀錄對應的字母和該字母出現的頻率(weight);
  2. 按照weight從小到大對其進行所有的Huffman樹進行排序,取出其中weight最小的兩棵樹,構造一個新的Huffman樹,新的Huffman樹的weight等于兩棵子樹的weight之和,然后再加入到原來的Huffman樹數組當中;
  3. 反復上面的2中的操作,直到該數組當中只剩下一棵Huffman樹,那么最后剩下來的那棵Huffman樹就是我們構造好的Huffman編碼樹;

下面幾個圖將展示對應上圖例子的一個構造Huffman編碼樹的過程,如圖 2和圖 3所示:

圖 2 構造Huffman編碼樹的過程 1
圖 3 構造Huffman編碼樹的過程 2

得到上面的Huffman編碼樹之后,就可以得到每個字符對應的編碼了,方法就是:從根節點找到該葉節點,如果向左子樹前進一步,那么code + = '0',如果向右子樹前進了一步,那么code+= '1',等到達該葉節點,code對應的內容,就是該葉節點對應字符的編碼

自此,你已經知道了如何使用Huffman編碼樹如何給字符分配編碼,并且也知道了如何去構造這樣的Huffman編碼樹,那么接下來就借助Python來實現它吧!

三、Python實現Huffman編碼對文件進行壓縮和解壓縮

文件壓縮的思路如圖 4所示:


圖 4 壓縮文件的思想

文件解壓縮的思路如圖 5所示:


圖 5 解壓縮文件的思想.png

Python代碼實現:
step1:實現Huffman編碼樹及其構造方法,代碼如下:

#-*- coding:utf-8 -*-
#copyright@zhanggugu
import six
import sys

class HuffNode(object):
    """
    定義一個HuffNode虛類,里面包含兩個虛方法:
    1. 獲取節點的權重函數
    2. 獲取此節點是否是葉節點的函數

    """
    def get_wieght(self):
        raise NotImplementedError(
            "The Abstract Node Class doesn't define 'get_wieght'")

    def isleaf(self):
        raise NotImplementedError(
            "The Abstract Node Class doesn't define 'isleaf'")


class LeafNode(HuffNode):
    """
    樹葉節點類
    """
    def __init__(self, value=0, freq=0,):
        """
        初始化 樹節點 需要初始化的對象參數有 :value及其出現的頻率freq
        """
        super(LeafNode, self).__init__()
        # 節點的值
        self.value = value
        self.wieght = freq

    
    def isleaf(self):
        """
        基類的方法,返回True,代表是葉節點
        """
        return True

    def get_wieght(self):
        """
        基類的方法,返回對象屬性 weight,表示對象的權重
        """
        return self.wieght

    def get_value(self):
        """
        獲取葉子節點的 字符 的值
        """
        return self.value


class IntlNode(HuffNode):
    """
    中間節點類
    """
    def __init__(self, left_child=None, right_child=None):
        """
        初始化 中間節點 需要初始化的對象參數有 :left_child, right_chiled, weight
        """
        super(IntlNode, self).__init__()

        # 節點的值
        self.wieght = left_child.get_wieght() + right_child.get_wieght()
        # 節點的左右子節點
        self.left_child = left_child
        self.right_child = right_child


    def isleaf(self):
        """
        基類的方法,返回False,代表是中間節點
        """
        return False

    def get_wieght(self):
        """
        基類的方法,返回對象屬性 weight,表示對象的權重
        """
        return self.wieght

    def get_left(self):
        """
        獲取左孩子
        """
        return self.left_child

    def get_right(self):
        """
        獲取右孩子
        """
        return self.right_child


class HuffTree(object):
    """
    huffTree
    """
    def __init__(self, flag, value =0, freq=0, left_tree=None, right_tree=None):

        super(HuffTree, self).__init__()

        if flag == 0:
            self.root = LeafNode(value, freq)
        else:
            self.root = IntlNode(left_tree.get_root(), right_tree.get_root())


    def get_root(self):
        """
        獲取huffman tree 的根節點
        """
        return self.root

    def get_wieght(self):
        """
        獲取這個huffman樹的根節點的權重
        """
        return self.root.get_wieght()

    def traverse_huffman_tree(self, root, code, char_freq):
        """
        利用遞歸的方法遍歷huffman_tree,并且以此方式得到每個 字符 對應的huffman編碼
        保存在字典 char_freq中
        """
        if root.isleaf():
            char_freq[root.get_value()] = code
            print ("it = %c  and  freq = %d  code = %s")%(chr(root.get_value()),root.get_wieght(), code)
            return None
        else:
            self.traverse_huffman_tree(root.get_left(), code+'0', char_freq)
            self.traverse_huffman_tree(root.get_right(), code+'1', char_freq)



def buildHuffmanTree(list_hufftrees):
    """
    構造huffman樹
    """
    while len(list_hufftrees) >1 :

        # 1. 按照weight 對huffman樹進行從小到大的排序
        list_hufftrees.sort(key=lambda x: x.get_wieght()) 
               
        # 2. 跳出weight 最小的兩個huffman編碼樹
        temp1 = list_hufftrees[0]
        temp2 = list_hufftrees[1]
        list_hufftrees = list_hufftrees[2:]

        # 3. 構造一個新的huffman樹
        newed_hufftree = HuffTree(1, 0, 0, temp1, temp2)

        # 4. 放入到數組當中
        list_hufftrees.append(newed_hufftree)

    # last.  數組中最后剩下來的那棵樹,就是構造的Huffman編碼樹
    return list_hufftrees[0]

上面代碼詳細展示了如何構造Huffman編碼樹,代碼當中的注釋已經足夠詳細,并且算法在第二部分已經進行了詳細的講解,在此就不贅述!

step2:壓縮文件函數compress() 的實現,其主要思路就如圖 4所示,其代碼參考github 工程:

step3:解壓縮文件函數decompress() 的實現,其主要思路就如圖 5所示,其代碼參考github 工程:

對代碼中解壓縮和壓縮對齊處理方法說明:

圖 6 解壓縮和壓縮過程中字節對齊處理方法



測試:

圖 7 原文件大小
圖 8 壓縮之后的文件大小
圖 9 解壓縮文件之后的內容

原則上,該程序可以對任意格式的文件進行壓縮,我自己也試過將xsl,word的文件進行壓縮,大概可以得到30%左右的空間節省

四、總結

看完本文,希望你對Huffman編碼方法能夠有一個清晰的了解,并且知道如何使用Python實現Huffman編碼樹并且對文件進行解壓縮。
完整源碼請參考github工程

參考資料:

[1] - Data Structures and Algorithm Analysis in C++ Third Edition by Clifford A. Shaffer (<<數據結構與算法分析 C++版第三版>> 作者Clifford A. Shaffer , 電子工業出版社 )

聲明:
  1. 聯系作者,新浪微博私信 @谷谷_z
  2. 如果在文章當中發現有描述錯誤的地方,還請您不吝指出,萬分感謝!
  3. 此文章系本人原創作品,轉發請注明出處!
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容