這個問題原始是用來實現一個可變長度的編碼問題,但可以總結成這樣一個問題,假設我們有很多的葉子節點,每個節點都有一個權值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