Python 算法(2) 哈夫曼編碼 Huffman Encoding


 

  這個問題原始是用來實現一個可變長度的編碼問題,但可以總結成這樣一個問題,假設我們有很多的葉子節點,每個節點都有一個權值w(可以是任何有意義的數值,比如它出現的概率),我們要用這些葉子節點構造一棵樹,那么每個葉子節點就有一個深度d,我們的目標是使得所有葉子節點的權值與深度的乘積之和$$\Sigma w{i}d{i}$$最小。

  很自然的一個想法就是,對於權值大的葉子節點我們讓它的深度小些(更加靠近根節點),權值小的讓它的深度相對大些,這樣的話我們自然就會想着每次取當前權值最小的兩個節點將它們組合出一個父節點,一直這樣組合下去直到只有一個節點即根節點為止。如下圖所示的示例

代碼實現比較簡單,使用了heapq模塊,樹結構是用list來保存的,有意思的是其中zip函數的使用,其中統計函數count作為zip函數的參數,  

 

 

 

 

代碼實現比較簡單,使用了heapq模塊,樹結構是用list來保存的,有意思的是其中zip函數的使用,其中統計函數count作為zip函數的參數,

from heapq import heapify, heappush, heappop
from itertools import count

def huffman(seq, frq):
    num = count()
    trees = list(zip(frq, num, seq))            # num ensures valid ordering
    heapify(trees)                              # A min-heap based on freq
    while len(trees) > 1:                       # Until all are combined
        fa, _, a = heappop(trees)               # Get the two smallest trees
        fb, _, b = heappop(trees)
        n = next(num)
        heappush(trees, (fa+fb, n, [a, b]))     # Combine and re-add them
    # print trees
    return trees[0][-1]

seq = "abcdefghi"
frq = [4, 5, 6, 9, 11, 12, 15, 16, 20]
print huffman(seq, frq)
# [['i', [['a', 'b'], 'e']], [['f', 'g'], [['c', 'd'], 'h']]]

現在我們考慮另外一個問題,合並文件問題,假設我們將大小為 m 和大小為 n 的兩個文件合並在一起需要 m+n 的時間,現在給定一些文件,求一個最優的合並策略使得所需要的時間最小。

如果我們將上面哈夫曼樹中的葉子節點看成是文件,兩個文件合並得到的大文件就是樹中的內部節點,假設每個節點上都有一個值表示該文件的大小,合並得到的大文件上的值是合並的兩個文件的值之和,那我們的目標是就是使得內部節點的和最小的合並方案,因為葉子節點的大小是固定的,所以實際上也就是使得所有節點的和最小的合並方案!

細想也就有了一個葉子節點的所有祖先節點們都有一份該葉子節點的值包含在里面,也就是說所有葉子節點的深度與它的值的乘積之和就是所有節點的值之和!可以看下下面的示例圖,最終我們知道哈夫曼樹就是這個問題的解決方案。

            

 

 

 哈夫曼樹問題的一個擴展就是最優二叉搜索樹問題,后者可以用動態規划算法來求解

 其他實現方式:

#Huffman Encoding

#Tree-Node Type
class Node:
    def __init__(self,freq):
        self.left = None
        self.right = None
        self.father = None
        self.freq = freq
    def isLeft(self):
        return self.father.left == self
#create nodes創建葉子節點
def createNodes(freqs):
    return [Node(freq) for freq in freqs]

#create Huffman-Tree創建Huffman樹
def createHuffmanTree(nodes):
    queue = nodes[:]
    while len(queue) > 1:
        queue.sort(key=lambda item:item.freq)
        node_left = queue.pop(0)
        node_right = queue.pop(0)
        node_father = Node(node_left.freq + node_right.freq)
        node_father.left = node_left
        node_father.right = node_right
        node_left.father = node_father
        node_right.father = node_father
        queue.append(node_father)
    queue[0].father = None
    return queue[0]
#Huffman編碼
def huffmanEncoding(nodes,root):
    codes = [''] * len(nodes)
    for i in range(len(nodes)):
        node_tmp = nodes[i]
        while node_tmp != root:
            if node_tmp.isLeft():
                codes[i] = '0' + codes[i]
            else:
                codes[i] = '1' + codes[i]
            node_tmp = node_tmp.father
    return codes

if __name__ == '__main__':
    #chars = ['A','B','C','D','E','F','G','H','I','J','K','L','M','N']
    #freqs = [10,4,2,5,3,4,2,6,4,4,3,7,9,6]
    chars_freqs = [('C', 2), ('G', 2), ('E', 3), ('K', 3), ('B', 4),
                   ('F', 4), ('I', 4), ('J', 4), ('D', 5), ('H', 6),
                   ('N', 6), ('L', 7), ('M', 9), ('A', 10)]
    nodes = createNodes([item[1] for item in chars_freqs])
    root = createHuffmanTree(nodes)
    codes = huffmanEncoding(nodes,root)
    for item in zip(chars_freqs,codes):
        print 'Character:%s freq:%-2d   encoding: %s' % (item[0][0],item[0][1],item[1])

輸出結果:

>>>
Character:C freq:2  encoding: 10100
Character:G freq:2  encoding: 10101
Character:E freq:3  encoding: 0000
Character:K freq:3  encoding: 0001
Character:B freq:4  encoding: 0100
Character:F freq:4  encoding: 0101
Character:I freq:4  encoding: 0110
Character:J freq:4  encoding: 0111
Character:D freq:5  encoding: 1011
Character:H freq:6  encoding: 1110
Character:N freq:6  encoding: 1111
Character:L freq:7  encoding: 001
Character:M freq:9  encoding: 100
Character:A freq:10 encoding: 110

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM