系列文章目录
趣味算法(第二版)读书笔记: day1: 序章|学习的方法和目标. day2:算法之美|打开算法之门与算法复杂性 day3.算法之美|指数型函数对算法的影响实际应用 day4.数学之美|斐波那契数列与黄金分割 day5.算法基础|贪心算法基础 day6.算法基础||哈夫曼树 day7.算法基础||堆栈和队列
后续补充完善
提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
- 系列文章目录
- 课程导学
- 一、哈夫曼树和哈夫曼编码是什么?
- 二、哈夫曼树的应用
- 1.数据压缩
- 2.其他应用
- 2.1 平衡二叉树
- 2.2 红黑树
- 2.3 B-树
- 总结
课程导学
提示:对互联网贡献最大的贡献之一就是压缩算法,经历过拨号上网的应该都对ZIP不陌生,贪心算法中的哈夫曼树就是经典的应用,当时出现了很多算法很优秀的简约而不简单的程序,向经典致敬:
书中分了5个部分详细讲解了哈夫曼树的问题构解:
- 问题分析
- 算法设计
- 完美图解
- 算法详解
- 算法分析及优化 以下让我们来了解哈夫曼树及其应用:
一、哈夫曼树和哈夫曼编码是什么?
tips:包括软件在内计算机科学都是基于数学和电子学发展起来的,致敬哈夫曼、图灵等计算机和信息领域的先驱,我们都是站在巨人的肩膀上。
t
1951 年,正在麻省理工学院求学的哈夫曼需要完成一份学期报告,导师 Robert M. Fano 给他们的学期报告的题目是,寻找最有效的二进制编码。哈夫曼在研究过已有编码后发现,始终无法证明哪个编码是最有效的,因此他很快放弃了这些研究,而进行了新的探索,最后他终于突破了现有编码的算法,发现了基于有序频率二叉树编码,哈夫曼使用了一种自底向上的方法来构建二叉树,这一方法很快被证明是最有效的。 在数据结构理论中,哈夫曼树又称为最优树,相关的知识点还有哈弗曼编码等。
给定N个权值作为N个叶子结点,构造一棵二叉树,若该树的带权路径长度达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树(Huffman Tree)。哈夫曼树是带权路径长度最短的树,权值较大的结点离根较近。
(1)节点路径
按照规定,将树中一个节点到另一个节点所经历的分支,称为节点路径,比如上图中节点A到节点E的路径为ABE。
(2)路径长度
根据上述“节点路径”的定义,将路径上的分支总数称为路径长度,比如上图中节点A到节点E的路径长度为2。
(3)节点的带权路径长度
根据上述“节点路径”和“路径长度”的定义,将从根节点到某节点的路径长度和节点权值的乘积,称为节点的带权路径长度。比如上图中节点A到节点D的路径长度为2,权值为8,则带权路径长度为2x8=16。
(4)树的带权路径长度
根据上述“节点的带权路径长度”的定义,将树中所有叶子节点的带权路径长度,称为树的带权路径长度。比如上图中,三个叶子节点C、D、E,对应的路径长度分别为1、2、2,对应的权值分别为4、8、3,则树的带权路径长度为:
将上述概念形式化,可以使用下面的公式进行计算:
其中了表示路径长度,w表示权值,n表示叶子节点个数。
举个例子: 哈夫曼树能够根据节点的查找频率来构造更有效的搜索树,是 WPL 最小的树。
哈夫曼树的构造可以理解为将权值最小的两棵二叉树合并,这个树的权值等于 2 个子树的和。
关于如何选取两个权值最小的二叉树,可以使用最小堆实现,复杂度是 O(N log N)。
比如权值:{1,2,3,4,5},可以得出:
计算以下 WPL = 2 * 3 2 * 4 2 * 5 3 * 1 3 * 2 = 33
哈夫曼树的特点:
没有度为 1 的节点(即不存在只有一个子节点的节点) n 个叶子节点的哈夫曼树,总节点数为 2n-1 n0:叶节点总数 n1:只有一个子节点的节点总数 n2:有两个子节点的节点总数 那么 n2 = n0 - 1 由于没有度为 1 的节点,所以其总节点数为 n n - 1 = 2n-1 哈夫曼树任意非叶节点的左右子树交换后仍是哈夫曼树 对同一权值{W1,W2,W3,…,Wn},允许存在不同构造的两颗哈夫曼树
哈夫曼编码用于数据存储中做压缩,如下案例: 问题: 给定一段包含 50 个字符的字符串,由 {a,b,c,d,e,f}构成,且每个字符出现次数不同,会有如下几种存储方式。 分析 等长 ASCII 编码,存储长度为 50 * 8 = 400 位 等长 3 位编码,存储长度为 50 * 3 = 150 位 不等长编码,出现频率高的字符编码短些,出现频率低的字符编码长些。 第三种便可以使用哈夫曼树来实现,假如给定:
字符 | a | b | c | d | e | f |
---|---|---|---|---|---|---|
次数 | 18 | 4 | 16 | 1 | 1 | 10 |
构成哈夫曼树:
`
``
所以: a:0; b:1101; c:10; d:11000; e:11001; f:111 。
长度为: 1 * 18 4 * 4 16 * 2 1 * 5 1 * 5 10 * 3 = 106 字符。
伪代码实现:
代码语言:javascript复制Change_Array_To_Huffman(A[n]) {
BinaryTree *T[n];
BinaryTree *B;
代码语言:javascript复制 for(i : from 1 to n) {
T[i]->data = A[i];
T[i]->left = Null;
T[i]->right = Null;
}
for(i : from 1 to n) {
select k1 & k2 as non-empty minimum-data index;
B->data = T[k1]->data T[k2]->data;
B->left = T[k1];
B->right = T[k2];
T[k1] = B;
T[k2] = Null;
}
return B;
}
二、哈夫曼树的应用
1.数据压缩
哈夫曼压缩算法编码是无损压缩当中最好的方法。它使用预先二进制描述来替换每个符号,长度由特殊符号出现的频率决定。常见的符号需要很少的位来表示,而不常见的符号需要很多为来表示。 哈夫曼算法在改变任何符号二进制编码引起少量密集表现方面是最佳的。然而,它并不处理符号的顺序和重复或序号的序列。 简短的说,这个问题的解决方案是为了查找每个符号的通用程度,我们建立一个未压缩数据的柱状图;通过递归拆分这个柱状图为两部分来创建一个二叉树,每个递归的一半应该和另一半具有同样的权(权是∑NK =1符号数k, N是分之中符号的数量,符号数k是符号k出现的次数)
这棵树有两个目的: 1. 编码器使用这棵树来找到每个符号最优的表示方法 2. 解码器使用这棵树唯一的标识在压缩流中每个编码的开始和结束,其通过在读压缩数据位的时候自顶向底的遍历树,选择基于数据流中的每个独立位的分支,一旦一个到达叶子节点,解码器知道一个完整的编码已经读出来了。
哈夫曼编码器生成哈夫曼树 压缩后的数据流是24位(三个字节),原来是80位(10个字节)。当然,我应该存储哈夫曼树,这样解码器就能够解码出对应的压缩流了,这就使得该例子中的真正数据流比输入的流数据量大。这是相对较短的数据上的副作用。对于大数据量来说,上面的哈夫曼树就不占太多比例了。
最终的压缩数据流 解码的时候,从上到下遍历树,为压缩的流选择从左/右分支,每次碰到一个叶子节点的时候,就可以将对应的字节写到解压输出流中,然后再从根开始遍历。
缺点 1. 慢位流实现 2. 相当慢的解码(比编码慢) 3. 最大的树深度是32(编码器在任何超过32位大小的时候退出)。如果我不是搞错的话,这是不可能的,除非输出的数据大于232字节。 优点 1. 哈夫曼树以一个紧密的形式每个符号要求12位(对于8位的符号)的方式存储,这意味着最大的头为384。 2. 编码相当容易理解 哈夫曼编码在数据有噪音的情况(不是有规律的,例如RLE)下非常好,这中情况下大多数基于字典方式的编码器都有问题。 以上就是对哈夫曼压缩算法的简单介绍。
2.其他应用
2.1 平衡二叉树
用的最多的应该是平衡二叉树,有种特殊的平衡二叉树红黑树,查找、插入、删除的时间复杂度最坏为O(log n) 平衡二叉查找树:简称平衡二叉树。由前苏联的数学家 Adelse-Velskil 和 Landis 在 1962 年提出的高度平衡的二叉树,根据科学家的英文名也称为 AVL 树。它具有如下几个性质: 可以是空树。 假如不是空树,任何一个结点的左子树与右子树都是平衡二叉树,并且高度之差的绝对值不超过 1。
2.2 红黑树
Java集合中的TreeSet和TreeMap,C STL中的set、map,也就是经典的红黑树: 1.TreeMap存储K-V键值对,通过红黑树(R-B tree)实现; 2.TreeMap继承了NavigableMap接口,NavigableMap接口继承了SortedMap接口,可支持一系列的导航定位以及导航操作的方法,当然只是提供了接口,需要TreeMap自己去实现; 3.TreeMap实现了Cloneable接口,可被克隆,实现了Serializable接口,可序列化; 4.TreeMap因为是通过红黑树实现,红黑树结构天然支持排序,默认情况下通过Key值的自然顺序进行排序;
2.3 B-树
以及Linux虚拟内存的管理,都是通过红黑树去实现的。还有哈夫曼树编码方面的应用。B-Tree,B±Tree在文件系统中的应用。 B-树 B-tree,即B树,而不要读成B减树,它是一种多路搜索树(并不是二叉的):
1.定义任意非叶子结点最多只有M个儿子;且M>2;
2.根结点的儿子数为[2, M];
3.除根结点以外的非叶子结点的儿子数为[M/2, M];
4.每个结点存放至少M/2-1(取上整)和至多M-1个关键字;(至少2个关键字)
5.非叶子结点的关键字个数=指向儿子的指针个数-1;
6.非叶子结点的关键字:K[1], K[2], …, K[M-1];且K[i] < K[i 1];
7.非叶子结点的指针:P[1], P[2], …, P[M];其中P[1]指向关键字小于K[1]的子树,P[M]指向关键字大于K[M-1]的子树,其它P[i]指向关键字属于(K[i-1], K[i])的子树;
8.所有叶子结点位于同一层;
总结
对书中哈夫曼树的基本概念和应用做一个梳理和总结,书中最大的收获是结合视频看老师的推导过程,这个思路是单纯的阅读无法体验的,:
下一章:day7.算法基础||堆栈和队列