数据结构与算法(Java描述)-19、哈夫曼树、哈夫曼编码算法

2023-11-12

一、哈夫曼树的基本概念

在一棵二叉树中,定义从A结点到B结点所经过的分支序列叫做从A结点到B结点的路径
从A结点到B结点所经过的分支个数叫做从A结点到B结点的路径长度

从二叉树的根结点到二叉树中所有叶结点的路径长度之和称作该二叉树的路径长度

如果二叉树中的叶结点都带有权值,我们可以把这个定义加以推广。设二叉树有n个带权值的叶结点,定义从二叉树的根结点到二叉树中所有叶结点的路径长度与相应叶结点权值的乘积之和为该二叉树的带权路径长度(WPL),即:

WPL =

wi为第i个叶结点的权值,li为从根结点到第i个叶结点的路径长度

二、哈夫曼树

具有相同叶结点和不同带权路径长度的二叉树


(a)WPL = 1×2+3×2+5×2+7×2 = 32
(b)WPL = 1×2+3×3+5×3+7×1 = 33
(c)WPL = 7×3+5×3+3×2+1×1 = 43

(d)WPL = 1×3+3×3+5×2+7×1 = 29

三、哈夫曼树构造算法

根据哈夫曼树的定义,要使一棵二叉树的带权路径长度WPL值最小,必须使权值越大的叶结点越靠近根结点。哈夫曼提出的构造哈夫曼树构造算法为:
(1)由给定的n个权值{w1,w2,…,wn}构造n棵只有根 结点的二叉树,从而得到一个二叉树森林F={T1,T2,…,Tn}。

(2)在二叉树森林F中选取根结点的权值最小和次小的两棵二叉树作为新的二叉树的左右子树构造新的二叉树,新的二叉树的根结点权值为左右子树根结点权值之和。

(3)在二叉树森林F中删除作为新二叉树左右子树的两棵二叉树,将新二叉树加入到二叉树森林F中。

(4)重复步骤(2)和(3),当二叉树森林F中只剩下一棵二叉树时,这棵二叉树就是所构造的哈夫曼树。

对于一组给定的叶结点,设它们的权值集合为{7,5,3,1},按哈夫曼树构造算法对此集合构造哈夫曼树的过程如图所示。 


四、哈夫曼树编码问题

哈夫曼树可用于构造代码总长度最短的编码方案。具体构造方法如下:

设需要编码的字符集合为{d1,d2,…,dn},各个字符在电文中出现的次数集合为{w1,w2,…,wn},以d1,d2,…,dn作为叶结点,以w1,w2,…,wn作为各叶结点的权值构造一棵二叉树,规定哈夫曼树中的左分支为0,右分支为1,则从根结点到每个叶结点所经过的分支对应的0和1组成的序列便为该结点对应字符的编码。这样的代码总长度最短的不等长编码称之为哈夫曼编码。


哈夫曼编码的数据结构设计

设计哈夫曼树的结点存储结构为双亲孩子存储结构。


存放哈夫曼编码的存储结构为


五、哈夫曼树以及哈夫曼编码的实现

//哈夫曼树的结点类
public class HaffNode {

    int weight; //权值
    int parent;//双亲
    int flag;//标志
    int leftChild; //左孩子
    int rightChild;//右孩子

    HaffNode() {

    }

}
//哈夫曼编码类
public class Code {

    int[] bit;  //编码数组
    int start;  //编码的开始下标
    int weight; //权值

    Code(int n) {
        bit = new int[n];
        start = n - 1;
    }
}
/哈夫曼树类
public class HaffmanTree {
    //最大权值
    static final int MAXVALUE = 1000;
    int nodeNum; //叶子结点个数

    public HaffmanTree(int n) {
        this.nodeNum = n;
    }

