霍夫曼编码问题

时间:2013-11-06 16:49:10

标签: python algorithm data-structures huffman-code

作为练习,我正在尝试使用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)]

我发现自己真的很难避免这种错误/排序错误 - 我将来如何解决这个问题呢?

2 个答案:

答案 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)

使用上面的编码方法计算所有符号的代码,然后你可以使用它们进行编码。

注意:更改比较函数,从比较代码到比较权重。