作为练习,我正在尝试使用Huffman树编码一些符号,但是使用我自己的类而不是Python的内置数据类型。
这是我的节点类:
class Node(object):
left = None
right = None
weight = None
data = None
code = ''
length = len(code)
def __init__(self, d, w, c):
self.data = d
self.weight = w
self.code = c
def set_children(self, ln, rn):
self.left = ln
self.right = rn
def __repr__(self):
return "[%s,%s,(%s),(%s)]" %(self.data,self.code,self.left,self.right)
def __cmp__(self, a):
return cmp(self.code, a.code)
def __getitem__(self):
return self.code
这是编码功能:
def encode(symbfreq):
tree = [Node(sym,wt,'') for sym, wt in symbfreq]
heapify(tree)
while len(tree)>1:
lo, hi = sorted([heappop(tree), heappop(tree)])
lo.code = '0'+lo.code
hi.code = '1'+hi.code
n = Node(lo.data+hi.data,lo.weight+hi.weight,lo.code+hi.code)
n.set_children(lo, hi)
heappush(tree, n)
return tree[0]
(注意,data
字段最终将包含节点子节点中所有项目的set()
。它只包含当前的总和,同时我得到正确的编码)。
这是我用于编码树的上一个函数:
def encode(symbfreq):
tree = [[wt, [sym, ""]] for sym, wt in symbfreq]
heapq.heapify(tree)
while len(tree)>1:
lo, hi = sorted([heapq.heappop(tree), heapq.heappop(tree)], key=len)
for pair in lo[1:]:
pair[1] = '0' + pair[1]
for pair in hi[1:]:
pair[1] = '1' + pair[1]
heapq.heappush(tree, [lo[0] + hi[0]] + lo[1:] + hi[1:])
return sorted(heapq.heappop(tree)[1:], key=lambda p: (len(p[-1]), p))
但是我注意到我的新程序是不正确的:它为顶级节点提供了最长的代码字而不是最终的叶子,并且没有为输入符号的排列产生相同的树,即以下不产生同一棵树(使用新的编码功能运行时):
input1 = [(1,0.25),(0,0.25),(0,0.25),(0,0.125),(0,0.125)]
input2 = [(0,0.25),(0,0.25),(0,0.25),(1,0.125),(0,0.125)]
我发现自己真的很难避免这种错误/排序错误 - 我将来如何解决这个问题呢?
答案 0 :(得分:2)
这个代码中有多个奇怪的东西;-)但我认为你的主问题是这样的:
def __cmp__(self, a):
return cmp(self.code, a.code)
堆操作使用比较方法对堆进行排序,但由于某种原因,您告诉它按其代码的当前长度对Node
进行排序。你几乎肯定希望堆通过它们的权重来排序它们,对吗?这就是霍夫曼编码的工作方式。
def __cmp__(self, a):
return cmp(self.weight, a.weight)
对于其他人来说,很难理解,因为5个符号中有4个是相同的(4个0
和1个1
)。你怎么可能知道它是否有效?
在循环内部,这是紧张的:
lo, hi = sorted([heappop(tree), heappop(tree)])
鉴于对__cmp__
的修复,这更容易:
lo = heappop(tree)
hi = heappop(tree)
排序毫无意义 - 始终弹出当前最小的元素。因此弹出两次,lo <= hi
必须为真。
我会说更多;-),但此时我对你最终要完成的事情感到困惑。如果您同意__cmp__
应该修复,请进行更改并编辑问题,以便为和输入您希望得到的确切输出。
关于:
它为顶级节点提供最长的代码字而不是最终的叶子,
这不是一个“off by 1”的东西,它更像是一个“向后”的东西;-) Huffman编码首先查看权重最小的节点。从堆中弹出一个节点,权重越高,其代码应该更短。但是你的代码更长了这个过程继续进行的时间更长。它们应该越来越短了。这个过程继续进行。“
构建树时,无法执行此操作。实际上,在树构建过程完成之前,代码是不可知的。
所以,我不会猜测意图等,而是会给出一些可以修改的工作代码。我将包括一个示例输入及其输出:
from heapq import heappush, heappop, heapify
class Node(object):
def __init__(self, weight, left, right):
self.weight = weight
self.left = left
self.right = right
self.code = None
def __cmp__(self, a):
return cmp(self.weight, a.weight)
class Symbol(object):
def __init__(self, name, weight):
self.name = name
self.weight = weight
self.code = None
def __cmp__(self, a):
return cmp(self.weight, a.weight)
def encode(symbfreq):
# return pair (sym2object, tree), where
# sym2object is a dict mapping a symbol name to its Symbol object,
# and tree is the root of the Huffman tree
sym2object = {sym: Symbol(sym, w) for sym, w in symbfreq}
tree = sym2object.values()
heapify(tree)
while len(tree) > 1:
lo = heappop(tree)
hi = heappop(tree)
heappush(tree, Node(lo.weight + hi.weight, lo, hi))
tree = tree[0]
def assigncode(node, code):
node.code = code
if isinstance(node, Node):
assigncode(node.left, code + "0")
assigncode(node.right, code + "1")
assigncode(tree, "")
return sym2object, tree
i = [('a', 1), ('b', 2), ('c', 3), ('d', 4), ('e', 5)]
s2o, t = encode(i)
for v in s2o.values():
print v.name, v.code
打印:
a 010
c 00
b 011
e 11
d 10
因此,正如所希望的那样,权重最高的符号具有最短的代码。
答案 1 :(得分:1)
我怀疑问题在于细分: -
lo.code = '0'+lo.code
hi.code = '1'+hi.code
hi&amp; amp; low可以是中间节点,您需要在实际符号所在的叶子处更新代码。我认为你不应该在构建霍夫曼树时保留任何代码,而是在构建霍夫曼树之后通过遍历获得单个符号的代码。
继承我编码的伪代码: -
tree = ConstructHuffman(); // Same as your current but without code field
def encode(tree,symbol):
if tree.data == symbol :
return None
else if tree.left.data.contains(symbol) :
return '0' + encode(tree.left,symbol)
else :
return '1' + encode(tree.right,symbol)
使用上面的编码方法计算所有符号的代码,然后你可以使用它们进行编码。
注意:更改比较函数,从比较代码到比较权重。