哈夫曼樹
哈夫曼樹(或者赫夫曼樹、霍夫曼樹),指的是一種滿二叉樹,該類型二叉樹具有一項(xiàng)特性,即樹的帶權(quán)路徑長(zhǎng)最小,所以也稱之為最優(yōu)二叉樹。
節(jié)點(diǎn)的帶權(quán)路徑長(zhǎng)指的是葉子節(jié)點(diǎn)的權(quán)值與路徑長(zhǎng)的乘積,樹的帶權(quán)路徑長(zhǎng)即為樹中所有葉子節(jié)點(diǎn)的帶權(quán)路徑長(zhǎng)度之和。由此可知,若葉子節(jié)點(diǎn)的權(quán)值都是已知的,則二叉樹的構(gòu)造過(guò)程中,使得權(quán)值越大的葉子節(jié)點(diǎn)路徑越小,則整棵樹的帶權(quán)路徑長(zhǎng)最小。
編碼與解碼
數(shù)據(jù)在計(jì)算機(jī)上是以二進(jìn)制表達(dá)的,即計(jì)算機(jī)只識(shí)別二進(jìn)制序列,所以所有字符內(nèi)容都需要完成與二進(jìn)制的轉(zhuǎn)換,才能在計(jì)算機(jī)中存儲(chǔ)和呈現(xiàn)為我們看到的內(nèi)容。并且這種轉(zhuǎn)換方式必須是一致的,即字符內(nèi)容若以方式 轉(zhuǎn)換為二進(jìn)制序列,則二進(jìn)制序列同樣需要以方式
轉(zhuǎn)換為字符內(nèi)容,否則會(huì)產(chǎn)生所謂的亂碼現(xiàn)象。這種字符到二進(jìn)制的轉(zhuǎn)換即為編碼,二進(jìn)制到字符的轉(zhuǎn)換即為解碼,編碼和解碼需要使用相同的映射規(guī)則,才不會(huì)產(chǎn)生亂碼。
哈夫曼編碼
構(gòu)造哈夫曼樹的目的是為了完成哈夫曼編碼,哈夫曼編碼是一種變長(zhǎng)、極少多余編碼方案。相對(duì)于等長(zhǎng)編碼,將文件中每個(gè)字符轉(zhuǎn)換為固定個(gè)數(shù)的二進(jìn)制位,變長(zhǎng)編碼根據(jù)字符使用頻率的高低,使用了不同長(zhǎng)度的二進(jìn)制位與不同字符進(jìn)行映射,使得頻率高的字符對(duì)應(yīng)的二進(jìn)制位較短,頻率低的字符對(duì)應(yīng)的二進(jìn)制位較長(zhǎng)。使得源文件利用哈夫曼編碼后的二進(jìn)制序列大小,相對(duì)于原編碼方案能夠有較大縮小,如此即完成了文件的壓縮。
哈夫曼編碼能夠用于實(shí)現(xiàn)文件的無(wú)損壓縮,自然保證了文件解壓縮過(guò)程的正確性,即二進(jìn)制序列向字符的映射過(guò)程不會(huì)發(fā)生錯(cuò)亂。解碼過(guò)程的正確性通過(guò)哈夫曼樹的結(jié)構(gòu)可以得到證明,以哈夫曼樹中的每個(gè)葉子節(jié)點(diǎn)作為一個(gè)字符,則從根節(jié)點(diǎn)到每個(gè)葉子的路徑都是唯一的,即不存在一個(gè)葉子節(jié)點(diǎn)的路徑是另一個(gè)葉子節(jié)點(diǎn)的路徑前綴。滿足該特性的編碼稱之為前綴編碼,所以哈夫曼編碼中能夠?qū)崿F(xiàn)二進(jìn)制到字符的正確映射。
哈夫曼樹的構(gòu)造
哈夫曼樹是一棵滿二叉樹,樹中只有兩種類型的節(jié)點(diǎn),即葉子節(jié)點(diǎn)和度為 2 的節(jié)點(diǎn),所以樹中任意節(jié)點(diǎn)的左子樹和右子樹同時(shí)存在。構(gòu)建步驟如下:
- 對(duì)字符集合按照字符頻率進(jìn)行升序排序,并構(gòu)建一顆空樹;
- 遍歷字符集合,將每一個(gè)字符添加到樹中,添加規(guī)則為:
【1】若樹為空,則作為根節(jié)點(diǎn);
【2】若字符頻率不大于根節(jié)點(diǎn)頻率,則字符作為根節(jié)點(diǎn)的左兄弟,形成一個(gè)新的根節(jié)點(diǎn),頻率值為左、右子節(jié)點(diǎn)之和;
【3】若字符頻率大于根節(jié)點(diǎn)頻率,則字符作為根節(jié)點(diǎn)的右兄弟,形成一個(gè)新的根節(jié)點(diǎn),頻率值為左右子節(jié)點(diǎn)之和。
構(gòu)造示例
這里自然不可能以所有字符集作示例,假設(shè)字符集范圍為
~
字符集合為:
對(duì)應(yīng)的頻率為:
step 1:
對(duì)字符集合按照頻率進(jìn)行排序,這里使用插入排序算法進(jìn)行排序。
算法示例:
# synchronise sort the valueArr and contentArr
def insertionSort(valueArr, contentArr):
for i in range(1, len(valueArr)): # iteration times
tmpValue = valueArr[i]
tmpContent = contentArr[i]
while i > 0 and tmpValue < valueArr[i - 1]:
valueArr[i] = valueArr[i - 1]
contentArr[i] = contentArr[i - 1]
i = i - 1
valueArr[i] = tmpValue
contentArr[i] = tmpContent
排序后字符集合和對(duì)應(yīng)的頻率為:
step 2:
遍歷將集合中元素添加到樹中,其中定義如下:
樹節(jié)點(diǎn)定義為:
# tree node definition
class Node(object):
def __init__(self, value, content=None, lchild=None, rchild=None):
self.lchild = lchild
self.rchild = rchild
self.value = value
self.content = content
樹定義為:
# tree definition
class Tree(object):
def __init__(self, root=None):
self.root = root
self.codeMap = {}
# merge two nodes and return one root node
def acceptNewNode(self, value, content):
if not self.root:
self.root = Node(value, content)
else:
newNode = Node(value, content)
newRoot = Node(self.root.value + value)
lchild, rchild = (self.root, newNode) if self.root.value < value else (newNode, self.root)
newRoot.lchild, newRoot.rchild = lchild, rchild
self.root = newRoot
樹結(jié)構(gòu)中定義的 方法,用于向樹中添加新字符,其中
表示新字符的頻率,
表示字符體。
第一個(gè)元素
,頻率為
第二個(gè)元素
,頻率為
第三個(gè)元素
,頻率為
...
...
...
第十個(gè)元素
,頻率為
哈夫曼樹編解碼
哈夫曼樹構(gòu)造完成之后,以 表示左分支,
表示右分支,則樹中每個(gè)字符都有唯一的二進(jìn)制映射。這里借用哈希表結(jié)構(gòu),將字符與對(duì)應(yīng)的二進(jìn)制序列存儲(chǔ)為鍵值對(duì),來(lái)演示編碼過(guò)程;利用二進(jìn)制序列在二叉樹中查找具體的字符,來(lái)演示解碼過(guò)程。
構(gòu)造哈希表
首先根據(jù)哈夫曼樹,生成哈希表,有點(diǎn)類似于前序遍歷:
# initialize the huffman tree code map
def initializeCodeMap(node, byteArr, codeMap):
if node.lchild:
byteArr.append('0')
initializeCodeMap(node.lchild, byteArr, codeMap)
byteArr.append('1')
initializeCodeMap(node.rchild, byteArr, codeMap)
byteArr.pop() if len(byteArr) > 0 else None # in case only the root node left
else:
codeMap[node.content] = ''.join(byteArr)
byteArr.pop()
代碼中以 作為存儲(chǔ)鍵值對(duì)的哈希表, 以
存儲(chǔ)二進(jìn)制路徑信息。因?yàn)楣蚵鼧涫菨M二叉樹,節(jié)點(diǎn)的左子樹存在則右子樹同時(shí)存在,所以判斷左子樹是否存在即可判斷是否為葉子節(jié)點(diǎn)。每個(gè)左葉子節(jié)點(diǎn)訪問(wèn)結(jié)束則記錄鍵值對(duì)到
中,并將路徑
回退到父節(jié)點(diǎn),開(kāi)始訪問(wèn)右子樹;每個(gè)右葉子節(jié)點(diǎn)訪問(wèn)結(jié)束則記錄鍵值對(duì)到
中,并將路徑
回退到父節(jié)點(diǎn)的父節(jié)點(diǎn),訪問(wèn)其右子樹。
編碼與解碼
構(gòu)造完成哈希表后,編碼 過(guò)程只需要根據(jù)字符取二進(jìn)制序列即可。解碼
過(guò)程就是根據(jù)二進(jìn)制序列,不斷在二叉樹中查找字符而已,找到字符后則從根節(jié)點(diǎn)繼續(xù)查找下一個(gè)字符。
編碼與解碼函數(shù)體實(shí)現(xiàn)如下:
# tree definition
class Tree(object):
# encode
def encode(self, chars):
bytes = ''
for i in chars: # get the mapped bytes
bytes += self.codeMap.get(i.upper(), '###')
return bytes
# decode
def decode(self, bytes):
chars = ''
tmpNode = self.root
for i in bytes:
if i == '0':
tmpNode = tmpNode.lchild
elif i == '1':
tmpNode = tmpNode.rchild
if not tmpNode.lchild:
chars += tmpNode.content
tmpNode = self.root
return chars
代碼附錄
完整代碼如下:
# tree node definition
class Node(object):
def __init__(self, value, content=None, lchild=None, rchild=None):
self.lchild = lchild
self.rchild = rchild
self.value = value
self.content = content
# tree definition
class Tree(object):
def __init__(self, root=None):
self.root = root
self.codeMap = {}
# initialize the huffman tree code map
def initializeCodeMap(self):
initializeCodeMap(self.root, [], self.codeMap)
# encode
def encode(self, chars):
bytes = ''
for i in chars: # get the mapped bytes
bytes += self.codeMap.get(i.upper(), '###')
return bytes
# decode
def decode(self, bytes):
chars = ''
tmpNode = self.root
for i in bytes:
if i == '0':
tmpNode = tmpNode.lchild
elif i == '1':
tmpNode = tmpNode.rchild
if not tmpNode.lchild:
chars += tmpNode.content
tmpNode = self.root
return chars
# merge two nodes and return one root node
def acceptNewNode(self, value, content):
if not self.root:
self.root = Node(value, content)
else:
newNode = Node(value, content)
newRoot = Node(self.root.value + value)
lchild, rchild = (self.root, newNode) if self.root.value < value else (newNode, self.root)
newRoot.lchild, newRoot.rchild = lchild, rchild
self.root = newRoot
# initialize the huffman tree code map
def initializeCodeMap(node, byteArr, codeMap):
if node.lchild:
byteArr.append('0')
initializeCodeMap(node.lchild, byteArr, codeMap)
byteArr.append('1')
initializeCodeMap(node.rchild, byteArr, codeMap)
byteArr.pop() if len(byteArr) > 0 else None # in case only the root node left
else:
codeMap[node.content] = ''.join(byteArr)
byteArr.pop()
# construct the huffman tree
def createHuffmanTree(valueArr, contentArr):
insertionSort(valueArr, contentArr) # synchronise sort the valueArr and contentArr
hfTree = Tree()
for i in range(len(valueArr)): # construct the huffman tree
hfTree.acceptNewNode(valueArr[i], contentArr[i])
hfTree.initializeCodeMap() # initialize the huffman tree code map
return hfTree
# synchronise sort the valueArr and contentArr
def insertionSort(valueArr, contentArr):
for i in range(1, len(valueArr)): # iteration times
tmpValue = valueArr[i]
tmpContent = contentArr[i]
while i > 0 and tmpValue < valueArr[i - 1]:
valueArr[i] = valueArr[i - 1]
contentArr[i] = contentArr[i - 1]
i = i - 1
valueArr[i] = tmpValue
contentArr[i] = tmpContent
演示示例如下:
if __name__ == '__main__':
valueArr = [5, 3, 4, 0, 2, 1, 8, 6, 9, 7]
contentArr = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J']
huTree = createHuffmanTree(valueArr, contentArr)
chars = 'hIebAhcHdfGc'
bytes = huTree.encode(chars)
print(chars.lower(),'encode =',bytes)
chars = huTree.decode(bytes)
print(bytes,'decode =',chars.lower())
示例輸出為:
hiebahchdfgc encode = 11100111111111111110111101110111110111011111110011111110110111110
11100111111111111110111101110111110111011111110011111110110111110 decode = hiebahchdfgc
github
鏈接:哈夫曼樹