【问题标题】:Huffman encoding issue霍夫曼编码问题
【发布时间】:2013-11-06 16:49:09
【问题描述】:

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

我发现我在避免这种逐个/排序错误方面真的很糟糕 - 我将来如何解决这个问题?

【问题讨论】:

  • 你能发布一些不好的输出吗?尤其是最后一部分。
  • 一目了然,我怀疑您的错误在这里lo, hi = sorted([heappop(tree), heappop(tree)])。这里的两个元素都是节点,您已将节点的 __cmp__ 定义为 cmp(self.code, a.code),但您尚未设置代码,因此它总是比较两个空字符串。我不确定堆是否真的是你需要的东西,你不只是将元素视为一个列表,从最小到最大排序吗?我忘记了霍夫曼编码是如何工作的,所以我不确定这是否是正确的方法。
  • @PatrickCollins 我在原始的“无类”代码中使用了一个堆,因为它总是首先弹出重量最低的项目(这就是创建霍夫曼树的方式)。问题是从第一个代码到一个类,在那里我犯了一个我无法纠正的错误 - 因为我不理解 cmp
  • 再一次,如果您使用数据结构的唯一原因是确保它已排序,那么我看不出与使用排序列表相比有什么优势。原始算法中树数据结构的要点是给你更快的解码,而不是编码。
  • @PatrickCollins 我将它用作统计算法的数据结构——它不适用于排序列表。

标签: python algorithm data-structures huffman-code


【解决方案1】:

这段代码中有不止一个奇怪之处 ;-),但我认为您的主要问题是这样的:

def __cmp__(self, a):
    return cmp(self.code, a.code)

堆操作使用比较方法对堆进行排序,但由于某种原因,您告诉它按照代码的当前长度对Nodes 进行排序。您几乎肯定希望堆按重量排序,对吧?这就是霍夫曼编码的工作原理。

def __cmp__(self, a):
    return cmp(self.weight, a.weight)

对于其余部分,很难理解,因为 5 个符号中有 4 个是相同的(四个 0 和一个 1)。你怎么可能知道它是否有效?

在循环内部,这是紧张的:

lo, hi = sorted([heappop(tree), heappop(tree)])

鉴于__cmp__的修复,这更容易:

lo = heappop(tree)
hi = heappop(tree)

排序没有意义——总是弹出当前最小的元素。所以弹出两次,lo <= hi 一定是真的。

我想说更多;-),但在这一点上,我对你最终想要完成的事情感到困惑。如果您同意应修复 __cmp__,请进行更改并编辑问题以提供一些输入您希望获得的确切输出。

更多

关于:

它为顶部节点提供最长的代码字,而不是最终的叶子,

这不是“减 1”的事情,它更像是“倒退”的事情 ;-) 霍夫曼编码首先查看权重最小的节点。越晚从堆中弹出节点,权重越高,其代码应该。但是随着过程的进行,您正在使代码变得越来越长。随着过程的进行,它们应该越来越短。

在构建树的同时你不能这样做。事实上,在树构建过程完成之前,这些代码是不可知的。

因此,与其猜测意图等,我将提供一些您可以修改以适应口味的工作代码。我将包含一个示例输入及其输出:

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。我可以将 dict 修改为一个元组,因为我有重复的值。
  • 当然!没有对字典进行必要的使用;对于大多数应用程序来说,将符号映射到 Symbol 对象的 dict 将非常方便。例如,您可以将sym2object = ... 更改为symbols = tuple(Symbol(sym, w) for sym, w in symbfreq),然后将下一行替换为tree = list(symbols)。如果您编辑您的问题以提供输入及其所需输出的确切示例,我可以尝试为您提供确切的代码来获得它。
  • 感谢您的提议,但我今天确实设法得到了我自己需要的东西。
  • 很抱歉再次打扰您,但是排序应该确保原始数组的相对位置没有被编码过程改变(即第0个bin中的数据放在最左边叶子)。我将如何做到这一点?我已经编辑了问题以反映我的需要。
  • 请打开一个新问题 - 这个问题已经很长而且很困惑 ;-) 与其尝试描述你想要什么,不如给出确切 你想要的输入和输出。我不明白你的描述。
【解决方案2】:

我怀疑问题出在片段中:-

lo.code = '0'+lo.code
hi.code = '1'+hi.code

hi 和 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)

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

注意:将比较功能由比较代码改为比较权重。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2010-10-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多