哈夫曼树和哈夫曼编码详解(包含Java代码实现)

news2024/11/23 23:47:27

目录

  • 什么是哈夫曼树?
  • 如何构造哈夫曼树?
    • 构造过程
    • 代码实现
      • 哈夫曼树的结构
      • 构建哈夫曼树并计算WPL值
      • 测试代码
  • 什么是哈夫曼编码?
  • 如何构建哈夫曼编码?
    • 构建过程
    • 代码实现

什么是哈夫曼树?

哈夫曼树又称为最优树,是一类带权路径长度最短的树,在实际中有着广泛的应用。介绍哈夫曼树之前,我们需要了解下面几个概念:

路径:从树中的一个节点到另一个节点之间的分支构成这两个节点之间的路径。
路径长度路径上的分支数目称为路径长度。
树的路径长度:从树根到每一节点的路径长度之和。
:赋予某个实体的一个量,是对实体的某个属性或某些属性值的描述。
节点的带权路径长度:从该节点到树根之间的路径长度乘上权
树的带权路经长度:树中所有叶子节点带权路径长度之和,记为 W P L = ∑ k = 1 n w k l k WPL=\sum\limits_{k=1}\limits^{n}w_kl_k WPL=k=1nwklk
哈夫曼树:对于带有不同权值的节点,能够构造出的WPL最小的二叉树称为哈夫曼树。

  • 例如下图中 a a a, b b b, c c c, d d d 四个叶子节点权值分别为:7、5、2、4。不同的构造方法会使得 W P L WPL WPL 不同。

1

如何构造哈夫曼树?

构造过程

  • 以下图为例:给定了 a a a b b b c c c d d d e e e 五个节点,权值分别为7、5、5、2、4。我们将按照下述步骤构造哈夫曼树:

2

  1. 从给定的节点中选取权值最小的两个节点作为左右孩子构成一棵新的二叉树,新构成的二叉树的权值为其左右孩子权值之和
    3

  2. 从序列中删除用掉的这两个节点,并把新构成的树加入到序列中。

  3. 重复上述步骤1和步骤2,直到序列中只剩下一棵树,这棵树就是哈夫曼树。
    4
    5
    6

  • 经计算得到构造出的哈夫曼树 W P L = 52 WPL=52 WPL=52。这样的构造方法称为哈夫曼算法

代码实现

哈夫曼树的结构

  • 哈夫曼树是二叉树的一种,因此我们可以参考二叉排序树的构造方法创建一个哈夫曼树的节点类。详情可见:二叉排序树详解并通过Java代码实现
public class HTNode {
    int weight; // 权值
    int parent; //父亲节点
    int lChild; // 左孩子
    int rChild; // 右孩子

    @Override
    public String toString() {
        return "HTNode{" +
                "weight=" + weight +
                ", parent=" + parent +
                ", lChild=" + lChild +
                ", rChild=" + rChild +
                '}';
    }
}

构建哈夫曼树并计算WPL值

  • 构造哈夫曼树主要分为两步:

    1. 初始化:申请2n个单元,从1号单元开始存放节点,1-n的位置存放叶子节点,之后的n-1个位置存放其余非叶子节点。
    2. 创建树:通过n-1次循环的选择、删除、合并的操作来完成。从当前森林中选择父亲节点为0权值最小的两个节点,将其父亲节点改为非0并合并,合并后的节点存入第n+1之后的单元中,记录其左右孩子的下标。
  • 计算 W P L WPL WPL 值时,我们从叶子节点开始遍历,直到父亲节点为0为止,记录下路径长度,并乘上叶子节点对应的权值,最后求和即可求得 W P L WPL WPL 值。

public class HuffmanTree {
	// 选择两个最小的节点
    public int[] select(HTNode[] htNodes, int n) {
        int[] res = new int[2];
        // 从下标1开始遍历数组
        int i = 1;
        // 找到还没参与构建树的节点
        while (htNodes[i].parent != 0 && i <= n) {
            ++ i;
        }
        res[0] = i;
        ++ i;
        while (htNodes[i].parent != 0 && i <= n) {
            ++ i;
        }
        // 对两个节点进行比较,res[0]是小的
        if (htNodes[i].weight < htNodes[res[0]].weight) {
            res[1] = res[0];
            res[0] = i;
        } else {
            res[1] = i;
        }
        // 后续节点和已经存入的两个节点去比较
        for (int j = i + 1; j <= n; j ++) {
            // 如果有父亲节点,说明已经参与构建,则直接跳过
            if (htNodes[j].parent != 0) {
                continue;
            }
            // 如果比小的还小
            if (htNodes[j].weight < htNodes[res[0]].weight) {
                res[1] = res[0];
                res[0] = j;
                // 如果介于两者之间
            } else if (htNodes[j].weight < htNodes[res[1]].weight) {
                res[1] = j;
            }

        }
        return res;
    }
    
