【华为OD】2024D卷——生成哈夫曼树

embedded/2024/9/24 9:19:42/
题目描述:
给定长度为n的无序的数字数组,每个数字代表二叉树的叶子节点的权值,数字数组的值均大于等于1。
请完成一个函数,根据输入的数字数组,生成哈夫曼树,并将哈夫曼树按照中序遍历输出。为了保证输出的二叉树中序遍历结果统一,增加以下限制:
二叉树节点中,左节点权值小于等于右节点权值,
根节点权值为左右节点权值之和。
当左右节点权值相同时,左子树高度高度小于等于右子树。注意:所有用例保证有效,并能生成哈夫曼树。提醒:哈夫曼树又称最优二叉树,是一种带权路径长度最短的二叉树。
所谓树的带权路径长度,就是树中所有的叶结点的权值乘上其到根结点的路径长度
(若根结点为0层,叶结点到根结点的路径长度为叶结点的层数)。

解题思路:

由于未找到输入输出示例,自己手动创建一个输入权重列表weights = [5,5,15,40,30,10]

根据题意中,哈夫曼树的要求,可以构造树形如下:

故中序遍历结果[40, 105, 30, 65, 15, 35, 10, 20, 5, 10, 5]

构造哈夫曼树

1、创建一个优先级队列(最小堆)并将数组中的每个叶子节点作为一个节点加入堆中。

由于直接将节点压入最小堆中,使其只基于节点的权重进行比较。需要在树节点中重写类的 __lt__() 方法

解决可以解决heapq 无法比较 Node 实例的问题,否则会遇见报错TypeError: '<' not supported between instances of 'TreeNode' and 'TreeNode'。

2、不断从堆中取出两个最小的节点作为左右子树,构造一个新的父节点,其权值为两个节点权值之和,并将新节点插入堆中。

3、重复此过程,直到堆中只剩下一个节点,即为哈夫曼树的根节点。

对限制条件进行处理

1、在合并左右节点时,如果两个节点的权值相等,则优先选择左子树高度小于或等于右子树的情况。

故需要统计每个节点的高度,可以在树节点类型中添加height值。

2、根节点权值为左右节点权值之和。

在构造时,父节点权值 = left_weight + right_weight,重新压入heapq中。

3、如果左右节点的权值不等,左子树权值必须小于等于右子树权值。

heapq弹出的第一个元素为左孩子、第二个为右孩子。

中序遍历输出

使用递归进行中序遍历:左子树 -> 根节点 -> 右子树。


代码部分

python">class TreeNode:def __init__(self, weight, left = None, right = None):self.weight = weightself.left = leftself.right = rightself.height = 1#   递归计算节点的高度def get_height(self):if not self.left and not self.right:return 1left_height = self.left.get_height() if self.left else 0right_height = self.right.get_height() if self.right else 0return 1 + max(left_height, right_height)#   重写方法,基于节点的权重进行比较。这解决了 heapq 无法比较 Node 实例的问题def __lt__(self, other):return self.weight < other.weight
import heapq
class Solution:def build_huffman_tree(self, weights):if not weights:return # 初始化最小堆,创建叶子节点heap = [TreeNode(weight) for weight in weights]heapq.heapify(heap)while len(heap) > 1:# 取出两个权值最小的节点left = heapq.heappop(heap)right = heapq.heappop(heap)# 如果权值相同,按照高度进行处理if left.weight == right.weight:left_height = left.get_height()right_height = right.get_height()# 确保左子树高度 <= 右子树高度if left_height > right_height:left, right = right, left# 创建新的父节点,权值为左右节点权值之和merged_node = TreeNode(left.weight + right.weight, left, right)heapq.heappush(heap, merged_node)# 返回堆中唯一的节点,即哈夫曼树的根节点return heap[0]#   栈迭代法进行中序遍历def midorder_traversal(self, root:TreeNode):res = []if root is None:returnstack = [root]while stack:node = stack.pop()if node != None:#   按照顺序压栈:右节点、中节点、左节点if node.right:stack.append(node.right)stack.append(node)#   中节点访问过,但是还没有处理,加入空节点做为标记。stack.append(None)if node.left:stack.append(node.left)#  只有遇到空节点的时候,才将下一个节点放进结果集else:#   按照左节点、中节点、右节点出栈node = stack.pop()res.append(node.weight)return res#   递归法进行中序遍历def midorder_traversal_I(self, root:TreeNode):res = []def dfs(node):if node is None:returndfs(node.left)res.append(node.weight)dfs(node.right)dfs(root)return res#   递归法简化写法def midorder_traversal_II(self, root:TreeNode):if root is None:return []return self.midorder_traversal_II(root.left) + [root.weight] + self.midorder_traversal_II(root.right)if __name__=='__main__':weights = list(map(int, input().split(',')))solution = Solution()huffman_tree = solution.build_huffman_tree(weights)result = solution.midorder_traversal(huffman_tree)print(result)

拓展:

__lt__() 方法是一个特殊方法(也称为魔术方法或双下划线方法),它用于定义小于(<)运算符的行为。

