《征服数据结构》哈夫曼树(Huffman Tree)

news2024/9/27 5:32:39

摘要:

1,哈夫曼树的介绍

2,哈夫曼树的构造

3,哈夫曼树带权路径长度计算

4,哈夫曼树的编码

5,哈夫曼树的解码

1,哈夫曼树的介绍

哈夫曼树(Huffman Tree)也叫霍夫曼树,或者赫夫曼树,又称为最优树,是因为它是一种带权路径长度最短的二叉树。在学习哈夫曼树之前我们先来了解一些和哈夫曼树相关的概念:

路径:从任一个节点往下到达其它节点之间的通路。

路径长度:路径中线段的个数。

节点的权:节点的值。

节点的带权路径长度:从根节点到该节点之间的路径长度与该节点权的乘积。

树的带权路径长度:所有叶子节点的带权路径长度之和。

在讲解哈夫曼树之前我们来看这样一个问题,假如老师根据学生的成绩给学生进行评级,有下面几个等级:

String level(int score) {
    if (score < 60) return "不及格";
    else if (score < 70) return "及格";
    else if (score < 80) return "中等";
    else if (score < 90) return "良好";
    else return "优秀";
}

上面的分支语句整理出来像一棵二叉树,如下图所示:

2548ba48c1016b2933e05fc5320f7d4e.png

假如同学的成绩分布如下:

90分以上:10%
80到90分:35%
70到80分:45%
60到70分:8%
60分以下:2%

可以看到60分以下的只有2%,但我们每次都是先判断是否小于60,很明显大多数情况下都不小于60,也就是无效的判断。

为了减少判断次数,最有效的判断方式就是占比越高的离根节点越近。可以看到分数在70到80分的占到45%,80到90分的占到35%,这两个加起来占了80%,这种情况下可以像下图中这样查询。

9a1580f2bce1d12f940443667f63ccf7.png

假如把这里的百分比看作叶子节点的权值,用它构造一棵二叉树,这棵二叉树可以有多种,其中带权路径长度最小的就是最优树,也是哈夫曼树。

哈夫曼树就是给定 n 个权值作为叶子节点,构造一棵二叉树,并且该树的带权路径长度达到最小。

树的带权路径长度 WPL(Weighted Path Length of Tree) 记为 WPL=(W1*L1+W2*L2+W3*L3+...+Wn*Ln) ,其中 W 表示节点的权值, L 表示从根节点到该节点的路径长度。

要想让树的带权路径长度最小,权值越大的节点离根节点越近。如下图所示,是用权值为 9,3,2,8 分别构造的两棵树,很明显左边树的带权路径长度比右边树的带权路径长度要小。

061eab16e0b4bcba2182630968363c23.png

2,哈夫曼树的构造

哈夫曼树构造的原则是权值越大离根节点越近,使用的是贪心算法,步骤如下:

1. 用给定的 n 个权值创建 n 棵只有一个节点的树,把它们添加到集合 S 中。

2. 每次从集合 S 中取出两个权值最小的树,让它们组成一棵新的二叉树,新树的权值为它的两棵子树的权值之和,然后把这棵新的树添加到集合 S 中。

3. 重复步骤 2 ,直到集合中只有一棵树为止,这棵树就是哈夫曼树。

e18dbc9dad8bd6c7abeb09ca24e40c3a.png

1567d647f025a7d19ed9815c40ca68ea.png

如上图所示,因为每次都是选择两棵子树合并成一棵,所以哈夫曼树只有度为 0 和 2 的节点,没有度为 1 的节点,也就是说哈夫曼树中每个节点要么没有子节点,要么有两个子节点,不可能只有一个子节点。

在二叉树中,度为 2 的节点贡献两条边,度为 1 的节点贡献一条边,度为 0 的节点不贡献任何边。在二叉树中除了根节点外,每一个节点都有一个父节点指向它,所以在任何二叉树中节点的数量等于边的数量加 1 。

