首页 > 编程语言 >哈夫曼树和哈夫曼编码详解(包含Java代码实现)

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

时间:2024-08-24 10:26:19浏览次数:12  
标签:编码 Java 哈夫曼 int 详解 htNodes weights 节点

目录

什么是哈夫曼树?

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

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

代码实现

哈夫曼树的结构

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

标签:编码,Java,哈夫曼,int,详解,htNodes,weights,节点
From: https://blog.csdn.net/dawn191228/article/details/141469183

相关文章

  • 在Java中常见的池化技术
    什么是池化技术池化技术的原理可以用一个生活中的比喻来理解。 想象有一个图书馆,里面有很多人需要借书和还书。如果没有任何管理措施,每次有人借书时,图书馆管理员都要去仓库找一本新的书拿出来给读者,等读者还书时,管理员又要把书放回仓库。这样的过程非常耗时耗力,而且仓库里......
  • 基于Java+Vue的采购管理系统:提高决策效率(项目代码)
        前言:采购管理系统是一个综合性的管理平台,旨在提高采购过程的效率、透明度,并优化供应商管理。以下是对各个模块的详细解释:一、供应商准入供应商注册:供应商通过在线平台进行注册,填写基本信息和资质文件。资质审核:系统对供应商提交的资质文件进行自动或人工审核,确保......
  • 009java jsp SSM springboot月度员工绩效考核管理系统绩效指标管理(源码+文档+PPT+任务
     项目技术:Springboot+Maven+Vue等等组成,B/S模式+Maven管理等等。环境需要1.运行环境:最好是javajdk1.8,我们在这个平台上运行的。其他版本理论上也可以。2.IDE环境:IDEA,Eclipse,Myeclipse都可以。推荐IDEA;3.tomcat环境:Tomcat7.x,8.x,9.x版本均可4.硬件环境:window......
  • 015java jsp SSM springboot在线视频课程教育学习平台系统(源码+文档+PPT+开题+运行视
     项目技术:Springboot+Maven+Vue等等组成,B/S模式+Maven管理等等。环境需要1.运行环境:最好是javajdk1.8,我们在这个平台上运行的。其他版本理论上也可以。2.IDE环境:IDEA,Eclipse,Myeclipse都可以。推荐IDEA;3.tomcat环境:Tomcat7.x,8.x,9.x版本均可4.硬件环境:window......
  • Java 12 新特性—Switch 表达式
    作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬学习必须往深处挖,挖的越深,基础越扎实!阶段1、深入多线程阶段2、深入多线程设计模式阶段3、深入juc源码解析阶段4、深入jdk其余源码解析......
  • Java 12 新特性—新增 String API
    作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬学习必须往深处挖,挖的越深,基础越扎实!阶段1、深入多线程阶段2、深入多线程设计模式阶段3、深入juc源码解析阶段4、深入jdk其余源码解析......
  • 基于java教师课堂教学质量评价系统设计与实现
    课题背景高等学校的根本任务是培养人才,课堂教学是高校完成人才培养的重要环节,因此,教师教学质量的高低对学生掌握和运用知识的程度有着密不可分的作用,为了保证教师的教学质量,教学评价成为了各高校衡量教师教学质量的重要方式之一。目前,教师“听课”和学生“学期末教师课程评......
  • Java实现MQTT通信(发布订阅消息)
    文章目录前言一、相关pom依赖二、相关代码1.MQTT工具类2.MQTT回调函数3.订阅消息4.发布消息三、安装mosquitto1.mosquitto简介2.下载四、安装MQTT.fx1.MQTT.fx简介2.下载3.使用五、java订阅消息六、java发布消息前言MQTT是一种轻量级的物联网通信协议,基于客户端-......
  • java计算机毕业设计智慧社区养老服务系统(开题+程序+论文)
    本系统(程序+源码)带文档lw万字以上 文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容研究背景随着人口老龄化趋势的加剧,传统的家庭养老模式面临前所未有的挑战。智慧社区养老服务系统的应运而生,正是响应了这一社会变迁的迫切需求。在快节奏的现......
  • java计算机毕业设计校园摄影爱好者交流网站设计(开题+程序+论文)
    本系统(程序+源码)带文档lw万字以上 文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容研究背景随着数字技术的飞速发展,摄影艺术在校园内日益普及,成为学生们记录生活、表达情感、探索美的重要方式。然而,尽管校园内不乏才华横溢的摄影爱好者,但他们......