在尝试比较两个类的实例时,如果这两个实例之间的比较逻辑不是简单的基于它们的值或身份(即不是直接通过 ==!=<<=>>= 这样的操作符),就可以通过定义这些特殊方法来自定义这些操作符的行为。

__lt__() 方法应该接收一个参数(即与当前实例进行比较的另一个实例),并返回一个布尔值:如果当前实例小于传入的参数,则返回 True;否则返回 False。

哈夫曼树的特点:

带权路径长度(WPL, Weighted Path Length)最小

·对于一组给定的字符及其出现的频率(即权重),哈夫曼树能够构造出一种编码方式,使得这些字符的平均编码长度最短。

·这是通过构建一种特殊的二叉树来实现的,其中每个字符都位于树的一个叶节点上,且该叶节点到根节点的路径上的边所代表的字符(在哈夫曼编码中通常使用0和1表示)共同构成了该字符的编码。

贪心算法构建

·哈夫曼树的构建过程采用了贪心算法的策略。

·它从包含n个叶节点的森林开始,每个叶节点代表一个字符及其出现的频率。

·然后,算法反复地选择两个频率最小的节点,将它们合并为一个新的节点,新节点的频率是两个子节点频率之和。

·新节点作为这两个子节点的父节点,而原来的两个节点则成为新节点的左右子节点(通常约定左子节点频率小于等于右子节点)。

·这个过程一直进行,直到森林中只剩下一个节点为止,这个节点即为根节点,整棵树就是所求的哈夫曼树。


知识点:哈夫曼树、堆、二叉树的中序遍历、贪心


结语:越简单的题目解法应该越多,请路过大神留下新的思路供本小白学习一下,打开思路


http://www.ppmy.cn/embedded/110914.html

相关文章

网络学习-eNSP配置路由器

#PC1网关&#xff1a;192.168.1.254 #PC3网关&#xff1a;192.168.3.254 #PC4网关&#xff1a;192.168.4.254# 注&#xff1a;路由器接口必须配置不同网段IP地址 <Huawei>system-view Enter system view, return user view with CtrlZ. #给路由器两个接口配置IP地址 [Hua…

Linux 上安装 PostgreSQL

Linux 上安装 PostgreSQL PostgreSQL 是一款功能强大的开源关系数据库管理系统,因其稳定性、可扩展性和先进的功能而广受欢迎。在 Linux 系统上安装 PostgreSQL 是一个相对直接的过程,但具体步骤可能会因您使用的 Linux 发行版而异。本文将介绍在几种流行的 Linux 发行版上安…

AIPaperGPT写论文靠谱吗?

AIPaperGPT&#xff0c;论文写作神器~ https://www.aipapergpt.com/ 在信息爆炸的今天&#xff0c;学术写作的挑战日益增加&#xff0c;而AIPaperGPT作为一款旨在提升写作效率的工具&#xff0c;其可靠性自然成为了用户关注的焦点。本文将从多个维度对AIPaperGPT进行全面评估&…

CCF编程能力等级认证GESP—C++1级—20240907

CCF编程能力等级认证GESP—C1级—20240907 单选题&#xff08;每题 2 分&#xff0c;共 30 分&#xff09;判断题&#xff08;每题 2 分&#xff0c;共 20 分&#xff09;编程题 (每题 25 分&#xff0c;共 50 分)小杨购物第二题 单选题&#xff08;每题 2 分&#xff0c;共 30…

链表的实现

链表是数据结构中一种基础且重要的数据结构&#xff0c;它允许我们有效地在序列中插入和删除元素&#xff0c;而无需重新分配整个数据结构。与数组相比&#xff0c;链表提供了更高的灵活性&#xff0c;但也可能在访问速度上有所牺牲。现在我将将从基础概念出发&#xff0c;逐步…

Java 中处理 XML 文件

在 Java 中处理 XML 文件&#xff0c;通常使用两种主要的解析方式&#xff1a;DOM 解析 和 SAX 解析。每种解析方式各有优劣&#xff0c;适用于不同的场景。下面详细解释这两种 XML 解析方法的基本原理、适用场景、共性规律、注意事项和特殊技巧。 1. DOM 解析 (Document Obje…

架构师备考的一些思考(二)

前言 以我的视野来看&#xff0c;部长或技术总监这种岗位还是比较难竞争的&#xff0c;换言之&#xff0c;程序员的上升空间比较窄&#xff0c;如果想要拿到高级岗位&#xff0c;最好的是工作三五年后就转项目经理&#xff0c;然后再往上爬。 架构师倒是也能晋升高级岗位&#…

blender我的对称模型好像中点被我不小心移动了 我现在如果雕刻 两边修改的地方不是对称的 我该怎么办

blender我的对称模型好像中点被我不小心移动了 我现在如果雕刻 两边修改的地方不是对称的 我该怎么办 首先请调整好模型确保左右前后对其相应的xyz轴 之后CtrlA应用变换 确保这些都归0且模型和xyz轴对应 如果在Blender中模型的中点&#xff08;对称轴&#xff09;不小心被移动了…