    //构造哈夫曼树算法
    public void haffman(int[] weight, HaffNode[] nodes) {
        int n = this.nodeNum;
        //m1,m2,表示最小的两个权值,x1,x2,表示最小两个权值对应的编号
        int m1, m2, x1, x2;

        //初始化所有的结点,对应有n个叶子结点的哈夫曼树,有2n-1个结点。
        for (int i = 0; i < 2 * n - 1; i++) {
            HaffNode temp = new HaffNode();
            //初始化n个叶子结点
            if (i < n) {
                temp.weight = weight[i];
            } else {
                temp.weight = 0;
            }
            temp.parent = 0;
            temp.flag = 0;
            temp.leftChild = -1;
            temp.rightChild = -1;
            nodes[i] = temp;
        }

        //初始化n-1个非叶子结点
        for (int i = 0; i < n - 1; i++) {
            m1 = m2 = MAXVALUE;
            x1 = x2 = 0;
            for (int j = 0; j < n + i; j++) {
                if (nodes[j].weight < m1 && nodes[j].flag == 0) {
                    m2 = m1;
                    x2 = x1;
                    m1 = nodes[j].weight;
                    x1 = j;
                } else if (nodes[j].weight < m2 && nodes[j].flag == 0) {
                    m2 = nodes[j].weight;
                    x2 = j;
                }
            }
            nodes[x1].parent = n + i;
            nodes[x2].parent = n + i;
            nodes[x1].flag = 1;
            nodes[x2].flag = 1;
            nodes[n + i].weight = nodes[x1].weight + nodes[x2].weight;
            nodes[n + i].leftChild = x1;
            nodes[n + i].rightChild = x2;
        }


    }

    //哈弗曼编码算法
    public void haffmanCode(HaffNode[] nodes, Code[] haffCode) {
        int n = this.nodeNum;
        Code code = new Code(n);
        int child, parent;

        for (int i = 0; i < n; i++) {
            code.start = n - 1;
            code.weight = nodes[i].weight;
            child = i;
            parent = nodes[child].parent;
            while (parent != 0) {
                if (nodes[parent].leftChild == child) {
                    code.bit[code.start] = 0;
                } else {
                    code.bit[code.start] = 1;
                }

                code.start--;
                child = parent;
                parent = nodes[child].parent;
            }

            Code temp = new Code(n);
            for (int j = code.start + 1; j < n; j++) {
                temp.bit[j] = code.bit[j];
            }
            temp.weight = code.weight;
            temp.start = code.start;
            haffCode[i] = temp;
        }


    }
}
public class Test {

    public static void main(String[] args) {

        int n = 4;
        int[] weight = {1, 3, 5, 7};
        HaffmanTree haffTree = new HaffmanTree(n);
        HaffNode[] nodes = new HaffNode[2 * n - 1];
        Code[] codes = new Code[n];
        //构造哈夫曼树
        haffTree.haffman(weight, nodes);
        //生成哈夫曼编码
        haffTree.haffmanCode(nodes, codes);

        //打印哈夫曼编码
        for (int i = 0; i < n; i++) {
            System.out.print("Weight=" + codes[i].weight + " Code=");
            for (int j = codes[i].start + 1; j < n; j++) {
                System.out.print(codes[i].bit[j]);
            }
            System.out.println();
        }
    }

}

运行结果:



本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

数据结构与算法(Java描述)-19、哈夫曼树、哈夫曼编码算法 的相关文章

  • 五大常用经典算法

    五大常用算法之一 分治算法 一 基本概念 在计算机科学中 分治法是一种很重要的算法 字面上的解释是 分而治之 就是把一个复杂的问题分成两个或更多的相同或相似的子问题 再把子问题分成更小的子问题 直到最后子问题可以简单的直接求解 原问题的解即
  • python 历险记(五)— python 中的模块

    目录 前言 基础 模块化程序设计 模块化有哪些好处 什么是 python 中的模块 引入模块有几种方式 模块的查找顺序 模块中包含执行语句的情况 用 dir 函数来窥探模块 python 的内置模块有哪些 结语 参考文档 系列文章列表 前言
  • 算法--将数组分成和相等的多个子数组,求子数组的最大个数

    作者 陈太汉 一个整数数组 长度为n 将其分为m份 使各份的和相等 求m的最大值 比如 3 2 4 3 6 可以分成 3 2 4 3 6 m 1 3 6 2 4 3 m 2 3 3 2 4 6 m 3 所以m的最大值为3 算法 原理的思想是
  • 一文弄懂循环链表、双向链表、静态链表

    循环链表 双向链表 静态链表 三遍定律 理解了单链表本文的理解易如反掌 单链表请点击这里 理解了单链表本文的理解易如反掌 单链表请点击这里 理解了单链表本文的理解易如反掌 单链表请点击这里 1 循环链表 将单链表中终端结点的指针端由空指针改
  • 数据结构----链式栈

    目录 前言 链式栈 操作方式 1 存储结构 2 初始化 3 创建节点 4 判断是否满栈 5 判断是否空栈 6 入栈 7 出栈 8 获取栈顶元素 9 遍历栈 10 清空栈 完整代码 前言 前面我们学习过了数组栈的相关方法 链接 线性表 栈 栈
  • 微软2013暑假实习生笔试题

    自己mark一下 以作后备 下面提交原文链接 原文博客 部分题目答案不确定 会持续更新 1 Which of the following calling convention s support s supportvariable leng
  • DDP入门

    DDP 即动态动态规划 可以用于解决一类带修改的DP问题 我们从一个比较简单的东西入手 最大子段和 带修改的最大子段和其实是常规问题了 经典的解决方法是用线段树维护从左 右开始的最大子段和和区间最大子段和 然后进行合并 现在我们换一种方法来
  • 数据结构与算法学习总结(六)——字符串的模式匹配算法

    基本概念 字符串是一种特殊的线性表 即元素都是 字符 的线性表 字符是组成字符串的基本单位 字符的取值依赖于字符集 例如二进制的字符集为0 1 则取值只能为 0 1 再比如英语语言 则包括26个字母外加标点符号 例如 abcde 就是一个字
  • 递归算法中的时间复杂度分析

    对于一种算法的时间复杂度分析还是特别重要的 在一些非递归算法中 我们仅仅看运算次数最多的那一行代码可能执行多少次就可以 实际就是看在循环中变量的变化 但是对于递归算法中该怎么分析呢 下面介绍几种递归函数中的算法时间复杂度分析的方法 0 递推
  • 如何根据链表节点数据大小对链表节点进行排序

    对链表排序有两种方法 1 比较了两个节点的大小后 对指针进行改变 从而交换节点的顺序 2 比较了两个节点的大小后 只交换数据域 而不改变指针 从而交换节点的顺序 第二种办法比较简单 本文主要对第二种方法进行讲解 链表节点排序算法 采用 冒泡
  • 『Python基础-15』递归函数 Recursion Function

    什么是递归函数 一种计算过程 如果其中每一步都要用到前一步或前几步的结果 称为递归的 用递归过程定义的函数 称为递归函数 例如连加 连乘及阶乘等 凡是递归的函数 都是可计算的 即能行的 递归就是一个函数在它的函数体内调用它自身 编程语言中的
  • 数据结构之图的两种遍历实现(C语言版)

    上一期文章分享完了图的两种遍历方式 也是两种很重要的算法 DFS和BFS 这两种算法的应用和重要性我就不多说了 内行的人懂的都懂 今天这文章重要就是来上机实现这两种算法 又由于这两种算法都可以由邻接矩阵和邻接表来表示 博主分享的代码都是上机
  • 堆栈01--用两个栈实现队列

    堆栈01 用两个栈实现队列 jz05 题目概述 解析 参考答案 注意事项 说明 题目概述 算法说明 用两个栈来实现一个队列 完成队列的Push和Pop操作 队列中的元素为int类型 测试用例 队列先进先出 输入 1 2 输出 1 2 解析
  • 数理统计知识整理——回归分析与方差分析

    题记 时值我的北科研究生第一年下 选学 统计优化 课程 备考促学 成此笔记 以谨记 1 线性回归 1 1 原理分析 要研究最大积雪深度x与灌溉面积y之间的关系 测试得到近10年的数据如下表 使用线性回归的方法可以估计x与y之间的线性关系 线
  • 区块链中的哈希算法

    区块链中的密码学 密码学在区块链中的应用主要有两个 哈希算法与非对称加密算法 这次主要对哈希算法进行详细的说明 哈希算法 哈希算法的特点有 1 输入可以为任意大小的字符串 2 产生固定大小的输出 3 可以在合理的时间内算出输出值 若要满足密
  • 【试题】排列组合

    在写一个远程的代码 如果本地有M个显示器 远程有N个显示器 M lt N 依据分辨率 显示器刷新频率等要求 需要对远程的N个显示器进行最佳分辨率修改 之后 需要从N个远程显示器中选择M个 跟本地显示器进行一对一的匹配 即从 A N M N
  • 插入排序超详解释,一看就懂

    目录 一 插入排序的相关概念 1 基本思想 2 基本操作 有序插入 二 插入排序的种类 三 直接插入排序 1 直接插入排序的过程 顺序查找法查找插入位置 2 使用 哨兵 直接插入排序 四 直接插入排序算法描述 五 折半插入排序 1 查找插入
  • Leetcode1094. 拼车

    Every day a Leetcode 题目来源 1094 拼车 解法1 差分数组 对于本题 设 a i 表示车行驶到位置 i 时车上的人数 我们需要判断是否所有 a i 都不超过 capacity trips i 相当于把 a 中下标从
  • C++ AVL树(四种旋转,插入)

    C AVL树 四种旋转 插入 一 AVL树的概念及性质 二 我们要实现的大致框架 1 AVL树的节点定义 2 AVL树的大致框架 三 插入 1 插入逻辑跟BST相同的那一部分 2 修改平衡因子
  • 最大流-Dinic算法,原理详解,四大优化,详细代码

    文章目录 零 前言 一 概念回顾 可略过 1 1流网络 1 2流 1 3最大流 1 4残留网络 1 5增广路

随机推荐

  • C++57个入门知识点_37 虚函数的直接调用与间接调用(函数的调用分为直接调用和间接调用,间接调用是虚函数所具有的的性质;间接调用:运行期通过查找对象的虚表下标来调用函数的方法)

    前面两篇C 57个入门知识点 35 函数覆盖的概念1 函数覆盖条件 父子类继承关系 函数名 参数列表 返回值 调用约定必须相同 有virtual关键字 函数覆盖 类虚表中成员函数从继承自父类变为自己的 C 57个入门知识点 36 函数覆盖的
  • Android中的Loaders机制

    转自 http blog csdn net guoshaobei article details 17451647 Loaders机制在Android 3 0版本后引入 Loaders机制使一个Activity或者一个Fragment更加容
  • 职工管理系统(C++)

    职工管理系统有以下8个功能 增加职工信息 实现批量添加职工功能 将信息录入到文件中 职工信息为 职工编号 姓名 部门编号 显示职工信息 显示公司内部所有职工的信息 删除离职职工 按照编号删除指定的职工 修改职工信息 按照编号修改职工个人信息
  • python笔记-排序函数

    List排序 sort val list 1 7 3 9 5 6 val list sort sort 没有返回值 在原列表上排序 val list sort reverse True 逆序 print val list 使用sort 方法
  • IDEA启动tomcat控制台中文乱码问题

    IntelliJ IDEA是很多程序员必备且在业界被公认为最好的Java开发工具 有很多小伙伴在安装完IDEA并且tomcat之后 启动tomcat会出现控制台中文乱码问题 如下图所示 具体解决步骤 一 修改当前 Web 项目 Tomcat
  • 从用户页面获取作品列表

    最近web端更新比较频繁 所以搞了很多方案来应对更新问题 本文内容是其中一种方案 从用户主页的HTML响应内容中抽取user信息和作品列表数据 下图中出现的内容都是在html名为RENDER DATA的script标签中 以urlencod
  • spring与loc

    loc 是控制反转 是一个概念 当前比较流行的实现方式有两种 一种是依赖查找 第二就是依赖注入 依赖注入是目前最优秀的解耦方式 第一个小程序 所需的jar包 spring beans 4 2jar spring context 4 2 ja
  • 5折交叉验证_交叉验证:评估模型表现

    注明 本文章所有代码均来自scikit learn官方网站 在实际情况中 如果一个模型要上线 数据分析员需要反复调试模型 以防止模型仅在已知数据集的表现较好 在未知数据集上的表现较差 即要确保模型的泛化能力 它指机器学习对新鲜样本的适应能力
  • 重写或替换jar中的类或方法两种方式

    上一篇 还没毕业 我就进了HR的黑名单 目录 序言 重写jar的两种方式 第一种 第二种 序言 在某些特殊场景下 我们需要修改 jar 包中的某些类和方法 jar 我们没有修改权限 那么怎么重写里面的类和方法呢 本文教你两种常用的方法 分享
  • TortoiseGit SSH拉取GitLab代码

    ING 前述 Git获取远端代码的方式主要有两种https和SSH 这两种方式的主要区别在于 1 https url克隆会比较方便 复制https url然后到git Bash里面直接用clone命令克隆到本地就好了 但是每次fetch和p
  • 黑猫带你学eMMC协议第19篇:eMMC RPMB区域详解(重放保护内存块)

    1 前言 1 1 声明 本文依据eMMC JEDEC5 1 网络资料及个人工作经验整理而成 如有错误请留言 本文结合eMMC JEDEC5 1协议手册查看效果更佳 文章为个人辛苦整理 付费内容 禁止私自转载 1 2 内容提要 本文大约一万一
  • 2. Redis持久化、主从哨兵架构详解

    分布式缓存技术Redis 1 Redis持久化 1 1 RDB快照 snapshot 1 1 1 bgsave的写时复制 COW 机制 1 2 AOF append only file 1 2 1 AOF重写 1 3 Redis 4 0 混
  • flea-frame-db使用之基于EntityManager实现JPA分表的数据库操作【旧】

    基于EntityManager实现JPA分表的数据库操作 引言 1 EntityManager持久化操作 2 分表规则定义 3 分表操作实现 4 自测 4 1 新增数据 4 2 查询数据 4 3 更新数据 4 4 删除数据 更新 引言 本文
  • 在不影响线上服务情况下,删除大表数据表

    在不影响线上数据库服务情况下 如何删除数据库中的大表 分析 数据库中表涉及到db和os两个层面 1 db层面删表涉及到table cache的全局唯一锁 一旦数据表过大 会长时间占用全局为一锁 导致db卡死 2 os层面涉及到数据表物理文件
  • 伤腰的Python爬虫案例,零基础必备实战教程。

    前言 今天带大家采集一个二次元图片网站 里面漂亮的小姐姐层出不穷 图片的数据量也是比较大的 来一睹为快吧 开发环境介绍 python 3 6 pycharm requests parsel os 爬虫案例数据采集一般步骤 找数据对应的链接地
  • conan的学习与使用

    conan学习与使用 资源 官方地址 C C Open Source Package Manager conan io 重要概念 什么是包管理工具 包管理工具的主要作用是管理第三方依赖 也可以看成一个 轮子 工厂 每个人都可以上传自己造的
  • verdi显示数据

    在波形数据上点右键 2 s complement 就是大家计算机课上学的 补码 1 s complement 是课上讲的 反码 signed magnitude 最高位是符号位 0 正数 1 负数 低位是绝对值 另外 ncverilog v
  • FFmpeg命令行工具学习(五):FFmpeg 调整音视频播放速度

    转自 https www cnblogs com renhui p 10709074 html FFmpeg对音频 视频播放速度的调整的原理不一样 下面简单的说一下各自的原理及实现方式 一 调整视频速率 调整视频速率的原理为 修改视频的pt
  • QT之实现简陋聊天

    相关知识 QT 数据库 TCP IP Socket 1 登陆界面 包含登陆和注册两种功能 思路如下 难点 建立服务器和数据库 数据库保存数据 服务器与数据库产生联系 解决 数据库与服务器放在同一个类中 登陆和注册时 客户端与服务端连接 传输
  • 数据结构与算法(Java描述)-19、哈夫曼树、哈夫曼编码算法

    一 哈夫曼树的基本概念 在一棵二叉树中 定义从A结点到B结点所经过的分支序列叫做从A结点到B结点的路径 从A结点到B结点所经过的分支个数叫做从A结点到B结点的路径长度 从二叉树的根结点到二叉树中所有叶结点的路径长度之和称作该二叉树的路径长度