哈夫曼树的js实现小白知识_运算使用攻略

哈夫曼树的js实现小白知识

前言

哈夫曼树是数据压缩编码算法的基础,本文使用JavaScript语言实现了该算法。算法流程:输入待编码的字符串,算法去构造哈夫曼树,从而实现对字符串的二进制压缩编码。

哈夫曼树的js实现小白知识_运算使用攻略

对于哈夫曼树理论的学习,可去参见其他文章。本文仅包含实现的代码以及注释。

注释比较丰富,相信不难理解。

算法实现

树节点

既然是树数据结构,就要有树节点,下面是树节点定义

class Node {  
    constructor(value, char, left, right) {  
        this.val = value; // 字符出现次数  
        this.char = char; // 待编码字符  
        this.left = left;  
        this.right = right;  
    }  
}

一般来说,节点只需要val,left,right即可,这里加了一个char字段,表示该节点代表待编码字符串里面的哪个字符,当前节点是叶子节点的时候,会赋值这个字段。

核心代码

构造函数
class huffmanTree{ 
    constructor(str){  
        // 第一步,统计字符出现频率  
        let hash = {};  
        for(let i = 0; i < str.length; i++){  
            hash[str[i]] = ~~hash[str[i]] + 1;  
        }  
        this.hash = hash;  
  
        // 第二步,构造哈夫曼树  
        this.huffmanTree = this.getHuffmanTree();  
  
        // 第三步,遍历哈夫曼树,得到编码表
        let map = this.getHuffmanCode(this.huffmanTree);  
        // 查看编码表,即每个字符的二进制编码是什么  
        console.log(map);  
  
        // 第四部,根据编码对照表,返回最终的二进制编码  
        this.binaryStr = this.getBinaryStr(map, str);  
    } 
}

下面我们逐一的看一下,(1)构造哈夫曼树的过程、(2)遍历哈弗曼树取得编码表的过程 以及 (3)返回最终二进制串的过程。

构造哈夫曼树
    // 构造哈夫曼树  
    getHuffmanTree(){  
        // 以各个字符出现次数为node.val, 构造森林  
        let forest = []  
        for(let char in this.hash){  
            let node = new Node(this.hash[char], char); 
            forest.push(node);  
        }  
  
        let allNodes = []; // 存放被合并的节点,因为不能真的删除森林中任何一个节点,否则.left .right就找不到节点了  
        // 等到森林只剩一个节点时,表示合并过程结束,树就生成了
        while(forest.length !== 1){  
            // 从森林中找到两个最小的树,合并之  
            forest.sort((a, b) => {  
                return a.val - b.val;  
            });  
  
            let node = new Node(forest[0].val + forest[1].val, '');  
            allNodes.push(forest[0]);  
            allNodes.push(forest[1]);  
            node.left = allNodes[allNodes.length - 2]; // 左子树放置词频低的  
            node.right = allNodes[allNodes.length - 1]; // 右子树放置词频高的  
  
            // 删除最小的两棵树  
            forest = forest.slice(2);  
            // 新增的树加入  
            forest.push(node);  
        }  
  
        // 生成的哈夫曼树,仅剩一个节点,即整棵树的根节点
        return forest[0];  
    } 
遍历哈夫曼树,返回编码表
    // 遍历哈夫曼树,返回一个 原始字符 和 二进制编码 的对照表  
    getHuffmanCode(tree){  
        let hash = {};  // 对照表
        let traversal = (node, curPath) => {  
            if (!node.length && !node.right) return;  
            if (node.left && !node.left.left && !node.left.right){  
                hash[node.left.char] = curPath + '0';  
            }  
            if (node.right && !node.right.left && !node.right.right){  
                hash[node.right.char] = curPath + '1';  
            }  
            // 往左遍历,路径加0  
            if(node.left){  
                traversal(node.left, curPath + '0');  
            }  
            // 往右遍历,路径加1  
            if(node.right){  
                traversal(node.right, curPath + '1');  
            }  
        };  
        traversal(tree, '');  
        return hash;  
    }  
返回编码串
    // 返回最终的压缩后的二进制串  
    getBinaryStr(map, originStr){  
        let result = '';  
        for(let i = 0; i < originStr.length; i++){  
            result += map[originStr[i]];  
        }  
        return result;  
    }  
代码汇总
// 哈弗曼编码是将一个 字符串序列 用 二进制表示 的压缩算法  
class huffmanTree{  
    constructor(str){  
        // 第一步,统计字符出现频率  
        let hash = {};  
        for(let i = 0; i < str.length; i++){  
            hash[str[i]] = ~~hash[str[i]] + 1;  
        }  
        this.hash = hash;  
  
        // 构造哈夫曼树  
        this.huffmanTree = this.getHuffmanTree();  
  
        let map = this.getHuffmanCode(this.huffmanTree);  
        // 查看对照表,即每个字符的二进制编码是什么  
        console.log(map);  
  
        // 最终的二进制编码  
        this.binaryStr = this.getBinaryStr(map, str);  
    }  
  