假如哈夫曼树中,度为 0 (叶子节点)的节点有 n 个,度为 2 的节点有 m 个,我们可以得出边的数量是 2m ,总的节点是 m+n,根据 m+n=2m+1,可以得出 m=n-1,总的节点数就是 2n-1,所以我们可以得出一棵有 n 个叶子节点的哈夫曼树总共有 2n-1 个节点。

每次从集合中取出权值最小的两棵树,这里的集合我们可以使用最小堆,来看下代码。

Java 代码:

// 哈夫曼树的节点类。
public class HNode {
    // 节点对应的字符,只有叶子节点有,在编码和解码的时候会用到。
    private Character ch;
    private int weight;// 权值。
    private HNode left;// 左子树。
    private HNode right;// 右子树。
    private int deep;// 路径长度,也是节点的深度。

    public HNode(int weight) {
        this.weight = weight;
    }

    public HNode(HNode left, HNode right, int weight) {
        this.left = left;
        this.right = right;
        this.weight = weight;
    }
}

// 构建哈夫曼树
public HNode createTree(int[] nums) {
    // 优先队列,这里使用最小堆
    PriorityQueue<HNode> pq = new PriorityQueue<>(Comparator.comparingInt(o -> o.weight));
    // 用数字创建只有一个节点的树并全部添加到堆中。
    for (int num : nums)
        pq.offer(new HNode(num));

    while (pq.size() > 1) {// 大于 1 就合并。
        HNode left = pq.poll();// 出队。
        HNode right = pq.poll();
        // 两棵子树合并成一棵。
        HNode parent = new HNode(left, right, left.weight + right.weight);
        // 把合并之后的子树添加到队列中。
        pq.add(parent);
    }
    return pq.poll();// 最后一个就是构造成的哈夫曼树。
}

C++ 代码:

struct HNode {// 哈夫曼树的节点类。
    // 节点对应的字符,只有叶子节点有,在编码和解码的时候会用到。
    char ch;
    int weight = 0;// 权值。
    HNode *left = nullptr;// 左子树。
    HNode *right = nullptr;// 右子树。
    int deep = 0;// 路径长度,也是节点的深度。
    HNode(int weight) : weight(weight) {}

    HNode(HNode *left, HNode *right, int weight) : left(left), right(right), weight(weight) {}
};


// 构建哈夫曼树
HNode *createTree(vector<int> &nums) {
    auto cmp = [](HNode *a, HNode *b) { return a->weight > b->weight; };
    // 优先队列,这里使用最小堆
    priority_queue<HNode *, vector<HNode *>, decltype(cmp)> pq(cmp);
    // 创建节点并全部添加到堆中。
    for (int num: nums)
        pq.push(new HNode(num));

    while (pq.size() > 1) {// 大于 1 就合并。
        HNode *left = pq.top();
        pq.pop();// 出队。
        HNode *right = pq.top();
        pq.pop();// 出队。
        // 两棵子树合并成一棵。
        auto *parent = new HNode(left, right, left->weight + right->weight);
        // 把合并之后的子树添加到队列中。
        pq.push(parent);
    }
    return pq.top();// 最后一个就是构造成的哈夫曼树。
}

3,哈夫曼树带权路径长度计算

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

游戏怎么录制?王者荣耀游戏录制指南:iOS与电脑端全面教程

在王者荣耀的战场上&#xff0c;每一个五杀、每一次极限逃生都可能成为你游戏生涯中的高光时刻。但这些瞬间往往转瞬即逝&#xff0c;如何将它们永久保存&#xff0c;成为你游戏历程中不可磨灭的印记呢&#xff1f;本文将为你揭晓答案。无论你是手持iPhone的iOS用户&#xff0c…

正则中捕获组和非捕获组区别

