这是第 90 篇不掺水的原创,想获取更多原创好文,请搜索公众号关注咱们吧~ 本文首发于政采云前端博客: 如何用 JS 实现二叉堆
二叉树(Binary Tree)是一种树形结构,它的特色是每一个节点最多只有两个分支节点,一棵二叉树一般由根节点、分支节点、叶子节点组成,以下图所示。每一个分支节点也经常被称做为一棵子树,而二叉堆是一种特殊的树,它属于彻底二叉树。javascript
在平常工做中会遇到不少数组的操做,好比排序等。那么理解二叉堆的实现对之后的开发效率会有所提高,下面就简单介绍一下什么是二叉树,什么是二叉堆。前端
在二叉树中,咱们经常还会用父节点和子节点来描述,好比上图中左侧节点 2 为 6 和 3 的父节点,反之 6 和 3 是 2 子节点。java
二叉树分为满二叉树(full binary tree)和彻底二叉树(complete binary tree)。api
从图中咱们能够看出二叉树是从上到下依次排列下来,可想而知能够用一个数组来表示二叉树的结构,从下标 index( 0 - 8 ) 从上到下依次排列。数组
二叉堆是一个彻底二叉树,父节点与子节点要保持固定的序关系,而且每一个节点的左子树和右子树都是一个二叉堆。函数
从上图能够看出post
二叉堆根据排序不一样,能够分为最大堆和最小堆性能
经过上面的讲述想必你们对二叉堆有了必定的理解,那么接下来就是如何实现。以最大堆为例,首先要初始化数组而后经过交换位置造成最大堆。ui
从上面描述,咱们能够知道二叉堆其实就是一个数组,那么初始化就很是简单了。this
class Heap{ constructor(arr){ this.data = [...arr]; this.size = this.data.length; } }
图一中 2 做为父节点小于子节点,很显然不符合最大堆性质。maxHeapify 函数能够把每一个不符合最大堆性质的节点调换位置,从而知足最大堆性质的数组。
调整步骤:
1.调整分支节点 2 的位置(不知足最大堆性质)
2.获取父节点 2 的左右节点 ( 12 , 5 ) ,从 ( 2 , 15 , 5 ) 中进行比较
3.找出最大的节点与父节点进行交换,若是该节点自己为最大节点则中止操做
4.重复 step2 的操做,从 2 , 4 , 7 中找出最大值与 2 作交换(递归)
maxHeapify(i) { let max = i; if(i >= this.size){ return; } // 当前序号的左节点 const l = i * 2 + 1; // 当前须要的右节点 const r = i * 2 + 2; // 求当前节点与其左右节点三者中的最大值 if(l < this.size && this.data[l] > this.data[max]){ max = l; } if(r < this.size && this.data[r] > this.data[max]){ max = r; } // 最终max节点是其自己,则已经知足最大堆性质,中止操做 if(max === i) { return; } // 父节点与最大值节点作交换 const t = this.data[i]; this.data[i] = this.data[max]; this.data[max] = t; // 递归向下继续执行 return this.maxHeapify(max); }
咱们能够看到,初始化是由一个数组组成,如下图为例很显然并不会知足最大堆的性质,上述 maxHeapify 函数只是对某一个节点做出对调,没法对整个数组进行重构,因此咱们要依次对数组进行递归重构。
1.找到全部分支节点 Math.floor( N / 2 )(不包括叶子节点)
2.将找到的子节点进行 maxHeapify 操做
rebuildHeap(){ // 叶子节点 const L = Math.floor(this.size / 2); for(let i = L - 1; i >= 0; i--){ this.maxHeapify(i); } }
1.swap 函数交换首位位置
2.将最后一个从堆中拿出至关于 size - 1
3.执行 maxHeapify 函数进行根节点比较找出最大值进行交换
4.最终 data 会变成一个升序的数组
sort() { for(let i = this.size - 1; i > 0; i--){ swap(this.data, 0, i); this.size--; this.maxHeapify(0); } }
Insert 函数做为插入节点函数,首先
1.往 data 结尾插入节点
2.由于节点追加,size + 1
3.由于一个父节点拥有 2 个子节点,咱们能够根据这个性质经过 isHeap 函数获取第一个叶子节点,能够经过第一个叶子节点获取新插入的节点,而后进行 3 个值的对比,找出最大值,判断插入的节点。若是跟父节点相同则不进行重构(相等知足二叉堆性质),不然进行 rebuildHeap 重构堆
isHeap() { const L = Math.floor(this.size / 2); for (let i = L - 1; i >= 0; i--) { const l = this.data[left(i)] || Number.MIN_SAFE_INTEGER; const r = this.data[right(i)] || Number.MIN_SAFE_INTEGER; const max = Math.max(this.data[i], l, r); if (max !== this.data[i]) { return false; } return true; } } insert(key) { this.data[this.size] = key; this.size++ if (this.isHeap()) { return; } this.rebuildHeap(); }
delete 函数做为删除节点,首先
1.删除传入index的节点
2.由于节点删除,size - 1
3.重复上面插入节点的操做
delete(index) { if (index >= this.size) { return; } this.data.splice(index, 1); this.size--; if (this.isHeap()) { return; } this.rebuildHeap(); }
/** * 最大堆 */ function left(i) { return (i * 2) + 1; } function right(i) { return (i * 2) + 2; } function swap(A, i, j) { const t = A[i]; A[i] = A[j]; A[j] = t; } class Heap { constructor(arr) { this.data = [...arr]; this.size = this.data.length; this.rebuildHeap = this.rebuildHeap.bind(this); this.isHeap = this.isHeap.bind(this); this.sort = this.sort.bind(this); this.insert = this.insert.bind(this); this.delete = this.delete.bind(this); this.maxHeapify = this.maxHeapify.bind(this); } /** * 重构堆,造成最大堆 */ rebuildHeap() { const L = Math.floor(this.size / 2); for (let i = L - 1; i >= 0; i--) { this.maxHeapify(i); } } isHeap() { const L = Math.floor(this.size / 2); for (let i = L - 1; i >= 0; i--) { const l = this.data[left(i)] || Number.MIN_SAFE_INTEGER; const r = this.data[right(i)] || Number.MIN_SAFE_INTEGER; const max = Math.max(this.data[i], l, r); if (max !== this.data[i]) { return false; } return true; } } sort() { for (let i = this.size - 1; i > 0; i--) { swap(this.data, 0, i); this.size--; this.maxHeapify(0); } } insert(key) { this.data[this.size++] = key; if (this.isHeap()) { return; } this.rebuildHeap(); } delete(index) { if (index >= this.size) { return; } this.data.splice(index, 1); this.size--; if (this.isHeap()) { return; } this.rebuildHeap(); } /** * 交换父子节点位置,符合最大堆特征 * @param {*} i */ maxHeapify(i) { let max = i; if (i >= this.size) { return; } // 求左右节点中较大的序号 const l = left(i); const r = right(i); if (l < this.size && this.data[l] > this.data[max]) { max = l; } if (r < this.size && this.data[r] > this.data[max]) { max = r; } // 若是当前节点最大,已是最大堆 if (max === i) { return; } swap(this.data, i, max); // 递归向下继续执行 return this.maxHeapify(max); } } module.exports = Heap;
相信经过上面的讲述你们对最大堆的实现已经有了必定的理解,咱们能够利用这个来进行排序。
const arr = [15, 12, 8, 2, 5, 2, 3, 4, 7]; const fun = new Heap(arr); fun.rebuildHeap(); // 造成最大堆的结构 fun.sort();// 经过排序,生成一个升序的数组 console.log(fun.data) // [2, 2, 3, 4, 5, 7, 8, 12, 15]
文章中主要讲述了二叉树、二叉堆的概念,而后经过代码实现二叉堆。咱们能够经过二叉堆来作排序和优先级队列等。
政采云前端团队(ZooTeam),一个年轻富有激情和创造力的前端团队,隶属于政采云产品研发部,Base 在风景如画的杭州。团队现有 40 余个前端小伙伴,平均年龄 27 岁,近 3 成是全栈工程师,妥妥的青年风暴团。成员构成既有来自于阿里、网易的“老”兵,也有浙大、中科大、杭电等校的应届新人。团队在平常的业务对接以外,还在物料体系、工程平台、搭建平台、性能体验、云端应用、数据分析及可视化等方向进行技术探索和实战,推进并落地了一系列的内部技术产品,持续探索前端技术体系的新边界。
若是你想改变一直被事折腾,但愿开始能折腾事;若是你想改变一直被告诫须要多些想法,却无从破局;若是你想改变你有能力去作成那个结果,却不须要你;若是你想改变你想作成的事须要一个团队去支撑,但没你带人的位置;若是你想改变既定的节奏,将会是“5 年工做时间 3 年工做经验”;若是你想改变原本悟性不错,但老是有那一层窗户纸的模糊… 若是你相信相信的力量,相信平凡人能成就非凡事,相信能遇到更好的本身。若是你但愿参与到随着业务腾飞的过程,亲手推进一个有着深刻的业务理解、完善的技术体系、技术创造价值、影响力外溢的前端团队的成长历程,我以为咱们该聊聊。任什么时候间,等着你写点什么,发给 ZooTeam@cai-inc.com