Python - Huffman Tree 霍夫曼树实现与应用

目录

一.引言

二.Huffman Tree 理论

1.定义

2.结构

3.构造

三.Huffman Tree 实现

1.生成霍夫曼树

2.编码霍夫曼编码

3.解码霍夫曼编码

4.霍夫曼树编码解码实践

四.总结


一.引言

上篇 Word2vec 的文章中指出每次计算词库 N 个单词的 Softmax 计算量很大,可以通过负采样和层次 Softmax 进行计算优化,其中层次 Softmax 中用到了 Huffman Tree,下面简单介绍下霍夫曼树以及其 python 代码实现。

二.Huffman Tree 理论

1.定义

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

2.结构

霍夫曼树也可以简称最优树,根据定义我们可以得到几个关键词:

- 路径

路径即树中节点到另一个节点所经历的树分支,称为节点路径。如上图所示,我们将每个节点的左右分支分别标识为路径 0 和路径 1,以 d 点为例,则其路径为 110,而这个路径编码我们也称为霍夫曼编码,用来唯一标识根节点到对应节点的路径。以 Word2vec 为例,每一个叶子节点可以表示词库中的一个单词。

- 长度

路径上的分支总数称为路径长度,以 d 点为例,其编码 110 代表根节点通过三步走到达 d,所以其路径长度为 3。

- 权值

定义中 N 个带权对象可以理解为每个二叉数 Node 节点有一个 Freq 变量标识其权重或频率,例如 Word2vec 中每个单词可以看做是每个叶子节点,而每个单词出现的频率就可以作为权值。

- 节点带权路径长度

以 d 点为例,其霍夫曼编码为 110,路径长度为3,权值为1,则带权路径长度 3x1 = 3。

- 树带权路径长度

将所有叶子节点的带权路径长度相加即为树的带权路径长度,假设有 N 个点:

\sum =l_1w_1+1_2w_2 + \cdots +l_nw_n

3.构造

S 代表 String 即字符,F 代表 Freq 即频率,根据霍夫曼树的定义,其要求树的带权路径长度最小,所以出现频率越低的单词路径越长,出现频率越高的单词路径越短,这样 ∑ W x L 才会最小。

- 排序与合并

首先将字符列表按频次进行排序,并 pop 弹出最小的两个 Node 构成一个新的 NewNode,其中 NewNode 的左子树为权值较小的 Node,右子树为权值较大的 Node,且 NewNode 权值为左右子树权值之和。

- 循环合并

将新合并的 NewNode 加入剩余 Node 的列表中,继续执行上一步排序与合并的逻辑。直到列表中只剩一个 Node,此时该 Node 为顶节点,霍夫曼树也构造完成。

- 生成示例

P1 [1,1,2,3,5,5], B-1、F-1 权值最小首先合并为权值为 1+1 = 2 的 新节点

P2 [2,2,3,5,5] New-2 与 C-2 是最小的两个节点,二者合并新节点,新节点权值为 4

P3 [3,4,5,5] 将 A-3 和 New-4 构成权值为 7 的新节点

P4 [5,5,7] 将 D-5 和 E-5 构成 新节点为 New-10

P5 [7,10] 构成新的节点 New-17,小的在左即 7 在左,大的在右即 10 在右

P6 [17] 此时列表只有一个元素,迭代完成 

三.Huffman Tree 实现

class Node:
    def __init__(self, freq, char=None):
        self.left = None
        self.right = None
        self.freq = freq
        self.char = char

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

首先定义二叉树节点 Node 类,添加 freq 变量代表对应字符 char 的出现频次。

1.生成霍夫曼树

def create_tree(nodes):
    while len(nodes) > 1:
        nodes.sort()
        left_node = nodes.pop(0)
        right_node = nodes.pop(0)
        new_node = Node(left_node.freq + right_node.freq)
        new_node.left = left_node
        new_node.right = right_node
        nodes.append(new_node)

    return nodes[0]