捕获组和非捕获组 一. 捕获组&#xff08;Capturing Groups&#xff09;二. 非捕获组&#xff08;Non-Capturing Groups&#xff09;三. 区别四. 选择使用 这是我在这个网站整理的笔记,有错误的地方请指出&#xff0c;关注我&#xff0c;接下来还会持续更新。 作者&#xff1a;…

GESP等级考试C++二级-数学函数

C的cmath库中有丰富的数学函数&#xff0c;通过这些函数可以进行相应的数学计算。 1 cmath库的导入 通过import指令导入cmath库&#xff0c;代码如图1所示。 图1 导入cmath库的代码 2 abs()函数 abs()函数用来获取指定数的绝对值&#xff0c;代码如图2所示。 图2 abs()函数…

【递归】7. leetcode 404 左叶子之和

1 题目描述 题目链接&#xff1a;左叶子之和 2 解答思路 递归分为三步&#xff0c;接下来就按照这三步来思考问题 第一步&#xff1a;挖掘出相同的子问题 &#xff08;关系到具体函数头的设计&#xff09; 第二步&#xff1a;只关心具体子问题做了什么 &#xff08;关系…

macOS安装Redis教程, 通过brew命令, 时间是2024年9月26日, redis版本是0.7.2

搜索: brew search redis安装Redis: brew install redis关于启动命令的提示: To start redis now and restart at login:brew services start redis Or, if you dont want/need a background service you can just run:/opt/homebrew/opt/redis/bin/redis-server /opt/home…

【图像处理】多幅不同焦距的同一个物体的平面图象,合成一幅具有立体效果的单幅图像原理(二)

实现多幅不同焦距图像合成一幅具有立体效果的图像可以使用以下算法和开源库&#xff1a; 实现算法 图像对齐 使用特征点匹配&#xff08;如 SIFT、SURF 或 ORB&#xff09;来对齐图像。利用 RANSAC 算法剔除离群点&#xff0c;估计变换矩阵。 深度图生成 基于图像的焦距和视角…

Teams集成-会议侧边栏应用开发-会议转写

Teams应用开发&#xff0c;主要是权限比较麻烦&#xff0c;大量阅读和实践&#xff0c;摸索了几周&#xff0c;才搞明白。现将经验总结如下&#xff1a; 一、目标&#xff1a;开发一个Teams会议的侧边栏应用&#xff0c;实现会议的实时转写。 二、前提&#xff1a; 1&#x…

探索5 大 Node.js 功能

目录 单线程 Node.js 工作线程【Worker Threads】 Node.js 进程 进程缺点 工作线程 注意 集群进程模块【Cluster Process Module】 内部发生了什么&#xff1f; 为什么要使用集群 注意&#xff1a; 应用场景&#xff1a; 内置 HTTP/2 支持 这个 HTTP/2 是什么&…

OpenHarmony(鸿蒙南向)——平台驱动指南【PWM】

往期知识点记录&#xff1a; 鸿蒙&#xff08;HarmonyOS&#xff09;应用层开发&#xff08;北向&#xff09;知识点汇总 鸿蒙&#xff08;OpenHarmony&#xff09;南向开发保姆级知识点汇总~ 持续更新中…… 概述 功能简介 PWM即脉冲宽度调制&#xff08;Pulse Width Modul…

【Linux】部署 flask

