資料結構-哈夫曼樹(python實現)

浩然haoran發表於2019-07-22

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

哈夫曼樹(Huffman Tree)

給定n個權值作為n個葉子結點,構造一棵二叉樹,若該樹的帶權路徑長度達到最小,稱這樣的二叉樹為最優二叉樹,也稱為哈夫曼樹(Huffman Tree)。哈夫曼樹是帶權路徑長度最短的樹,權值較大的結點離根較近。

首先,我們有這樣一些資料:

sourceData = [('a', 8), ('b', 5), ('c', 3), ('d', 3), ('e', 8), ('f', 6), ('g', 2), ('h', 5), ('i', 9), ('j', 5), ('k', 7), ('l', 5), ('m', 10), ('n', 9)]

每一個資料項是一個元組,元組的第一項是資料內容,第二項是該資料的權重。也就是說,用於構建哈夫曼樹的資料是帶權重的。假設這些資料裡面的字母a-n的權重是根據這些字母在y一個文字出出現的概率計算得出的,字母出現的概率越高,則該字母的權重越大。例如字母 a 的權重為 8 .

好,拿到資料我們就可以來構建哈夫曼樹了。

  1. 首先,找出所有元素中權重最小的兩個元素,即g(2)和c(3),
  2. 以g和c為子節點構建二叉樹,則構建的二叉樹的父節點的權重為 2+3 = 5.
  3. 從除g和c以外剩下的元素和新構建的權重為5的節點中選出權重最小的兩個節點,
  4. 進行第 2 步操作。

以此類推,直至最後合成一個二叉樹就是哈夫曼樹。

我們用圖例來表示一下:

  1. 資料結構-哈夫曼樹(python實現)
  2. 資料結構-哈夫曼樹(python實現)
  3. 資料結構-哈夫曼樹(python實現)
  4. 資料結構-哈夫曼樹(python實現)
  5. 資料結構-哈夫曼樹(python實現)
  6. 資料結構-哈夫曼樹(python實現)
  7. 資料結構-哈夫曼樹(python實現)
  8. 資料結構-哈夫曼樹(python實現)
  9. 資料結構-哈夫曼樹(python實現)
  10. 資料結構-哈夫曼樹(python實現)
  11. 資料結構-哈夫曼樹(python實現)

好,這裡我們的哈夫曼樹就構建好了,節點中字母后面的數字表示該字母的權重,就是前面給定的資料。在這裡我要強調的是,同樣的資料建立的哈夫曼樹並不是唯一的,所以只要按照規則一步一步沒有出錯,你的哈夫曼樹就是正確的。

我們現在將訪問左節點定義為0,訪問右節點定義為1.則我們現在訪問字母a,則它的編碼為0110,訪問字母n的編碼為111,這個編碼就是哈夫曼編碼。

通過比對不同字母的哈夫曼編碼,你發現了什麼?

權重越大的字母對應的哈夫曼編碼越短,權重越小的字母對應的哈夫曼編碼則越長。也就是說文字中出現概率大的字母編碼短,出現概率小的字母編碼長。通過這種編碼方式來表示文字中的字母,那所得整個文字的編碼長度也會縮短。

這就是哈夫曼樹也就是哈夫曼編碼在文字壓縮中的應用。

下面我們用程式碼來實現:

定義一個二叉樹類:

class BinaryTree:
    def __init__(self, data, weight):
        self.data = data
        self.weight = weight
        self.left = None
        self.right = None

獲取節點列表中權重最小的兩個節點:

# 定義獲取列表中權重最大的兩個節點的方法:
def min2(li):
    result = [BinaryTree(None, float('inf')), BinaryTree(None, float('inf'))]
    li2 = []
    for i in range(len(li)):
        if li[i].weight < result[0].weight:
            if result[1].weight != float('inf'):
                li2.append(result[1])
            result[0], result[1] = li[i], result[0]
        elif li[i].weight < result[1].weight:
            if result[1].weight != float('inf'):
                li2.append(result[1])
            result[1] = li[i]
        else:
            li2.append(li[i])
    return result, li2

定義生成哈夫曼樹的方法:

def makeHuffman(source):
    m2, data = min2(source)
    print(m2[0].data, m2[1].data)
    left = m2[0]
    right = m2[1]

    sumLR = left.weight + right.weight
    father = BinaryTree(None, sumLR)
    father.left = left
    father.right = right
    if data == []:
        return father
    data.append(father)
    return makeHuffman(data)

定義廣度優先遍歷方法:

# 遞迴方式實現廣度優先遍歷
def breadthFirst(gen, index=0, nextGen=[], result=[]):

    if type(gen) == BinaryTree:
        gen = [gen]
    result.append((gen[index].data, gen[index].weight))
    if gen[index].left != None:
        nextGen.append(gen[index].left)
    if gen[index].right != None:
        nextGen.append(gen[index].right)

    if index == len(gen)-1:
        if nextGen == []:
            return
        else:
            gen = nextGen
            nextGen = []
            index = 0
    else:
        index += 1
    breadthFirst(gen, index, nextGen,result)

    return result

輸入資料:

# 某篇文章中部分字母根據出現的概率規定權重
sourceData = [('a', 8), ('b', 5), ('c', 3), ('d', 3), ('e', 8), ('f', 6), ('g', 2), ('h', 5), ('i', 9), ('j', 5), ('k', 7), ('l', 5), ('m', 10), ('n', 9)]
sourceData = [BinaryTree(x[0], x[1]) for x in sourceData]

建立哈夫曼樹並進行廣度優先遍歷:

huffman = makeHuffman(sourceData)
print(breadthFirst(huffman))

OK ,我們的哈夫曼樹就介紹到這裡了,你還有什麼不懂的問題記得留言給我哦。

相關文章