循环条件为 Nodes 长度大于1,所以 Nodes 长度为1时构造完成,可以参考上面构造的实例,这里可以使用最小堆优化排序的耗时。

2.编码霍夫曼编码

def huffman_encoding(text):
    # 1.构建字典
    freq_dict = {}
    for char in text:
        if char in freq_dict:
            freq_dict[char] += 1
        else:
            freq_dict[char] = 1

    # 2.构造霍夫曼树
    nodes = [Node(freq_dict[char], char) for char in freq_dict]
    root = create_tree(nodes)

    # 3.递归获取每个char的霍夫曼编码
    huffman_codes = {}
    traverse(root, "", huffman_codes)

    # 4.获取原 Text 的霍夫曼编码
    encoded_text = ""
    for char in text:
        encoded_text += huffman_codes[char]

    return encoded_text, huffman_codes

根据给定的 text 首先进行分词和词频统计,随后构造 Nodes 数组并创建霍夫曼,最后利用递归获取每个 char 对应的霍夫曼编码,这里 traverse 函数逻辑如下:

def traverse(node, code, huffman_codes):
    if node.char:
        huffman_codes[node.char] = code
    else:
        traverse(node.left, code + '0', huffman_codes)
        traverse(node.right, code + '1', huffman_codes)

只有 char 字符的节点即叶节点才会触发向 map 保存霍夫曼编码的行为,其余新生成的节点只会传递调用。 

3.解码霍夫曼编码

def huffman_decoding(encoded_text, huffman_codes):
    decoded_text = ""
    code = ""
    for bit in encoded_text:
        code += bit
        for char in huffman_codes:
            if huffman_codes[char] == code:
                decoded_text += char
                code = ""
                break

    return decoded_text

不断向 code 增加 bit 并判断是否在前面构造的 char - hufuman_code 的 map 中,发现后重置 code 并继续新的解码。

4.霍夫曼树编码解码实践

if __name__ == '__main__':
    text = "Hello BitDDD"

    encoded_text, huffman_codes = huffman_encoding(text.lower())
    decoded_text = huffman_decoding(encoded_text, huffman_codes)

    print("Original text:", text)
    print("Encoded text:", encoded_text)
    print("Huffman codes:", huffman_codes)
    print("Decoded text:", decoded_text)

- 原始文案 

('Original text:', 'Hello BitDDD')

- 编码后文案

('Encoded text:', '0001110101101001110011011111100010101')

- 编码字典

('Huffman codes:', {' ': '1100', 'b': '1101', 'e': '1110', 'd': '01', 'i': '1111', 'h': '000', 'l': '101', 'o': '001', 't': '100'})

- 解码文字

('Decoded text:', 'hello bitddd')

四.总结

上面介绍了 霍夫曼树 的基本概念与构造方法,这里简单分析下 霍夫曼树 如何应用于 word2vec 的层次 softmax,更详细的可以参考:Gensim Word2Vec 实践。

- 优化计算次数

通过对词库的 N 个单词构造霍夫曼树,计算每个单词的概率只需关注其二叉树路径即霍夫曼编码的长度次计算即可,计算量由 N 缩减为 LogN。

- 路径缩减

由于霍夫曼树构建是根据单词词频,所以高频的单词越靠上其路径也越短,所以针对高频的单词计算量相对来说更少。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/7376.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

办公工具-latex

一、排版总论 1.1 缺省权力 ​ 首先&#xff0c;最重要最需要强调的是&#xff0c;排版是一个信息量极大的工程。字体&#xff0c;格式&#xff0c;对齐方式&#xff0c;页眉页脚&#xff0c;都只是排版的冰山一角&#xff0c;可以说&#xff0c;一个人是没有办法完全控制一个…

JVM 运行时数据区概述及线程

当我们通过前面的&#xff1a;类的加载 --> 验证 --> 准备 --> 解析 --> 初始化&#xff0c;这几个阶段完成后&#xff0c;就会用到执行引擎对我们的类进行使用&#xff0c;同时执行引擎将会使用到我们运行时数据区。 运行时数据区结构 内存概念&#xff1a; 内存…

leetcode:只出现一次的数字 Ⅲ(详解)

前言&#xff1a;内容包括&#xff1a;题目&#xff0c;代码实现&#xff0c;大致思路&#xff0c;代码解读 题目&#xff1a; 给你一个整数数组 nums&#xff0c;其中恰好有两个元素只出现一次&#xff0c;其余所有元素均出现两次。 找出只出现一次的那两个元素。你可以按 任…

Qt界面编程(三)—— 父子关系、对象树、信号和槽(自定义信号和槽、Qt5与Qt4的写法)

一、Qt按钮小程序 1. 按钮的创建和父子关系 在Qt程序中&#xff0c;最常用的控件之一就是按钮了&#xff0c;首先我们来看下如何创建一个按钮&#xff1a; #include <QPushButton>QPushButton * btn new QPushButton; //设置父亲btn->setParent(this);//设置文字b…

接口测试-postman使用总结

一、为何使用postman postman是一款简单高效的接口测试工具&#xff0c;能够很方便发送接口请求&#xff0c;易于保存接口请求脚本&#xff0c;postman提供接口响应数据比对功能&#xff0c;可以设置预期结果作断言&#xff0c;还能把测试用例放在一个集合中批量执行&#xff…

【JavaWeb】9—监听器

⭐⭐⭐⭐⭐⭐ Github主页&#x1f449;https://github.com/A-BigTree 笔记链接&#x1f449;https://github.com/A-BigTree/Code_Learning ⭐⭐⭐⭐⭐⭐ 如果可以&#xff0c;麻烦各位看官顺手点个star~&#x1f60a; 如果文章对你有所帮助&#xff0c;可以点赞&#x1f44d;…

torchvision.transforms 常用方法解析(含图例代码以及参数解释)

本文代码和图片完全源于 官方文档: TRANSFORMING AND AUGMENTING IMAGES 中的 Illustration of transforms&#xff0c;参数介绍源自函数对应的官方文档。 代码中的变换仅仅使用了最简单的参数&#xff1a;pad&#xff0c;size 等&#xff0c;这里展现的只是简单的变换&#xf…

C/C++每日一练(20230408)

目录 1. 删除无效的括号 &#x1f31f;&#x1f31f;&#x1f31f; 2. 合并K个升序链表 &#x1f31f;&#x1f31f;&#x1f31f; 3. 四数之和 &#x1f31f;&#x1f31f; &#x1f31f; 每日一练刷题专栏 &#x1f31f; Golang每日一练 专栏 Python每日一练 专栏 …

SQL Server用户定义的函数(UDF)使用详解

SQL Server用户定义的函数一、背景知识1.1、用户定义函数的优点1.2、函数类型1.3、指引1.4、函数中的有效语句1.5、架构绑定函数1.6、指定参数二、创建用户定义函数2.1、限制和权限2.2、标量函数示例&#xff08;标量 UDF&#xff09;2.3、表值函数示例2.3.1、内联表值函数 &am…

项目管理软件调度的优势有哪些?

如果没有项目时间表&#xff0c;要跟踪在何时以及必须使用哪些资源之前需要完成什么是非常困难和耗时的。时间表是一个时间表&#xff0c;它概述了所有项目任务和需要完成的里程碑的开始和结束日期。 项目进度中的任务将具有依赖性&#xff0c;这意味着如果完成数据在一项活动上…

Redis7高级之Redlock算法和Redisson的使用(十)

10.1 Redlock 红锁算法 1.解决手写分布式锁的单点故障问题 Redis 提供了 Redlock 算法&#xff0c;用来实现基于多个实例的分布式锁锁变量由多个实例维护&#xff0c;即使有实例发生了故障&#xff0c;锁变量仍然是存在的&#xff0c;客户端还是可以完成锁操作Redlock算法是实…