文章目录 一、安装Python3环境二、将本地开发环境的依赖项目生成清单文件三、创建虚拟环境启用虚拟环境退出虚拟环境 四、添加自定义系统服务(很重要) 一、安装Python3环境 1.首先安装编译环境(后续需要从python官网获取Python3的源码自己编译python yum install zlib-devel …

案例研究丨国控星鲨利用DataEase释放数据潜能,重塑业务视野

国药控股星鲨制药&#xff08;厦门&#xff09;有限公司&#xff08;以下简称为国控星鲨&#xff09;始创于1952年&#xff0c;前身为厦门鱼肝油厂&#xff0c;距今已经有70余年历史&#xff0c;是国家商务部认定的“中华老字号”企业。2011年&#xff0c;国药控股与厦门轻工集…

C# C++ 笔记

第一阶段知识总结 lunix系统操作 1、基础命令 &#xff08;1&#xff09;cd cd /[目录名] 打开指定文件目录 cd .. 返回上一级目录 cd - 返回并显示上一次目录 cd ~ 切换到当前用户的家目录 &#xff08;2&#xff09;pwd pwd 查看当前所在目录路径 pwd -L 打印当前物理…

从“可用”到“好用”,百度智能云如何做大模型的“超级工厂”?

如果说&#xff0c;过去两三年大模型处于造锤子阶段&#xff0c;那么今年&#xff0c;更多的则是考验钉钉子的能力&#xff0c;面对各类业务场景大模型是否能够有的放矢、一击必中&#xff0c;为千行百业深度赋能。 当前市场上&#xff0c;已经有200多把这样的锤子在疯狂找钉子…

从零开始使用树莓派debian系统使用opencv4.10.0进行人脸识别(保姆级教程)

一、总体架构 本文主要是使用树莓派自带的csi摄像头&#xff0c;搭配上opencv4.10.0进行物体的识别。本文使用的环境是python3.7.3&#xff0c;环境不一样有可能安装的opencv的过程也会很不一样&#xff0c;但是python的环境我们可以自己自行安装。 二、树莓派系统的安装 本文…

江协科技STM32学习- P19 TIM编码器接口

&#x1f680;write in front&#x1f680; &#x1f50e;大家好&#xff0c;我是黄桃罐头&#xff0c;希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流 &#x1f381;欢迎各位→点赞&#x1f44d; 收藏⭐️ 留言&#x1f4dd;​…

亲身体验Llama 3.1:开源模型的部署与应用之旅

文章目录 1 Llama 3.1系列的诞生2 大型模型的未来发展3 使用教程4 Llama 3.1在客户服务中的运用 1 Llama 3.1系列的诞生 在人工智能的浪潮中&#xff0c;大型语言模型&#xff08;LLM&#xff09;正以其独特的魅力和潜力&#xff0c;成为深度学习领域的一颗耀眼明星。 这些模…

计算机毕业设计Hadoop+Spark知识图谱体育赛事推荐系统 体育赛事热度预测系统 体育赛事数据分析 体育赛事可视化 体育赛事大数据 大数据毕业设计

《HadoopSpark知识图谱体育赛事推荐系统》开题报告 一、研究背景及意义 随着互联网技术的迅猛发展和大数据时代的到来&#xff0c;体育赛事数据的数量呈爆炸式增长。用户面对海量的体育赛事信息&#xff0c;常常感到信息过载&#xff0c;难以快速找到感兴趣的赛事内容。如何高…

锐捷—NAT地址映射+IPsec隧道

任务目标 在出口路由器R3上将R5私网地址1对1映射的公网地址与R1建立IPsec隧道&#xff0c;使得R4在访问R5的映射公网地址时&#xff0c;可以进行IPsec隧道的转发 要求&#xff1a; 1、R4和R5可通过NAT转换正常访问互联网地址&#xff08;R2的lo0&#xff09; 2、R5的私网地…

云手机群控怎么用?有什么优势?

群控系统&#xff0c;顾名思义&#xff0c;是用于批量控制多部手机的工具&#xff0c;能够通过计算机或客户端同时管理多台设备。借助群控系统&#xff0c;用户可以在电脑上操作多部手机&#xff0c;模拟真实操作场景&#xff0c;从而大幅提升工作效率&#xff0c;并有效控制管…

calibre-web默认左上角字体修改

calibre-web默认左上角字体修改 如图&#xff1a; 有些奇异&#xff0c;如果想变成正常的常规字体&#xff0c;需要修改&#xff1a; cps\static\css\style.css 下的代码&#xff1a; 默认是GrandHotel-Regular&#xff1a; 换成其他字体即可。其他字体在 calibre-web\cps\s…