前言
今天分享的内容是 Huffman 编码的生成。
Huffman编码是一种用于数据压缩的编码算法。它的基本思想是将数据中的字符按照出现次数进行排序,然后将排序后的字符按照一定的规则进行编码。这样可以有效地减少数据的存储空间。
Huffman编码的优点在于,当字符出现的概率相等时,编码长度最短。这使得它在通信和数据存储领域得到了广泛的应用。例如,在互联网数据传输中,Huffman编码可以有效地减少数据量,从而提高传输速度。
下面的内容就是用 JS 代码实现,将一串字符串转成一段 Huffuman 编码
实现过程
Huffman编码的实现过程如下:
- 统计数据中每个字符出现的次数,并按照出现次数进行排序。
- 重复以下步骤,直到只剩下一个节点:
- 取出两个节点,创建一个新节点,新节点的值为其左右子节点的值的和。
- 将新节点插入到节点数组中。 c. 更新节点数组,将原来的两个节点替换为新节点。
- 编码规则:对于每个字符,根据其所在的节点路径,将编码设置为路径上所有节点的标签按顺序拼接而成的字符串。
第一步
统计数据中每个字符出现的次数,并按照出现次数进行排序
javascript
const data = "aabbcccdddfg";
class Node {
constructor(label, value) {
this.label = label || null;
this.value = value || null;
this.left = null;
this.right = null;
}
}
class Huffman {
constructor(data) {
this.data = data;
}
// 定义一个名为getPercentArray的函数,用于计算字符频率
getPercentArray = (data) => {
// 定义一个空对象res,用于存储字符频率
const res = {};
// 遍历data数组,统计每个字符出现的频率
for (let i = 0; i < data.length; i++) {
res[data[i]] ??= 0;
res[data[i]]++;
}
// 返回Object.entries(res)对象,其中包含了字符频率统计结果
return Object.entries(res)
.map((item) => new Node(item[0], item[1]))
.sort((a, b) => a.value - b.value);
};
}
上面定义了一个名为Huffman的类,其中有 getPercentArray 方法:接受一个字符串作为参数,并返回一个对象,该对象包含每个字符及其出现次数
getPercentArray 中将每个字符作为 key,出现的次数作为 value,是变成中统计次数时常用的技巧。统计完次数之后,就将 res 转成 node 的数组,并对数组按照出现频率进行排序
最后返回的对象,在控制台输出是这个样子
powershell
[
Node { label: 'f', value: 1, left: null, right: null },
Node { label: 'g', value: 1, left: null, right: null },
Node { label: 'a', value: 2, left: null, right: null },
Node { label: 'b', value: 2, left: null, right: null },
Node { label: 'c', value: 3, left: null, right: null },
Node { label: 'd', value: 3, left: null, right: null }
]
符合预期
第二步
重复以下步骤,直到只剩下一个节点:
- 取出两个节点,创建一个新节点,新节点的值为其左右子节点的值的和。
- 将新节点插入到节点数组中。 c. 更新节点数组,将原来的两个节点替换为新节点。
javascript
class Huffman{
// 省略其他代码
/**
*
* @param {number[]} percentArray
*/
generateTree(percentArray) {
// 当percentArray不为空时,继续循环
while (percentArray.length > 0) {
// 创建一个新节点,作为树的根节点
const root = new Node();
// 从percentArray中取出两个节点,作为根节点的左右子节点
const node1 = percentArray.shift();
const node2 = percentArray.shift();
// 将根节点的左右子节点设置为node1和node2
root.left = node1;
root.right = node2;
// 计算根节点的值,为左右子节点的值的和
root.value = node1.value + node2.value;
// 如果percentArray中还有节点,则将新节点插入到数组中
if (percentArray.length > 0) {
this.insertToArray(percentArray, root);
} else {
// 返回根节点
return root;
}
}
}
getValue(node) {
return node.value;
}
insertToArray(array, node) {
for (let i = 0; i < array.length; i++) {
if (this.getValue(node) < this.getValue(array[i])) {
array.splice(i, 0, node);
return;
}
}
array.push(node);
}
}
generateTree 函数就是不断循环 percentArray,以此构建 huffman tree 的。
循环过程中创建一个名为root的新节点,并将其作为当前树的根节点。然后从percentArray中取出两个节点,作为根节点的左右子节点。将根节点的左右子节点设置为node1和node2。并且计算根节点的值,为左右子节点的值的和。
最后,如果percentArray中还有节点,调用insertToArray函数将新节点插入到数组中。(插入之后依旧需要保证 percentArrayy 有序) 如果percentArray中没有节点了,说明生成完毕,返回根节点。
第三步
对于每个字符,根据其所在的节点路径,将编码设置为路径上所有节点的标签按顺序拼接而成的字符串。简单来说,就是用字符在树上的路径作为字符的编码
javascript
class Huffman {
// 省略其他代码
getHuffmanCode() {
// 调用getPercentArray函数获取字符频率统计结果
const percentArray = this.getPercentArray(this.data);
// 调用generateTree函数生成Huffman编码树
const tree = this.generateTree(percentArray);
// 定义一个空对象code,用于存储编码结果
const code = {};
// 调用_getHuffmanCode函数获取编码结果
this._getHuffmanCode(tree, [], code);
// 返回编码结果
return code;
}
// 定义一个名为_getHuffmanCode的私有函数,用于递归地获取编码结果
_getHuffmanCode(tree, deep, res) {
// 如果当前节点为空,则返回
if (!tree) return;
// 调用_getHuffmanCode函数获取左子树的编码结果
this._getHuffmanCode(tree.left, [...deep, 0], res);
// 如果当前节点的左子节点为空且当前节点没有右子节点,则将当前节点的编码结果设置为deep的join("")
if (!tree.left && !tree.right) {
res[tree.label] = deep.join("");
}
// 调用_getHuffmanCode函数获取右子树的编码结果
this._getHuffmanCode(tree.right, [...deep, 1], res);
}
}
_getHuffmanCode 获取对应编码的过程,采用了递归遍历二叉树的思想。往左节点走,编码为 1,往右节点走,编码为 0.如果当前是叶子节点,那么就将 deep 中的路径记录作为编码保存下来。
getHuffmanCode 中会一一调用之前两步用到的方法,且最后返回生成好的 code
执行代码,将 code 打印出来吧:
javascript
const huffman = new Huffman(data);
const code = huffman.getHuffmanCode();
console.log(code);
// { f: '000', g: '001', c: '01', d: '10', a: '110', b: '111' }
最后一步
获得了字符对应的 huffman 编码之后,还需要将原本的字符串都替换为 huffman 编码
javascript
class Huffman {
// 省略其他代码
transform(code) {
const strArray = this.data.split("");
return strArray
.map((item) => {
return code[item];
})
.join("");
}
}
先将 data 转成数组,然后再一一替换。
执行代码是这样的:
javascript
const newData = huffman.transform(code);
console.log(newData);
// 110110111111010101101010000001
好了,字符串转成 huffman 编码终于完成了。
转成了这种二进制的编码之后,怎么再变回来呢,这就是明天的内容了。明天会用 huffman 的思想来做一个简单压缩和解压缩的功能,敬请期待
总结:
这篇文章介绍了Huffman编码的生成过程以及如何将一段字符串转换为Huffman编码。Huffman编码是一种用于数据压缩的编码算法,其优点在于当字符出现的概率相等时,编码长度最短。文章通过一个简单的例子,使用JavaScript代码实现了一个Huffman编码的生成器,并展示了如何将一段字符串转换为Huffman编码。最后,文章提到明天将会介绍如何使用Huffman编码进行压缩和解压缩。
什么问题可以评论区留言哦。我每天都会分享一篇算法小练习,喜欢就点赞+关注吧