【算法系列一】霍夫曼压缩

既然是从霍夫曼压缩入手的,就先来看看它是个什么东西:

一种压缩算法,用较少的比特表示出现频率高的字符,用较多的比特表示出现频率低的字符。

它的大致过程是这样的:

  1. 假设压缩“beep boop beer!”
  2. 计算字符频次,由小到大排序,得到Priority Queue

    字符 次数
    ‘r’ 1
    ‘!’ 1
    ‘p’ 2
    ‘o’ 2
    ‘ ‘ 2
    ‘b’ 3
    ‘e’ 4
  3. 每次取前两个节点,分别作为左、右节点,次数相加合并为根节点,将根节点按由小到大的顺序插入Priority Queue,由此生成二叉树

【算法系列一】霍夫曼压缩_第1张图片

  1. 将二叉树左支编码0,右支编码1,由路径得出字符的编码

    字符 编码
    ‘b’ 00
    ‘e’ 11
    ‘p’ 101
    ‘ ‘ 011
    ‘o’ 010
    ‘r’ 1000
    ‘!’ 1001

详细过程见参考2

Python代码如下(基本思路和上述过程一致,利用堆的特性操作数据,每次执行得到的具体树和编码不保证相同):

class HuffmanCompression:
    class Trie:
        def __init__(self, val, char=''):
            self.val = val # 节点的权重
            self.char = char # 字符节点对应的字符
            self.coding = '' # 字符最后得到的编码
            self.left = self.right = None

        def __lt__(self, other):
            return self.val < other.val

        def __repr__(self):
            char = self.char
            if self.char == '':
                char = '-'
            if self.char == ' ':
                char = '\' \''
            return char + ': ' + str(self.val)

    def __init__(self, string):
        self.string = string
        counter = collections.Counter(string)
        heap = []
        # 构造二叉树
        for char, cnt in counter.items():
            heapq.heappush(heap, HuffmanCompression.Trie(cnt, char))

        while len(heap) != 1:
            left = heapq.heappop(heap)
            right = heapq.heappop(heap)
            trie = HuffmanCompression.Trie(left.val + right.val) # 合并的根节点
            trie.left, trie.right = left, right
            heapq.heappush(heap, trie)

        self.root = heap[0]
        self.s2b = {}
        # 遍历二叉树,得到编码
        self.bfs_encode(self.root, self.s2b)

    def bfs_encode(self, root, s2b):
        queue = collections.deque()
        queue.append(root)
        while queue:
            node = queue.popleft()
            if node.char:
                s2b[node.char] = node.coding
                continue
            if node.left:
                node.left.coding = node.coding + '0'
                queue.append(node.left)
            if node.right:
                node.right.coding = node.coding + '1'
                queue.append(node.right)

    def compress(self):
        bits = ''
        for char in self.string:
            bits += self.s2b[char]
        return bits

    def uncompress(self, bits):
        string = ''
        root = self.root
        for bit in bits:
            if bit == '0':
                root = root.left
            else:
                root = root.right
            if root.char:
                string += root.char
                root = self.root
        return string


s = 'beep boop beer!'

# huffman compression
hc = HuffmanCompression(s)
compressed = hc.compress()
print('Compressed binary: ' + compressed)
print('Uncompressed: ' + hc.uncompress(compressed))
print(hc.s2b)

作为一名计算机小白,对堆的概念并不太熟悉,这里补充下堆的相关知识:

数据结构中的堆(二叉堆)通常指的是完全二叉树。完全二叉树除了底层,每一层都是满的,因此可以用数组存放,且便于计算父、子节点下标。python中实现的二叉堆是最小堆,父节点值 <= 子节点值(最大堆相反)。所以每次 heappush 时,会将数据插入到正确的位置,以满足堆排序:

def testHeapq(heap):
    res = []
    for val in heap:
        heapq.heappush(res, val)
        print(res)
        show_tree(res)
    return res

testHeapq([3, 6, 4, 7, 1, 0])

"""
[3]

                 3
------------------------------------

[3, 6]

                 3
        6
------------------------------------

[3, 6, 4]

                 3
        6                 4
------------------------------------

[3, 6, 4, 7]

                 3
        6                 4
    7
------------------------------------

[1, 3, 4, 7, 6]

                 1
        3                 4
    7        6
------------------------------------

[0, 3, 1, 7, 6, 4]

                 0
        3                 1
    7        6        4
------------------------------------
"""

heappop 就是从堆中弹出最小值了,也就是根节点。

这里就简单地介绍 heappushheappop 两个基本方法,更多参见python文档

有童鞋又会问了,那堆排序如何实现呢?以最大堆为例,分3步走:

  1. 最大堆调整(Max-Heapify):子树均满足最大堆特性的前提下,保证树的最大堆特性

    【算法系列一】霍夫曼压缩_第2张图片

  2. 创建最大堆(Build-Max-Heap):从最后节点的父节点开始,自下而上 Max-Heapify

    【算法系列一】霍夫曼压缩_第3张图片

  3. 堆排序(Heap-Sort):依次互换堆顶、堆底,剔除最大值,从根 Max-Heapify

    【算法系列一】霍夫曼压缩_第4张图片

代码见heapSort

参考:
1. Huffman Compression - 霍夫曼压缩
2. HUFFMAN 编码压缩算法——详细描述了算法的执行过程
3. 常见排序算法 - 堆排序 (Heap Sort)——详细描述了堆排序的执行过程

你可能感兴趣的:(【算法系列一】霍夫曼压缩)