论文学习「Python」:Huffman编码详述及代码实现

在学习 word2vec 时,首先接触到的就是 Huffman 编码,这里简单记录一下学习内容。

目录

一、简介

二、Huffman树

(一)基础术语

(二)构建

三、Huffman编码

四、代码

(一)python

(二)结果


 

一、简介

哈夫曼编码Huffman Coding),又称霍夫曼编码,是一种编码方式,哈夫曼编码是可变字长编码VLC)的一种。是 Huffman1952 年提出一种编码方法,该方法完全依据字符出现概率来构造异字头的平均长度最短的码字,有时称之为最佳编码,一般就叫做Huffman  编码(有时也称为霍夫曼编码)[1]

首先,为介绍引入 Huffman 编码的重要性,这里介绍一种简单的 word2vec 方法:One-Hot 编码

One-Hot 编码即独热编码,又称一位有效编码,其方法是使用 N 位状态寄存器来对 N 个状态进行编码,每个状态都有它独立的寄存器位,并且在任意时候,其中只有一位有效。举个例子,“冬天的雨和夏天的雪”,这句中存在的词有“冬天”、“夏天”、“雨”、“雪”、“和”、“的”,即 N=6 ,故我们分别编码得:[100000]、[010000]、[001000]、[000100]、[000010]、[000001]。

这种编码方式存在离散稀疏问题,比如当语料库的十分庞大时,得到的编码向量会过于稀疏,并且会造成维度灾难

Huffman 编码给出了一种解决方案,它是一种用于无损数据压缩的熵编码(权编码)算法。

二、Huffman树

给定 M 个权值作为 M 个叶子结点,构造一棵二叉树,若该树的带权路径长度达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树Huffman Tree)。哈夫曼树是带权路径长度最短的树,权值较大的结点离根较近[2]

哈夫曼树又称为最优树,这里先给出一个哈夫曼树便于理解下面概念(图1 )。

图1:哈夫曼树

(一)基础术语

1. 路径和路径长度

在一棵树中,从一个结点往下可以达到的子或孙子结点之间的通路(不可逆向、横向传递),称为路径。通路中分支的数目称为路径长度(等同于通过经过的层数)。若规定根结点的层数为 1 ,则从根结点到第 L 层结点的路径长度为 L-1

图1 中,权重为 38 的结点为根结点,以权重为 4 结点为例,其路径为 38→23→9→4,路径长度为 3

2. 结点的权及带权路径长度

若将树中结点赋予一个有着某种含义的数值,则这个数值称为该结点的权。结点的带权路径长度为:从根结点到该结点之间的路径长度与该结点的权的乘积。

图1 中,权重为 4 结点的带权路径长度为 3\times 4=12

3. 树的带权路径长度

树的带权路径长度规定为所有叶子结点的带权路径长度之和,记为 WPLWPL=(W_{1}\times L_{1}+W_{2}\times L_{2}+...+W_{M}\times L_{M})

图1 中,树的带权路径长度为38\times 0+23\times 1+15\times 1+14\times 2+...+1\times 4

(二)构建

假设有 M 个权值,则构造出的哈夫曼树有 M 个叶子结点。 M 个权值分别设为 W_{1},W_{2},...,W_{M},则哈夫曼树的构造规则为:

1. 将 W_{1},W_{2},...,W_{M} 看成是有 M 棵树的森林(每棵树仅有一个结点);

2. 在森林中选出两个根结点的权值最小的树合并,作为一棵新树的左、右叶子结点,且新树的根结点权值为其左、右叶子结点权值之和;

3. 从森林中删除选取的两棵树,并将新树加入森林(新树只观察其合并之后的根结点,不再考虑其叶子结点);

4. 重复2、3步,直到森林中只剩一棵树为止,该树即为所求得的哈夫曼树。

下面给出一个具体例子,方便理解。

例:假设我们在一个文本中发现“冬天”、“夏天”、“雨”、“雪”、“和”、“的”这六个词出现的次数分别是1、3、5、6、8、15。我们以这六个词为叶子结点,以相应词频为权值,构造一棵哈夫曼树(图2 )。

图2:哈夫曼树构造过程

图2 中可以看到,权值越大的结点离根越近。

三、Huffman编码

这里直接给出一个定理在变字长编码中,如果码字长度严格按照对应符号出现的概率大小逆序排列(从大到小),则其平均码字长度为最小(证明略,详细可见[1])。我们就上例中“冬天”、“夏天”、“雨”、“雪”、“和”、“的”六个词给出 Huffman 编码方法,具体过程以图示展示(图3 ):

图3:Huffman编码示意图

图3 所示,我们在每个分支结点分别标注 1 0 ,注意,这里标注时保证整体标准一致即可(即可左 1 右 0 ,或左 0 右 1 )。最终的 Huffman 编码结果分别为:

  • “冬天”:[1,0,0,0]
  • “夏天”:[1,0,0,1]
  • “雨”:[1,0,1]
  • “雪”:[1,1,0]
  • “和”:[1,1,1]
  • “的”:[0]

四、代码

这里直接给出相关代码,具体描述请看代码中的注释信息。

(一)python

# 结点对象
class Node:
    def __init__(self):
        # 权重
        self.frequency = 0
        # 符号名
        self.name = None
        # 左结点
        self.l_child = None
        # 右结点
        self.r_child = None
        self.code = None

    # 对频数排序
    def __lt__(self, other):
        return self.frequency < other.frequency


# 从文本中获取每个符号的频数
def frequency(origin_path):
    # 频数字典
    frequency_dict = dict()
    with open(origin_path, 'r') as f:
        for line in f.readlines():
            line = line.lower()
            for j in line:
                if j.isalpha():
                    if j in frequency_dict:
                        frequency_dict[j] += 1
                    else:
                        frequency_dict.update({j: 1})

    # 返回频数字典
    return frequency_dict


# 创建哈夫曼树
def establish_huffman_tree(frequency_dict):
    # 输出哈夫曼树的根结点
    node_list = []
    for j in frequency_dict:
        a = Node()
        # 频数
        a.frequency = frequency_dict[j]
        # 符号名
        a.name = j
        node_list.append(a)

    # while len(node_list) > 1:
    # 生成N-1个新结点
    for j in range(len(frequency_dict) - 1):
        # 从大到小排序
        node_list.sort(reverse=True)
        # 提取频数最小的两个结点,并将它们从node_list中删除
        node_1 = node_list.pop()
        node_2 = node_list.pop()

        # 新结点
        new_node = Node()
        new_node.frequency = node_1.frequency + node_2.frequency
        # 左边频数较右边频数大
        new_node.l_child = node_2
        new_node.r_child = node_1
        node_list.append(new_node)

    # 返回哈夫曼树的根结点
    return node_list[0]


# 哈夫曼编码
def encode(base_node, rst_dict, code):
    if base_node.name:
        rst_dict.update({base_node.name: code})
        return
    code += '1'
    encode(base_node.l_child, rst_dict, code)
    code = code[:-1]
    code += '0'
    encode(base_node.r_child, rst_dict, code)
    return rst_dict


# 编码文本文件,并写入新的文件
def encode_text(code_dict, origin_path, code_text):
    string = ''
    with open(origin_path, 'r') as f:
        for line in f.readlines():
            line = line.lower()
            for j in line:
                if j.isalpha():
                    string += code_dict[j]
                else:
                    string += '\n'
    with open(code_text, 'w') as f:
        f.write(string)


# 解码
def decode(text_address, result_address, base_node):
    text_string = ''
    a = base_node
    with open(text_address, 'r') as f:
        for line in f.readlines():
            for j in line:
                if j == '1':
                    b = a.l_child
                    if b.name:
                        text_string += b.name
                        a = base_node
                    else:
                        a = b
                elif j == '0':
                    b = a.r_child
                    if b.name:
                        text_string += b.name
                        a = base_node
                    else:
                        a = b
                else:
                    text_string += '\n'
    with open(result_address, 'w') as f:
        f.write(text_string)


if __name__ == '__main__':
    # 原始文件路径
    my_origin_path = "../data/origin.txt"
    # 获取频数字典
    my_frequency_dict = frequency(my_origin_path)
    # my_frequency_dict = {'冬天': 1, '夏天': 3, '雨': 5, '雪': 6, '和': 8, '的': 15}
    # my_frequency_dict = {'a': 1, 'b': 3, 'c': 5, 'd': 6, 'e': 8, 'f': 15}

    # 构建哈夫曼树
    my_base_node = establish_huffman_tree(my_frequency_dict)

    # 哈夫曼编码
    my_code_dict = encode(my_base_node, {}, '')

    # 编码文件路径
    my_code_text = "../data/code.txt"
    # 编码文本文件
    encode_text(my_code_dict, my_origin_path, my_code_text)

    # 解码文件路径
    my_result_address = "../data/result.txt"
    decode(my_code_text, my_result_address, my_base_node)

(二)结果

  • origin.txt

该文件为待编码文件,内容如下,这里分别将“冬天”、“夏天”、“雨”、“雪”、“和”、“的”匹配到“a”、“b”、“c”、“d”、“e”、“f”,并频数分别设定为1、3、5、6、8、15

abbbcccccddddddeeeeeeeefffffffffffffff
  • code.txt

文件内容如下:

1000100110011001101101101101101110110110110110110111111111111111111111111000000000000000

该文件为对 origin.txt 进行哈夫曼编码的结果文件,该结果与第三部分得出的编码结果相同。

  • result.txt

文件内容如下:

abbbcccccddddddeeeeeeeefffffffffffffff

该文件输出结果是对 code.txt 文件内容的解码,解码结果与 origin.txt 文件内容相同。

[1] https://baike.baidu.com/item/哈夫曼编码/1719730?fromtitle=HUFFMAN%E7%BC%96%E7%A0%81&fromid=364674&fr=aladdin

[2] https://baike.baidu.com/item/哈夫曼树/2305769?fromtitle=%E9%9C%8D%E5%A4%AB%E6%9B%BC%E6%A0%91&fromid=7783678&fr=aladdin

  • 3
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,以下是一个基于PythonHuffman编码实现: ```python import heapq from collections import defaultdict def huffman_encoding(data): # 构建字符频率字典 freq_dict = defaultdict(int) for char in data: freq_dict[char] += 1 # 将字符频率字典转换为堆 heap = [[freq, [char, ""]] for char, freq in freq_dict.items()] heapq.heapify(heap) # 合并堆中的节点,构建Huffman while len(heap) > 1: left = heapq.heappop(heap) right = heapq.heappop(heap) for pair in left[1:]: pair[1] = '0' + pair[1] for pair in right[1:]: pair[1] = '1' + pair[1] heapq.heappush(heap, [left[0] + right[0]] + left[1:] + right[1:]) # 构建编码字典 encoding_dict = dict(heapq.heappop(heap)[1:]) # 返回编码后的数据和编码字典 encoded_data = "" for char in data: encoded_data += encoding_dict[char] return encoded_data, encoding_dict def huffman_decoding(encoded_data, encoding_dict): # 构建反向编码字典 decoding_dict = {v: k for k, v in encoding_dict.items()} # 解码数据 current_code = "" decoded_data = "" for bit in encoded_data: current_code += bit if current_code in decoding_dict: decoded_data += decoding_dict[current_code] current_code = "" return decoded_data ``` 示例用法: ```python data = "hello world" encoded_data, encoding_dict = huffman_encoding(data) decoded_data = huffman_decoding(encoded_data, encoding_dict) print("Encoded data:", encoded_data) print("Decoded data:", decoded_data) ``` 输出: ``` Encoded data: 0110110111000110010011110100111100001010101101101110001 Decoded data: hello world ``` 这个实现使用了Python的heapq和collections模块,用于构建堆和默认字典。它首先构建字符频率字典,然后将其转换为堆。接下来,它在堆中合并节点,直到只剩下一个节点为止。合并过程中,左分支被标记为0,右分支被标记为1。最终,编码字典被构建,用于编码数据。解码过程中,使用反向编码字典将编码数据转换为原始数据。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值