    // 创建哈夫曼树
    public HTNode[] createHuffmanTree(int[] weights) {
        // 初始化
        int n = weights.length;
        if (n <= 1) {
            return new HTNode[0];
        }
        HTNode[] htNodes = new HTNode[2 * n];
        for (int i = 1; i <= n; i ++) {
            htNodes[i] = new HTNode();
            htNodes[i].weight = weights[i - 1];
        }

        // 构建哈夫曼树
        for (int i = n + 1; i < 2 * n; i ++) {
            htNodes[i] = new HTNode();
            // 选择
            int[] min = select(htNodes, i - 1);
            // 删除合并
            htNodes[min[0]].parent = i;
            htNodes[min[1]].parent = i;
            // 存放
            htNodes[i].lChild = min[0];
            htNodes[i].rChild = min[1];
            htNodes[i].weight = htNodes[min[0]].weight + htNodes[min[1]].weight;
        }
        return htNodes;
    }

	// 计算构建出的哈夫曼树的WPL值
    public int calculateWPL(int[] weights) {
        HTNode[] htNodes = createHuffmanTree(weights);
        int sum = 0;
        for (int i = 1; i <= weights.length; i ++) {
            int cnt = 0;
            int index = i;
            while (htNodes[index].parent != 0) {
                cnt ++;
                index = htNodes[index].parent;
            }
            cnt *= htNodes[i].weight;
            sum += cnt;
        }
        return sum;
    }
}

测试代码

public class Test {
    public static void main(String[] args) {
        int[] weights = new int[5];
        weights[0] = 7;
        weights[1] = 5;
        weights[2] = 5;
        weights[3] = 2;
        weights[4] = 4;
        HuffmanTree huffman = new HuffmanTree();
        HTNode[] htNodes = huffman.createHuffmanTree(weights);
        for (int i = 1; i < htNodes.length; i ++) {
            System.out.println(htNodes[i]);
        }
        System.out.println("WPL = " + huffman.calculateWPL(weights));
    }
}
  • 测试结果:
    7

什么是哈夫曼编码?

在远程通讯中,带传输的字符内容往往都是压缩成二进制的字符串进行传输。对于每一个字符,我们可以给定一个唯一确定的数字来代表(例如ASCII编码),这种形式称为编码。

编码包含定长编码和不定长编码对于定长编码,我们在解码时直接通过字符串截取就可以实现;对于不定长编码,我们需要确保编码为前缀编码(任何一个编码都不是其他任何编码的前缀(左子串)),才能够实现正常的解码操作。
哈夫曼编码就是最优前缀编码。

如何构建哈夫曼编码?

构建过程

  1. 统计每个字符出现的频率作为其权值
  2. 将每个字符作为叶子节点构建哈夫曼树。
  3. 在哈夫曼树的每个分支上标记 0 0 0 1 1 1(左 0 0 0 1 1 1

8

  1. 从根节点开始读取,直到叶子节点即为该字符的编码。

9

PS:由于没有一个叶子节点是其他叶子节点的祖先,所以每个字符的编码都不可能包含其他字符编码的前缀。

代码实现

  • 从叶子节点出发,逆序遍历每一个节点,最后再将结果反转即可得到编码。
public String[] HuffmanCoding(int[] weights) {
    int n = weights.length;
    HTNode[] htNodes = createHuffmanTree(weights);
    String[] huffmanCoding = new String[n];
    for (int i = 1; i <= n; i ++) {
        huffmanCoding[i - 1] = "";
        //从叶子结点出发,得到的哈夫曼编码是逆序的,需要在字符串数组中逆序存放
        String s = "";
        int last = i, cur = i;
        while (htNodes[cur].parent != 0) {
            cur = htNodes[cur].parent;
            s += htNodes[cur].lChild == last ? "0" : "1";
            last = cur;
        }

        // 调整顺序
        for (int j = 0; j < s.length(); j ++) {
            huffmanCoding[i - 1] += s.charAt(s.length() - j - 1);
        }
    }

    return huffmanCoding;
}
  • 测试代码:
public class Test {
    public static void main(String[] args) {
        int[] weights = new int[5];
        weights[0] = 7;
        weights[1] = 5;
        weights[2] = 5;
        weights[3] = 2;
        weights[4] = 4;
        HuffmanTree huffman = new HuffmanTree();
        HTNode[] htNodes = huffman.createHuffmanTree(weights);
        for (int i = 1; i < htNodes.length; i ++) {
            System.out.println(htNodes[i]);
        }
        System.out.println();
        System.out.println("WPL = " + huffman.calculateWPL(weights));

        String[] code;
        code = huffman.HuffmanCoding(weights);
        System.out.println();
        for (int i = 0; i < 5; i ++) {
            System.out.println(code[i]);
        }
    }
}

10

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

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

相关文章

应用实例 | Simufact 增材制造工艺仿真助力保时捷薄壁件打印

在过去的 30 多年里&#xff0c;增材制造技术被广泛应用于各行各业&#xff0c;尤其在医疗器械、航空领域尤为突出。其中激光束进行金属粉末床熔融的工艺应用最为广泛&#xff0c;由该工艺制造的零部件普遍兼具高设计自由度、高灵活性、优异机械性能等特点。对于汽车行业&#…

百日筑基第六十天-学习一下Tomcat

百日筑基第六十天-学习一下Tomcat 一、Tomcat 顶层架构 Tomcat 中最顶层的容器是 Server&#xff0c;代表着整个服务器&#xff0c;从上图中可以看出&#xff0c;一个 Server可以包含至少一个 Service&#xff0c;用于具体提供服务。Service 主要包含两个部分&#xff1a;Conn…

都说25张宇是大趋势,那660、880还刷吗?

25上半年张宇很火&#xff0c;是有客观原因的&#xff1a; 1. 大纲改革后&#xff0c;大题变少了&#xff0c;选填变多了&#xff1b; 2. 但是考试覆盖的知识点不能少&#xff0c; ——因为知识点越少&#xff0c;随机性越高&#xff0c;这个考试就越不公平。 这直接导致了&…

vue组件和插件使用

前端组件 1、安装pinia(Vue 的专属状态管理库)&#xff1a; npm install pinia2、安装pinia-plugin-persistedstate(持久存储插件): npm install pinia-plugin-persistedstate浏览器刷新时&#xff0c;有些数据希望是保存下来的。如用户登录后&#xff0c;用户信息会存储在全…

Scheme5.0标准之重要特性及用法实例(三十七)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 新书发布&#xff1a;《Android系统多媒体进阶实战》&#x1f680; 优质专栏&#xff1a; Audio工程师进阶系列…

设置虚拟机使用主机以太网而不是WiF连接

虚拟机使用主机的以太网连接而不是Wi-Fi连接&#xff0c;可以通过在虚拟化软件中配置虚拟机的网络设置来实现。以下是一些常见的虚拟化软件&#xff08;如VMware和VirtualBox&#xff09;中设置虚拟机网络以使用以太网连接的步骤&#xff1a; 一、VMware中设置 1、打开虚拟网…

Python画笔案例-007 绘制水滴

1、绘制水滴 通过 python 的turtle 库绘制一个水滴的图案&#xff0c;如下图&#xff1a; 2、实现代码 今天绘制的水滴&#xff0c;在tuitle 库里并没有直接的功能可以绘制&#xff0c;我们仔细观察&#xff0c;可以看出&#xff0c;水滴往下越来越粗&#xff0c;所以我们可以…

API商品数据接口(电商数据api)——京东淘宝价格详情

众多品牌选择使用比价工具进行采购&#xff0c;主要是出于以下几个重要原因&#xff1a; 提高开发效率&#xff1a;API接口允许不同的应用程序之间高效地进行交互&#xff0c;节省了大量的人力物力成本&#xff0c;使得开发者可以将更多时间和精力集中于自身的核心业务。 增加数…

正则表达式匹配成对括号

匹配一对括号&#xff0c;用于在一个html文本中提取JSon 文本。例如 { “duration”:7599,"minBufferTime{second bracket }{third bracket} } 一对加粗的{} &#xff0c;而不要中间的{}。简单写法会出现错误匹配。 在.Net Framework的正则表达式中&#xff0c;提供了”…

在Activity中使用Toast

在Activity中使用Toast Toast是Android系统提供的一种非常好的提醒方式&#xff0c;在程序中可以使用它将一些短小的信息通知给用户&#xff0c;这些信息会在一段时间后自动消失&#xff0c;并且不会占用任何屏幕空间&#xff0c;我们现在就尝试一下如何在活动中使用Toast。 …

谷粒商城篇章11--P311-P325--秒杀服务【分布式高级篇八】

目录 1 后台添加秒杀商品 1.1 配置优惠券服务网关 1.2 添加秒杀场次 1.3 上架秒杀商品 2 定时任务 2.1 cron 表达式 2.2 cron表达式特殊字符 2.3 cron示例 3 秒杀服务 3.1 创建秒杀服务模块 3.1.1 pom.xml 3.1.2 application.yml配置 3.1.3 bootstrap.yml配置 3.…

【赵渝强老师】Docker三剑客

在Docker容器中提供了三个非常有用的工具&#xff0c;它们分别是&#xff1a;Docker Compose、Docker Machine和Docker Swarm。下面分别进行介绍。 视频讲解如下&#xff1a; Docker三剑客 【赵渝强老师】Docker的三剑客 一、容器编排工具Docker Compose 在使用Docker部署应用…

如何在 Nuxt 中动态设置页面布局

title: 如何在 Nuxt 中动态设置页面布局 date: 2024/8/24 updated: 2024/8/24 author: cmdragon excerpt: 摘要:本文介绍如何在Nuxt框架中通过设置setPageLayout函数动态调整页面布局,包括安装Nuxt、创建不同布局文件及中间件,并通过示例演示如何根据不同路径设置相应布局…

Transformer模型-1-概述、核心部件及应用场景

Transformer概述 什么是Transformer Transformer模型是由谷歌公司提出的一种基于自注意力机制的神经网络模型&#xff0c;用于处理序列数据。相比于传统的循环神经网络模型&#xff0c;Transformer模型具有更好的并行性能和更短的训练时间&#xff0c;因此在自然语言处理领域…

设计模式—工厂模式

文章目录 工厂模式1、没有使用工厂2、简单工厂模式3、工厂方法模式4、抽象工厂模式5、工厂模式小结 工厂模式 1、没有使用工厂 需求 看一个披萨的项目&#xff1a;要便于披萨种类的扩展&#xff0c;要便于维护 披萨的种类很多(比如 GreekPizz、CheesePizz 等)披萨的制作有 pr…

谷粒商城实战笔记-252~254-商城业务-消息队列-Exchange-三种type的使用

文章目录 一&#xff0c;252-商城业务-消息队列-Direct-Exchange1&#xff0c;创建4个队列2&#xff0c;exchange绑定queue3&#xff0c;发送消息 二&#xff0c;253-商城业务-消息队列-Fanout-Exchange1&#xff0c;创建一个type为fanout的exchange2&#xff0c;给这个exchang…

通过C# 读取PDF页面大小、方向、旋转角度

在处理PDF文件时&#xff0c;了解页面的大小、方向和旋转角度等信息对于PDF的显示、打印和布局设计至关重要。本文将介绍如何使用免费.NET 库通过C#来读取PDF页面的这些属性。 文章目录 C# 读取PDF页面大小&#xff08;宽度、高度&#xff09;C# 判断PDF页面方向C# 检测PDF页面…

VMWare中添加Ubuntu20.04.06镜像

一、下载Ubuntu镜像 Ubuntu20.04&#xff1a; 官方下载地址https://releases.ubuntu.com/20.04.6/ 进入官网 点击下图红框位置&#xff0c;下载镜像镜像名为ubuntu-20.04.6-desktop-amd64.iso 也可点击下面链接直接下载&#xff1a;https://releases.ubuntu.com/20.04.6/ubu…

安科瑞ACREL-7000能源管控平台在综合能耗监测系统在大型园区的应用

摘要&#xff1a;大型综合园区已经成为多种能源消耗的重要区域&#xff0c;为了探索适用于大型综合园区的综合能耗监测系统&#xff0c;建立了综合能耗监测系统整体框架&#xff0c;提出了综合能耗网络、能耗关系集合、能耗均衡度等概念&#xff0c;并以某大型综合园区为例对综…

【三维深度补全模型】PENet

【版权声明】本文为博主原创文章&#xff0c;未经博主允许严禁转载&#xff0c;我们会定期进行侵权检索。 参考书籍&#xff1a;《人工智能点云处理及深度学习算法》 本文为专栏《Python三维点云实战宝典》系列文章&#xff0c;专栏介绍地址“【python三维深度学习】python…