java哈夫曼树的应用_【java算法】赫夫曼树(Huffman)的构建和应用(编码、译码)...

本文详细介绍了赫夫曼树的概念、构建方法以及如何利用Java实现赫夫曼编码和解码的过程。通过构建赫夫曼树,可以优化编码,减少不必要的空间浪费,同时避免编码时的二义性,实现正确译码。
摘要由CSDN通过智能技术生成

赫夫曼树的概念

要了解赫夫曼树,我们要首先从扩充二叉树说起

二叉树结点的度

结点的度指的是二叉树结点的分支数目, 如果某个结点没有孩子结点,即没有分支,那么它的度是0;如果有一个孩子结点, 那么它的度数是1;如果既有左孩子也有右孩子, 那么这个结点的度是2.

扩充二叉树

对于一颗已有的二叉树, 如果我们为它添加一系列新结点, 使得它原有的所有结点的度都为2,那么我们就得到了一颗扩充二叉树, 如下图所示:

8ab3b51bf80a

在这里插入图片描述

其中原有的结点叫做内结点(非叶子结点), 新增的结点叫做外结点(叶子结点)

我们可以得出: 外结点数 = 内结点数 + 1

并进一步得出: 总结点数 = 2 × 外结点数 -1

扩充二叉树,构成了赫夫曼树的基本形态,而上面的公式,也是我们构建赫夫曼树的依据之一

赫夫曼树的外结点和内结点

赫夫曼树的外结点和内结点的性质区别:外节点是携带了关键数据的结点, 而内部结点没有携带这种数据, 只作为导向最终的外结点所走的路径而使用

正因如此,我们的关注点最后是落在赫夫曼树的外结点上, 而不是内结点。

带权路径长度WPL

让我们思考一下: 在一颗在外结点上存储了数据的扩充二叉树中进行查找时,数据结点怎么分布才能尽可能减少查找的开销呢? 这里我们再加上一个前提:不同的数据结点搜索的频率(或概率)是不一致的。

显然, 我们大致的思路是: 如果一个数据结点搜索频率越高,就让它分布在离根结点越近的地方,也即从根结点走到该结点经过的路径长度越短。 这样就能从整体上优化整颗树的性能。

频率是个细化的量,这里我们用一个更加标准的一个词描述它——“权值”。

综上, 我们为扩充二叉树的外结点(叶子结点)定义两条属性: 权值(w)和路径长度(l)。同时规定带权路径长度(WPL)为扩充二叉树的外结点的权值和路径长度乘积之和:

8ab3b51bf80a

在这里插入图片描述

(注意只是外结点!)

赫夫曼树(最优二叉树)

由n个权值构造一颗有n个叶子结点的二叉树, 则其中带权路径长度WPL最小的二叉树, 就是赫夫曼树,或者叫做最优二叉树。

例如下图中对a, b, c

8ab3b51bf80a

在这里插入图片描述

对a: WPL = 7×2 + 5×2 + 2×2 + 4×2 = 36;

对b: WPL = 7×3 + 5×3 + 2×1 + 4×2 = 46;

对c: WPL = 7×1 + 5×2 + 2×3 + 4×3 = 35;

c中WPL最小, 可以验证, 它就是赫夫曼树, 而a和b都不是赫夫曼树

对于同一组权值的叶结点, 构成的赫夫曼树可以有多种形态, 但是最小WPL值是唯一的。

8ab3b51bf80a

在这里插入图片描述

赫夫曼树的构建

构建过程分四步:

根据给定的n个权值{w1, w2, w3 ... wn }构成n棵二叉树的集合, 每棵二叉树都只包含一个结点

在上面的二叉树中选出两颗根结点权值最小的树, 同时另外取一个新的结点作为这两颗树的根结点, 设新节点的权值为两颗权值最小的树的权值和, 将得到的这颗树也加入到树的集合中

在2操作后, 从集合中删除权值最小的那两颗树

重复2和3,直到集合中的树只剩下一棵为止, 剩下的这颗树就是我们要求得的赫夫曼树。

如下图所示:

8ab3b51bf80a

在这里插入图片描述

(注意a和b的分界线在4和7中间,图中画的不是很清晰)

我们上面提到过WPL相同的情况下, 赫夫曼树不止一种,在我们介绍的算法中,人为要求某个内结点的左儿子的权值要比右儿子大, 这样一来, 就将我们算法中的赫夫曼树变为唯一一种了。

构建赫夫曼树的的方法有多种,但基于实际应用的考虑(赫夫曼编码和译码), 下面我给出基于数组存储实现的赫夫曼树:

Node类的设计

我们首先需要一个编写一个结点类, 结点类里有5种实例变量: weight表示权值, data表示外结点存储的字符,data属性在下面的编码/解码中会用到。 而同样因为赫夫曼编码,解码的需求,这里我们使用三叉链实现二叉树,,即在left和right属性的基础上,为结点增加了parent属性,目的是能够从叶子结点上溯到根结点,从而实现赫夫曼编码。

/**

* @Author: HuWan Peng

* @Date Created in 23:21 2018/1/14

*/

public class Node {

char data; // 数据

int weight; // 权值

int left, right, parent; // 三条链接

public Node (char data, int weight) {

this.data = data;

this.weight = weight;

}

public Node (int weight) {

this.weight = weight;

}

}

buildTree方法的设计

输入参数和返回值

输入参数: 一个由外结点对象组成的Node数组, 假设其为nodes

返回值: 一个由内、外结点共同组成,且建立了链接关系的Node数组, 假设其为HT(HuffmanTree)

具体操作

首先要做的事情是: 获取输入的nodes数组的长度 n , 创建一个长度为 2n - 1的数组——HT,在数组HT中, 前n个元素用来存放外结点, 后n个元素用来存放内结点, 如下图所示:

图A

8ab3b51bf80a

在这里插入图片描述

图B

接下来要做的是:

8ab3b51bf80a

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值