好,前面我們介紹了一般二叉樹、完全二叉樹、滿二叉樹,這篇文章呢,我們要介紹的是哈夫曼樹。
哈夫曼樹也叫最優二叉樹,與哈夫曼樹相關的概念還有哈夫曼編碼,這兩者其實是相同的。哈夫曼編碼是哈夫曼在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 .
好,拿到資料我們就可以來構建哈夫曼樹了。
- 首先,找出所有元素中權重最小的兩個元素,即g(2)和c(3),
- 以g和c為子節點構建二叉樹,則構建的二叉樹的父節點的權重為 2+3 = 5.
- 從除g和c以外剩下的元素和新構建的權重為5的節點中選出權重最小的兩個節點,
- 進行第 2 步操作。
以此類推,直至最後合成一個二叉樹就是哈夫曼樹。
我們用圖例來表示一下:
好,這裡我們的哈夫曼樹就構建好了,節點中字母后面的數字表示該字母的權重,就是前面給定的資料。在這裡我要強調的是,同樣的資料建立的哈夫曼樹並不是唯一的,所以只要按照規則一步一步沒有出錯,你的哈夫曼樹就是正確的。
我們現在將訪問左節點定義為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 ,我們的哈夫曼樹就介紹到這裡了,你還有什麼不懂的問題記得留言給我哦。