    // 构造哈夫曼树  
    getHuffmanTree(){  
        // 以各个字符出现次数为node.val, 构造森林  
        let forest = []  
        for(let char in this.hash){  
            let node = new Node(this.hash[char], char); 
            forest.push(node);  
        }  
  
        // 等到森林只剩一个节点时,表示合并过程结束,树就生成了  
        let allNodes = []; // 存放被合并的节点,因为不能真的删除森林中任何一个节点,否则.left .right就找不到节点了  
        while(forest.length !== 1){  
            // 从森林中找到两个最小的树,合并之  
            forest.sort((a, b) => {  
                return a.val - b.val;  
            });  
  
            let node = new Node(forest[0].val + forest[1].val, '');  
            allNodes.push(forest[0]);  
            allNodes.push(forest[1]);  
            node.left = allNodes[allNodes.length - 2]; // 左子树放置词频低的  
            node.right = allNodes[allNodes.length - 1]; // 右子树放置词频高的  
  
            // 删除最小的两棵树  
            forest = forest.slice(2);  
            // 新增的树加入  
            forest.push(node);  
        }  
  
        // 生成的哈夫曼树  
        return forest[0];  
    }  
  
    // 遍历哈夫曼树,返回一个 原始字符 和 二进制编码 的对照表  
    getHuffmanCode(tree){  
        let hash = {};  // 对照表
        let traversal = (node, curPath) => {  
            if (!node.length && !node.right) return;  
            if (node.left && !node.left.left && !node.left.right){  
                hash[node.left.char] = curPath + '0';  
            }  
            if (node.right && !node.right.left && !node.right.right){  
                hash[node.right.char] = curPath + '1';  
            }  
            // 往左遍历,路径加0  
            if(node.left){  
                traversal(node.left, curPath + '0');  
            }  
            // 往右遍历,路径加1  
            if(node.right){  
                traversal(node.right, curPath + '1');  
            }  
        };  
        traversal(tree, '');  
        return hash;  
    }  
  
    // 返回最终的压缩后的二进制串  
    getBinaryStr(map, originStr){  
        let result = '';  
        for(let i = 0; i < originStr.length; i++){  
            result += map[originStr[i]];  
        }  
        return result;  
    }  
}

测试代码

let tree = new huffmanTree('ABBCCCDDDDEEEEE')  
console.log(tree)

编码对照表:{ C: ’00’, A: ‘010’, B: ‘011’, D: ’10’, E: ’11’ }
最终编码结果:010011011000000101010101111111111

结语

前端算法库:https://github.com/cunzaizhuyi
这里记录了我刷过的近500道LeetCode的题解,
希望对前端同行找工作面试、修炼算法内功有帮助。

海计划公众号
(0)
上一篇 2020/03/19 07:13
下一篇 2020/03/19 07:13

您可能感兴趣的内容

  • 10分钟快速了解 React 基础基础入门_react指南攻略

    如果你还不会 React,希望本文可以帮你快速了解 React.js 的基础知识。创建项目使用 create-react-app 工具快速创建 React SPA。# 创建项目
    yarn create react-app my-appcd my-app# 开发模式下运行程序
    yarn start项目初始结构:my-app/README.mdnode_modu

    2020/03/26
  • 团队管理上的一点思考基础知识_管理入门基础

    『铁打的营盘,流水的兵』,这句话在大约09年开始,就时常会听到,意思大概就是一个如钢铁般的营地,是由一批批如流水般更替的士兵打造出来的。很多时候说这句话会有点伤感,有点『一将功成万骨枯』的感觉,不过渐渐的我有了新的体会,为什么会觉得伤感?因为关注的点不同,之前没有带团队,所以更多的关注到了后半句的『流水的兵』,自己身为一个兵的感慨;而如果把关注点放到前半句『

    2020/04/03
  • 优化Webpack构建性能的几点建议入门教程_webpack小白知识

    Webpack 作为目前最流行的前端构建工具之一,在 vue/react 等 Framework 的生态圈中都占据重要地位。在开发现代 Web 应用的过程中,Webpack 和我们的开发过程和发布过程都息息相关,如何改善 Webpack 构建打包的性能也关系到我们开发和发布部署的效率。 以下是一些关于优化 Webpack 构建性能的几点建议:一、选择合适的

    2020/04/05
  • jpg、gif、png、svg、bmp、WebP图像格式的区别以及应用场景基础知识教程_图片使用说明

    一、jpg格式 全名应该是JPEG。JPEG 图片以 24 位颜色存储单个光栅图像。JPEG 是与平台无关的格式,支持最高级别的压缩,不过,这种压缩是有损耗的。渐近式JPEG文件支持交错。可以提高或降低 JPEG文件压缩的级别。但是,文件大小是以图像质量为代价的。压缩比率可以高达 100:1。(JPEG 格式可在 10:1 到 20:1 的比率下轻松地压缩

    2020/03/31
  • new运算符的原理小白教程_原理入门攻略

    关于 new 运算符的原理: 1、红宝书上解释:(1)创建一个新对象(2)将构造函数的作用域赋给新对象(3)执行构造函数中的代码(4)返回新对象 2、MDN上的解释:(1)一个继承自 Foo.prototype 的新对象被创建(2)使用指定的参数调用构造函数 Foo,并将 this 绑定到新创建的对象。new Foo 等同于 new Foo(),也就是没有指

    2020/03/26
  • 函数式响应式编程 – Functional Reactive Programming菜鸟教程_响应式基础教程

    我们略过概念,直接看函数式响应式编程解决了什么问题。故事从下面这个例子展开:两个密码输入框,一个提交按钮。密码、确认密码都填写并一致,允许提交;不一致提示错误。HTML 如下:
    <input id="confirmPwd" placehold

    2020/03/24
  • 7个常见的 JavaScript 测验及解答小白帮助_js知识新手入门

    介绍我相信学习新事物并评估我们所知的东西对自己的进步非常有用,可以避免了我们觉得自己的知识过时的情况。在本文中,我将介绍一些常见的 JavaScript 知识。请享用!1.声明查看以下代码,并回答输出的内容(以及原因)。// situation 1
    console.log(person);
    var person = ‘John’;// situation 2

    2020/03/23
  • CDN(Content Delivery Network)原理入门基础知识_CDN零基础入门

    CDN即内容分发网络,一般包括分发服务系统,负载均衡系统和管理系统。分发服务系统,其基本的工作单元就是各个cache服务器。负责直接响应用户请求,将内容快速分发到用户;同时还负责内容更新,保证和源站内容同步。根据内容类型和服务种类的不同,分发服务系统分为多个子服务系统,如:网页加速服务、流媒体加速服务、应用加速服务等。每个子服务系统都是一个分布式的服务集群,

    2020/03/24
  • 页面需要渲染10万条数据,应该怎么实现?使用教程_渲染基础知识教程

    关键点:不卡顿,交互流畅一、最传统、最简单粗暴的方式

    <meta http-equiv="X-UA-

    2020/03/29
  • Assetizr小白教程_在线图片最佳优化工具

    Assetizr小白教程 官方网址:https://assetizr.com/ 简介描述:在线图片最佳优化工具 Assetizr是一款图片处理工具,提供最佳化、调整图片大小、重新命…

    2020/03/10
  • vue源码解析:nextTick菜鸟教程网_源码入门百科

    1 nextTick的使用vue中dom的更像并不是实时的,当数据改变后,vue会把渲染watcher添加到异步队列,异步执行,同步代码执行完成后再统一修改dom,我们看下面的代码。{{msg}}
    export default {name: ‘index’,data ()

    2020/03/29
  • HTML5中的lang属性,zh-CN还是zh-Hans?小白入门_属性使用帮助

    一、资源先提供资源。如果我弄错了什么,请以这些文档为准:W3C文档、IANA已登记的子标签、BCP 47、RFC 5646。二、格式简介先上一张图片:一个Language Tags,由①到⑦一共四个子标签组成。有什么盘算不清楚的,请参考资源部分提供的文档。三、各部分含义①language:主语言,用代码“zh”表示汉语,小写。好像对于大小写没有强制要求,习惯

    2020/03/20
  • 油猴脚本编写教程入门基础_浏览器基础知识

    油猴脚本(Tampermonkey)是一个非常流行的浏览器扩展,它可以运行由广大社区编写的扩展脚本,来实现各式各样的功能,常见的去广告、修改样式文件、甚至是下载视频。今天我们就来看看如何编写自己的油猴脚本。当然为了运行油猴脚本,你应该在浏览器中安装油猴插件。安装油猴插件安装油猴插件非常简单,直接在浏览器的扩展商店中安装即可。国产浏览器的话一般可以通过下载扩展

    2020/03/20
  • 当我开始编程时,我希望知道的 30 件事小白教程_程序员小白教程

    如果你想成为一名程序员,这个建议可以帮助你走上正确的道路。程序员不是一个容易的职业,每年都有许多人从国内顶尖院校的计算机科学专业毕业,这是任何人都能从事的竞争最大的职业之一。同时,编程也是令人兴奋的。随着技术的进步,工业界每天都有创新。编程对于热爱它的人来说是一项充满激情的事业。当我 15 年前开始做程序员的时候,我希望有人能告诉我下面清单上的一切建议。这个

    2020/03/26
  • js检测页面上一个元素是否已经滚动到了屏幕的可视区域内入门教程_js知识小白攻略

    应用场景:只要页面加载了,其中在页面中出现的li就向控制台输出第几个发送请求;在本次加载的页面中,再将滚动条滚回前边的li,不再向控制台输出东西,也就是说已经显示过的li,不再向控制台输出东西。

    • 0001
    • 0002
    • 0003
    • 0004</li
    2020/04/03
  • JS 进阶需要掌握的13个概念小白指南_概念小白知识

    1.变量赋值 (值 vs 引用)理解 JS 如何给变量赋值可以帮助我们减少一些不必要的 bug。相反,如果,不理解这一点,可能很容易地编写被无意中更改值的代码。JS 总是按照值来给变量赋值。 这一部分非常重要:当指定的值是 JavaScript 的五种基本类型之一(即 Boolean,null,undefined,String 和 Number)时,分配是实

    2020/03/23