🥳前端算法面试之Huffman编码-每日一练

前言

今天分享的内容是 Huffman 编码的生成。

Huffman编码是一种用于数据压缩的编码算法。它的基本思想是将数据中的字符按照出现次数进行排序,然后将排序后的字符按照一定的规则进行编码。这样可以有效地减少数据的存储空间。

Huffman编码的优点在于,当字符出现的概率相等时,编码长度最短。这使得它在通信和数据存储领域得到了广泛的应用。例如,在互联网数据传输中,Huffman编码可以有效地减少数据量,从而提高传输速度。

下面的内容就是用 JS 代码实现,将一串字符串转成一段 Huffuman 编码

实现过程

Huffman编码的实现过程如下:

  1. 统计数据中每个字符出现的次数,并按照出现次数进行排序。
  2. 重复以下步骤,直到只剩下一个节点:
    1. 取出两个节点,创建一个新节点,新节点的值为其左右子节点的值的和。
    2. 将新节点插入到节点数组中。 c. 更新节点数组,将原来的两个节点替换为新节点。
  3. 编码规则:对于每个字符,根据其所在的节点路径,将编码设置为路径上所有节点的标签按顺序拼接而成的字符串。

第一步

统计数据中每个字符出现的次数,并按照出现次数进行排序

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 }
]

符合预期

第二步

重复以下步骤,直到只剩下一个节点:

  1. 取出两个节点,创建一个新节点,新节点的值为其左右子节点的值的和。
  2. 将新节点插入到节点数组中。 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编码进行压缩和解压缩。

什么问题可以评论区留言哦。我每天都会分享一篇算法小练习,喜欢就点赞+关注吧

相关推荐
跑跑快跑2 分钟前
React vite + less
前端·react.js·less
chenziang16 分钟前
leetcode hot 全部子集
算法·leetcode·职场和发展
EdwardYange6 分钟前
LeetCode 83 :删除排链表中的重复元素
数据结构·算法·leetcode·链表
nuyoah♂6 分钟前
DAY37|动态规划Part05|完全背包理论基础、LeetCode:518. 零钱兑换 II、377. 组合总和 Ⅳ、70. 爬楼梯 (进阶)
算法·leetcode·动态规划
web1368856587111 分钟前
ctfshow_web入门_命令执行_web29-web39
前端
编程探索者小陈13 分钟前
【优先算法】专题——二分查找算法
算法
GISer_Jing19 分钟前
前端面试题合集(一)——HTML/CSS/Javascript/ES6
前端·javascript·html
清岚_lxn20 分钟前
es6 字符串每隔几个中间插入一个逗号
前端·javascript·算法
胡西风_foxww23 分钟前
【ES6复习笔记】Map(14)
前端·笔记·es6·map
星就前端叭24 分钟前
【开源】一款基于SpringBoot的智慧小区物业管理系统
java·前端·spring boot·后端·开源