1.哈夫曼树

1.1哈夫曼树简介

哈夫曼树:给定N个权值作为N个叶子节点,构造一棵二叉树,若该树的带权路径长度达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树(Huffman Tree)。哈夫曼树是带权路径长度最短的树,权值较大的结点离根较近。

以下图为例,先说明几个概念:

​权:

赋予某个实体的一个量,是对实体的某个或某些属性的数值描述。在数据结构中,实体有节点(元素)和边(关系)两大类,所以对应有节点权和边权。

路径:

在一棵树中,一个结点到另一个结点之间的通路,称为路径。上图,从根结点到结点 h之间的通路就是一条路径。

节点路径长度:

在一棵树中,从一个节点到另一个节点所经过的“边”的数量,被我们称为两个结点之间的路径长度;或者说路径上的分支数目称作路径长度。上图中从根结点到结点 h 的路径长度为3。

树的路径长度:

树的路径长度就是从树根到每一结点的路径长度之和。上图的树的路径长度为1+2+3+3+2+3+1+2+2+3=22

节点的带权路径长度:

树的每一个结点,都可以拥有自己的“权重”(Weight),权重在不同的算法当中可以起到不同的作用。结点的带权路径长度,是指树的根结点到该结点的路径长度,和该结点权重的乘积。上图中h节点带权路径长度为3 * 3=9

树的带权路径长度:

树中所有叶子结点的带权路径长度之和。也被简称为WPL。上图的树的带权路径长度为3 * 2+3 * 3+3 * 4+2 * 1+3 * 5=44

1.2

哈夫曼算法

哈夫曼树的构建方法被称为哈夫曼算法,其构建步骤为:根据给定的n个权值{w1,w2,…,wn}构成n棵二叉树的集合F={T1,T2,…,Tn},其中每棵二叉树Ti中只有一个带权为wi根结点,其左右子树均为空。

在F中选取两棵根结点的权值最小的树作为左右子树构造一棵新的二叉树,且置新的二叉树的根结点的权值为其左右子树上根结点的权值之和。

在F中删除这两棵树,同时将新得到的二叉树加入F中。

重复2和3步骤,直到F只含一棵树为止。这棵树便是哈夫曼树。

注意:叶子上的权值均相同时,完全二叉树一定是最优二叉树,否则完全二叉树不一定是最优二叉树。

最优二叉树中,权越大的叶子离根越近。

最优二叉树的WPL最小,但是形态不唯一。

1.3

案例

有五个带权节点{ A5,B15,C40,D30,E10}。要求构建哈夫曼树。

(1)先把有权值的叶子结点按照从小到大的顺序排列成一个有序序列,即:A5,E10,B15,D30,C40。

(2)取头两个最小权值的结点作为一个新节点N1的两个子结点,取相对较小的是左孩子(实际上也可以不遵守,因为哈夫曼树是有多种形状的,但是WPL都是最小的),这里就是A为N1的左孩子,E为N1的右孩子,如下图,新结点的权值为两个叶子权值的和5+10=15。

​(3)将N1替换A与E,插入有序序列中,保持从小到大排列。即:N115,B15,D30,C40。

(4)重复步骤2。将N1与B作为一个新节点N2的两个子结点。如下图,N2的权值=15+15=30。

​(5)将N2替换N1与B,插入有序序列中,保持从小到大排列。即:N230,D30,C40。

(6)重复步骤2。将N2与D作为一个新节点N3的两个子结点。如下图,N3的权值=30+30=60。

​(7)将N3替换N2与D,插入有序序列中,保持从小到大排列。即:C40,N360。

(8)重复步骤2。将C与N3作为一个新节点T的两个子结点,如下图,由于T即是根结点,完成哈夫曼树的构造。

此时的上图二叉树的带权路径长度WPL=40×1+30×2+15×3+10×4+5×4=205。这就是最短带权路径长度。

​ 根据最优二叉树形态不唯一的性质和上面的一种形状,我们还可以写出下面形状的最优二叉树:

​2.哈夫曼树的实现

现提供哈夫曼树的Java实现,下面的类提供了两种构建方法,区别主要在一个排序使用Arrays.sort排序(注意由于是对象排序,它并非双轴快排),另一个使用了最小堆排序。它们的时间效率是不一致的。实际使用时选取一种即可。

/**

* 哈夫曼树简单实现

*/

public class HuffmanTree {

/**

* 外部保存根节点的引用

*/

private BinaryTreeNode root;

/**

* 内部节点

*

* @param 节点数据类型

*/

public static class BinaryTreeNode {

//节点数据

E data;

//节点权重

String weight;

//左子结点

BinaryTreeNode left;

//右子节点

BinaryTreeNode right;

public BinaryTreeNode(E data, String weight) {

this.data = data;

this.weight = weight;

}

@Override

public String toString() {

return "Node{" +

"data=" + data +

", weight='" + weight + '\'' +

'}';

}

}

/**

* 根据指定的普通node节点集合构建哈夫曼树

*

* @param binaryTreeNodes node节点集合,采用普通list集合

* @param 节点数据类型

* @return 哈夫曼树

*/

public static HuffmanTree build(List> binaryTreeNodes) {

//比较器

Comparator> comparator = (o1, o2) -> new BigDecimal(o1.weight).subtract(new BigDecimal(o2.weight)).intValue();

while (binaryTreeNodes.size() > 1) {

//手动对集合的节点按照权值大小从大到小进行排序

binaryTreeNodes.sort(comparator);

//移除并获取权值最小的两个节点

BinaryTreeNode left = binaryTreeNodes.remove(0);

BinaryTreeNode right = binaryTreeNodes.remove(0);

//生成新节点,新节点的权值为两个子节点的权值之和

BinaryTreeNode parent = new BinaryTreeNode<>(null, new BigDecimal(left.weight).add(new BigDecimal(right.weight)).toString());

//让新节点作为两个权值最小节点的父节点

parent.left = left;

parent.right = right;

//将新节点加入到集合中

binaryTreeNodes.add(parent);

}

//采用循环不断地执行上面的步骤,直到list集合中只剩下一个节点,最后剩下的这个节点就是哈夫曼树的根节点

HuffmanTree huffmanTree = new HuffmanTree<>();

huffmanTree.root = binaryTreeNodes.remove(0);

return huffmanTree;

}

/**

* 根据指定的最小堆构建哈夫曼树

*

* @param binaryTreeNodes node节点集合,采用最小堆

* @param 节点数据类型

* @return 哈夫曼树

*/

public static HuffmanTree build(PriorityQueue> binaryTreeNodes) {

while (binaryTreeNodes.size() > 1) {

//移除并获取权值最小的两个节点

BinaryTreeNode left = binaryTreeNodes.poll();

BinaryTreeNode right = binaryTreeNodes.poll();

//生成新节点,新节点的权值为两个子节点的权值之和

BinaryTreeNode parent = new BinaryTreeNode<>(null, new BigDecimal(left.weight).add(new BigDecimal(right.weight)).toString());

//让新节点作为两个权值最小节点的父节点

parent.left = left;

parent.right = right;

//将新节点加入到最小堆中,自动排序

binaryTreeNodes.add(parent);

}

//采用循环不断地执行上面的步骤,直到list集合中只剩下一个节点,最后剩下的这个节点就是哈夫曼树的根节点

HuffmanTree huffmanTree = new HuffmanTree<>();

huffmanTree.root = binaryTreeNodes.poll();

return huffmanTree;

}

/**

* 获取根节点

*

* @return 根节点或者null-表示空树

*/

public BinaryTreeNode getRoot() {

return root;

}

}

2.1 测试

public class HuffmanTreeTest {

/*采用用普通集合和最小堆都行,最大的区别是它们的采用不同的排序算法,效率是不一致的*/

/**

* 采用普通list作为临时存储节点数据的集合,因此我们需要手动排序

*/

@Test

public void test1() {

//采用普通list作为临时存储节点数据的集合,因此我们需要手动排序

List> binaryTreeNodes = new ArrayList<>();

// A5,B15,C40,D30,E10

binaryTreeNodes.add(new BinaryTreeNode<>("A", "5"));

binaryTreeNodes.add(new BinaryTreeNode<>("B", "15"));

binaryTreeNodes.add(new BinaryTreeNode<>("C", "40"));

binaryTreeNodes.add(new BinaryTreeNode<>("D", "30"));

binaryTreeNodes.add(new BinaryTreeNode<>("E", "10"));

HuffmanTree huffmanTree = HuffmanTree.build(binaryTreeNodes);

BinaryTreeNode root = huffmanTree.getRoot();

System.out.println(root);

}

/**

* 采用 最小堆--priorityQueue 作为临时存储节点数据的集合,priorityQueue的性质就是对集合的元素进行自动排序,因此我们不必手动排序

*/

@Test

public void test2() {

//采用最小堆--priorityQueue.作为临时存储节点数据的集合,priorityQueue的性质就是对集合的元素进行自动排序,我们只需要指定排序规则

PriorityQueue> priorityQueueBinaryTreeNodes = new PriorityQueue<>((o1, o2) -> new BigDecimal(o1.weight).subtract(new BigDecimal(o2.weight)).intValue());

priorityQueueBinaryTreeNodes.add(new BinaryTreeNode<>("A", "5"));

priorityQueueBinaryTreeNodes.add(new BinaryTreeNode<>("B", "15"));

priorityQueueBinaryTreeNodes.add(new BinaryTreeNode<>("C", "40"));

priorityQueueBinaryTreeNodes.add(new BinaryTreeNode<>("D", "30"));

priorityQueueBinaryTreeNodes.add(new BinaryTreeNode<>("E", "10"));

HuffmanTree huffmanTree = HuffmanTree.build(priorityQueueBinaryTreeNodes);

BinaryTreeNode root = huffmanTree.getRoot();

System.out.println(root);

}

}

3.哈夫曼编码

哈夫曼树的出现主要是为了哈夫曼编码服务的,哈夫曼编码有着非常广泛的应用,哈夫曼编码常被使用在数据的压缩和解压缩技术之中。

哈夫曼编码的基本思想是以字符的使用频率作为权,构造一棵哈夫曼树,然后利用哈夫曼树对字符进行编码。这棵哈夫曼树,是将所要编码的字符作为叶子结点,该字符在文件中的使用频率作为叶子结点的权,以自底向上的方式,通过n-1次合并运算后构造出一棵树,权值越大的叶子离根越近。

比如我们有一段文字内容为“BADCADFEED”要网络传输给别人,显然用二进制的数字(0和1)来表示是很自然的想法。我们现在这段文字只有六个字母ABCDEF,那么我们可以用相应的二进制数据表示。

这样真正传输的数据就是编码后的“001000011010000011101100100011”,对方接收时可以按照3位一分来译码。如果一篇文章很长,这样的二进制串也将非常的可怕。而且事实上,不管是英文、中文或是其他语言,字母或汉字的出现频率是不相同的,比如英语中的几个元音字母“ae i o u”,中文中的“的 了 有 在”等汉字都是频率极高。

假设六个字母的频率为A 27,B 8,C 15,D15,E 30,F 5,合起来正好是100%。那就意味着,我们完全可以重新按照哈夫曼树来规划它们。下左图为构造哈夫曼树的过程的权值显示。右图为将权值左分支改为0,右分支改为1后的哈夫曼树。

​此时,我们对这六个字母用其从树根到叶子所经过路径的0或1来编码,可以得到下表所示这样的定义。

我们将文字内容为“BADCADFEED”再次编码,对比可以看到结果串变小了。原编码二进制串:001000011010000011101100100011(共30个字符)

新编码二进制串:1001010010101001000111100(共25个字符)

也就是说,我们的数据被压缩了,节约了大约17%的存储或传输成本。随着字符的增加和多字符权重的不同,这种压缩会更加显出其优势。

当我们接收到1001010010101001000111100这样压缩过的新编码时,我们应该如何把它解码出来呢?

编码中非0即1,哈夫曼编码是可变字长编码(VLC)的一种。但是如果长短不等的话其实是很容易混淆的,所以若要设计长短不等的编码,则必须是任一字符的编码都不是另一个字符的编码的前缀,这种编码又称做前缀编码。

按照上表设计的编码就不存在容易与1001、1000混淆的“10”和“100”编码。实际上哈夫曼编码就是一种前缀编码。

但是在解码时,还是要用到哈夫曼树,即发送方和接收方必须要约定好同样的哈夫曼编码规则。

当我们接收到1001010010101001000111100时,由约定好的哈夫曼树可知,1001得到第一个字母是B,接下来01意味着第二个字符是A,如下图所示,其余的也相应的可以得到,从而成功解码。

​一般地,设需要编码的字符集为{d1,d2,…,dn},各个字符在电文中出现的次数或频率集合为{w1,w2,…,wn},以d1,d2,…,dn作为叶子结点,以w1,w2,…,wn作为相应叶子结点的权值来构造一棵哈夫曼树。规定哈夫曼树的左分支代表0,右分支代表1,则从根结点到叶子结点所经过的路径分支组成的0和1的序列便为该结点对应字符的编码,这就是哈夫曼编码。

哈夫曼java_哈夫曼树和哈夫曼编码介绍以及Java实现案例相关推荐

  1. 赫夫曼树(哈夫曼树)

    赫夫曼树->赫夫曼编码 在数据膨胀.信息爆炸的今天,数据压缩的意义不言而喻. 一个字节8位 赫夫曼编码压缩-无损压缩 可以看成成绩的排布.成绩是70-90之间占有70%,所以以下两个数据结构优化 ...

  2. 蓝桥哈夫曼树C语言,实验四 哈夫曼树及哈夫曼编码

    实验目的## 掌握哈夫曼树的概念.哈夫曼编码及其应用. 掌握生成哈夫曼树的算法. 会用哈夫曼树对传输报文进行编码. 掌握二叉树的二叉链表存储方式及相应操作的实现. ##实验内容## 用哈夫曼编码进行通 ...

  3. python哈夫曼树_python霍夫曼树

    class Node(): data=0 left=None right=None father=None def __init__(self,data,left,right): self.data= ...

  4. 一文看懂哈夫曼树与哈夫曼编码

    转自:http://www.cnblogs.com/Jezze/archive/2011/12/23/2299884.html 在一般的数据结构的书中,树的那章后面,著者一般都会介绍一下哈夫曼(HUF ...

  5. 树:哈夫曼树和哈夫曼编码的详细介绍以及代码实现

    闲扯前言 哈夫曼编码的代码实现对于初学数据结构的同学可能会有些困难,没有必要灰心,其实没啥,学习就犹如攀登一座又一座的山峰,每当我们攻克一个难点后,回首来看,也不过如此嘛.我们要做的就是不断的去攀越学 ...

  6. 听说你还不懂哈夫曼树和哈夫曼编码

    基本概念 哈夫曼(Huffman)树又称最优树,是一类带权路径长度最短的树,在实际中有广泛的用途. 基本概念 路径:从树中一个结点到另一个结点之间的分支构成这两个结点之间的路径. 路径长度:路径上的分 ...

  7. 数据结构图文解析之:哈夫曼树与哈夫曼编码详解及C++模板实现

    0. 数据结构图文解析系列 数据结构系列文章 数据结构图文解析之:数组.单链表.双链表介绍及C++模板实现 数据结构图文解析之:栈的简介及C++模板实现 数据结构图文解析之:队列详解与C++模板实现 ...

  8. 【Java数据结构与算法】第十二章 哈夫曼树和哈夫曼编码

    第十二章 哈夫曼树和哈夫曼编码 文章目录 第十二章 哈夫曼树和哈夫曼编码 一.哈夫曼树 1.基本术语 2.构建思路 3.代码实现 三.哈夫曼编码 1.引入 2.介绍 3.代码实现哈夫曼编码综合案例 一 ...

  9. 【数据结构】树与树的表示、二叉树存储结构及其遍历、二叉搜索树、平衡二叉树、堆、哈夫曼树与哈夫曼编码、集合及其运算

    1.树与树的表示 什么是树? 客观世界中许多事物存在层次关系 人类社会家谱 社会组织结构 图书信息管理 分层次组织在管理上具有更高的效率! 数据管理的基本操作之一:查找(根据某个给定关键字K,从集合R ...

最新文章

  1. 区块链行业报告|从交易流程到Token经济的全方位解析
  2. MYSQL韩文显示正常一法
  3. SDN的实现思路—Vecloud微云
  4. 干货|一文读懂中国7大支付体系(附27页流程图)
  5. python获取动态数据采集仪代理_Python3爬虫技术文档(3)——动态页面数据采集,三,获取...
  6. 辍学的名人_辍学效果如此出色的5个观点
  7. 贝塞尔修正_贝塞尔修正背后的推理:n-1
  8. 云服务器 性能监控软件,云监控 - 云应用监控 - ManageEngine Applications Manager
  9. 深度学习之GAN对抗神经网络
  10. 模糊规则优化matlab,遗传算法优化模糊pid控制规则
  11. PGIS平台部署中的问题及解决方案
  12. 从大数据的角度看 房价一定会下跌
  13. 抗击疫情 融云在行动
  14. At least one JAR was scanned for TLDs yet contained no TLDs. Enable debug logging for this logger fo
  15. 超人视觉怎么样/机器视觉培训适合报培训班吗
  16. 超信Linux版(超信 for Linux下载) v1.3.0官方版
  17. java毕业设计智能小区物业管理系统Mybatis+系统+数据库+调试部署
  18. 有感而发:中国十大最黑心的职业排行榜
  19. flink之SQL入门
  20. 并联串联混合的电压和电流_电流互感器知识:铭牌、接线图、重点问题详解

热门文章

  1. Hadoop2 7 1 集群部署及自动化脚本
  2. 【corpwechat-bot】一个好用的企业微信消息推送python接口库
  3. HTML5单选框与复选框
  4. 数据分析方法与思维:漏斗分析
  5. oracle 横向列变为纵向列
  6. 【计算机视觉40例】案例26:姿势识别
  7. [unity3d]recast navigation navmesh 导航网格 寻路算法 源码分析
  8. win7怎么设置开机不用密码登陆?
  9. CityEngine之cga语法------------NIL()(结束循环)
  10. SearchView搜索框的功能与用法