[toc]
# 几个概念
## 路径
在一棵树中,从一个结点到另一个结点所经过的所有结点,被我们称为两个结点之间的路径。
比如:A -> H 的路径:
![](https://img.kancloud.cn/1d/d4/1dd44197a23aa5aa130a0e573339e6eb_1236x1084.png)
## 路径长度
在一棵树中,从一个结点到另一个结点所经过的“边”的数量,被我们称为两个结点之间的路径长度。
比如:A -> H的路径长度为 3:
![](https://img.kancloud.cn/34/bc/34bc723d1197eb6f40b5d7ab31ab0819_1216x1076.png)
## 节点的带权路径长度
每一个节点都可以有一个权重(数字)。
带权路径长度:节点权重 * 节点到根节点的路径长度
比如:H 的带权路径长度 = 3 * 3 = 9
![](https://img.kancloud.cn/25/4d/254d6b2f2a066c713251a34f913be694_1248x1128.png)
## 树的带权路径长度
树的带权路径长度(也做WPL):所有 `叶子` 节点的带权路径长度之和。
比如:下面这棵树的 WPL = 3*3 + 6*3 + 1*2 + 4*2 + 8*2 = 53
![](https://img.kancloud.cn/57/dc/57dc02737778338ab788f7f00ff1a43d_1220x1148.png)
## 哈夫曼树
哈夫曼树(Huffman Tree):是在叶子结点和权重确定的情况下,带权路径长度最小的二叉树,也被称为最优二叉树。
比如下图左侧就比右侧 WPL 小。
![](https://img.kancloud.cn/92/ad/92ade9107dccfa537b6f5aac934bd33d_1210x620.png)
总结:核心特点:权重越大的节点,越靠近根节点。
面试官问:什么是最优二叉树?什么是哈夫曼树?
## 哈夫曼编码
哈夫曼编码可以用来进行 `数据压缩` 。
### 编码
比如要压缩:“acbcbacddaddaddccd”
1. 先统计文本中每个字符出现的频率
![](https://img.kancloud.cn/e3/ef/e3ef35efcf04208adb20ae55136fc516_502x506.png)
2. 频率当作权重构建哈夫曼树
![](https://img.kancloud.cn/de/30/de3015365e17db53e820f89cb9364ae1_640x904.png)
3. 根据哈夫曼树对字符串进行编码
编码规则:
1. 左子节点是0,右子节点是1
2. 从根节点到叶子结果
根据哈夫曼树可以将字符串编码为:
* a:111
* ac:1111 0
* acb:1111 0110
* acbc:1111 011010
* acbcb:1111 0110 1011 0…
说明:编码之后好像是变长了,但注意这里的存储 单位是不同的:
- 1 字节(Byte) = 6 位(bit)
- 一个字母 = 1 字节 = 8 位
- 一个汉字 = 2~4字节 = 16~32位
- 而1010这个哈夫曼编码,单位是位,一个1或0只带1 位。
所以综合:哈夫曼编码的压缩率在 20% ~ 90% 之间。
### 解码
编码过程就是:
1. 将哈夫曼编码到哈夫曼树上 “从根向下跑一遍”
2. 0 向左、 1 向右
3. 当遇到一个 “叶子” 节点时结束
4. 重复从根开始跑,重复 1~3
比如:1111 0110 1011 0... 到树上跑得到:acbcb
## 用途
1. 数据压缩
2. 加密、解密
# 构建哈夫曼树的流程
1. 将带权节点 `按升序` 放到数组中
![](https://img.kancloud.cn/12/8b/128b5929fcb6921078de7bf385f0a283_240x934.png)
2. 取出数组中最小的两个节点做为子节点构造一棵树,和为父节点
![](https://img.kancloud.cn/73/d8/73d8ff811a3ba7a711b98cc118321a2a_728x998.png)
3. 将父节点的值放回数组中并 `排序`
![](https://img.kancloud.cn/16/fc/16fce61c87f9ed15056c1bd92e3d6626_698x758.png)
4. 重复2~3步骤
拿出最小两个节点:
![](https://img.kancloud.cn/bd/c0/bdc0ec78d98a96918c1cfe61d4a84af2_800x790.png)
将的父节点放回并排序
![](https://img.kancloud.cn/fa/bb/fabbcc40b85f411f2c7469373179446d_802x724.png)
5. 重复2~3
![](https://img.kancloud.cn/19/9e/199ea6adfdb224a03b9021f49f4f76d0_832x810.png)
![](https://img.kancloud.cn/a1/22/a122afea34ac0cb71a3c004df3a51275_802x744.png)
6. 重复2~3
![](https://img.kancloud.cn/89/ca/89ca511e426fddb9138a888fb724d4ae_958x908.png)
![](https://img.kancloud.cn/da/28/da28935b7f8cff23b6356dbb61e84bc2_1002x938.png)
7. 重复2~3
![](https://img.kancloud.cn/bd/38/bd38c53f244326d64357c98d280b391e_1220x1138.png)
![](https://img.kancloud.cn/0b/1f/0b1f9c52ef4b2e234cbc8b1c9923800f_1200x1156.png)
最后数组中只剩下一个节点,说明构造结束!
# 代码实现 - 构造哈夫曼树
~~~
class Node {
constructor(data) {
this.data = data
this.left = null
this.right = null
}
}
class HuffmanTree {
constructor(arr) {
this.queue = arr
this.root = null
let first, second, parent
while(this.queue.length>1) {
// 排序
this.queue.sort((a,b)=>a-b)
first = this.queue.shift()
second = this.queue.shift()
parent = first + second
this.queue.unshift(parent)
parent = new Node(parent)
first = new Node(first)
second = new Node(second)
if(this.root === null) {
parent.left = first
parent.right = second
this.root = parent
} else {
if(this.root.data == first.data) {
parent.left = this.root
parent.right = second
this.root = parent
} else {
parent.left = first
parent.right = this.root
this.root = parent
}
}
}
}
}
~~~