计算机网络考试复习——第一章 1.5 1.6

1.5 计算机网络的类别 1.5.1计算机网络的定义&#xff1a; 系统集合&#xff0c;连接起来&#xff0c;协议工作&#xff0c;资源共享 计算机网络主要是由一些通用的、可编程的硬件互连而成的&#xff0c;而这些硬件并非专门用来实现某一特定目的&#xff08;例如&#xff0…

Java源码(一)ThreadLocal、SpringBoot Jar 启动原理

思维导图 一、ThreadLocal 1.场景 项目采用SSMShiro登录认证&#xff0c;改造需求如下&#xff1a; 后台管理员登录需要限制&#xff0c;同一个用户的不同IP需要通过过自定义验证后才能登录。 2.问题 在完成需求后发现有管理员用户&#xff08;这里就用A&#xff09;通过验…

Android build.gradle配置详解

Android Studio是采用gradle来构建项目的&#xff0c;gradle是基于groovy语言的&#xff0c;如果只是用它构建普通Android项目的话&#xff0c;是可以不去学groovy的。当我们创建一个Android项目时会包含两个Android build.gradle配置详解文件&#xff0c;如下图&#xff1a; 一…

区块链3链(TRC ERC BSC)授权持币生息源码

分享一款3链&#xff08;TRC ERC BSC&#xff09;授权持币生息源码、来自群友投稿的资源、据说是运营级的。简单的看了下没有问题什么大问题、有能力的可以拿来二开其他的模板。 搭建非常简单&#xff0c;教程就不写了、环境NGINX1.2PHP7.2MYSQL5.6TP默认伪静态 此类源码需要…

【Python】数学 - 用 Python 自动化求解函数 f(x) 的值

目录 1、缘起 2、求以下函数的值 3、代码清单 3.1、求解 f(0)、f(1)、 f(​编辑)、f(​编辑) 3.2、求解 g(0)、g(1)、g(​编辑)、g(​编辑) 3.3、求解 h(0)、h(1)、h(​编辑)、h(​编辑) 4、总结 1、缘起 Python 是一种强大的编程语言&#xff0c;它具有广泛的应用领域。…

Python模拟星空

文章目录前言Turtle基础1.1 Turtle画板1.2 Turtle画笔1.3 Turtle画图1.4 Turtle填色1.5 Turtle写字模拟星空模拟星球浪漫星空尾声前言 Python模拟星空&#xff0c;你值得拥有&#xff01;uu们一周不见啦&#xff0c;本周博主参考网上大佬们的星空&#xff0c;给大家带来了属于…

C语言操作符优先级

在平时写代码时&#xff0c;经常会用到操作符&#xff0c;但是如果不了解这些操作符的优先级&#xff0c;可能会让程序的执行效果和我们预期的不一样。 例如&#xff1a; int a 2;int b 3;int c 4;//int ret a b * c;//我们想要执行的顺序是ab的值再乘c//如果了解操作符优…

chat GPT人工智能写论文-怎么用chatGpt写论文

用chatGPT写文章会重复吗 使用 ChatGPT 写文章可能会出现重复的情况。因为 ChatGPT 是基于机器学习的自然语言处理技术&#xff0c;它并不具备人类的创造性思维&#xff0c;其生成的文本内容是基于已有语言数据的统计模型而产生的。 当输入信息重复、语言结构复杂或指定主题较…

【测试】《软件测试》阅读总结

第一章 软件测试的流程是什么&#xff1f; 需求分析--------测试计划----------测试开发--------测试执行-------测试报告 如何描述一个BUG 版本&#xff0c;测试环境、测试步骤和测试数据、实际结果、预期结果、附件&#xff08;截图、错误日志&#xff09; 软件测试过程包括…