堆
在计算机科学中, 一个 ** 堆(heap)** 是一种特殊的基于树的数据结构,它满足下面描述的堆属性。
在一个 最小堆(min heap) 中, 如果 P 是 C 的一个父级节点, 那么 P 的key(或value)应小于或等于 C 的对应值.
在一个 最大堆(max heap) 中, P 的key(或value)大于 C 的对应值。
Heap:
import Comparator from '../../utils/comparator/Comparator'; /** * Parent class for Min and Max Heaps. */ export default class Heap {//堆类 /** * @constructs Heap * @param {Function} [comparatorFunction] */ constructor(comparatorFunction) {//构造函数,参数是自定义比较方法 if (new.target === Heap) { //new.target属性允许你检测函数或构造方法是否是通过new运算符被调用的。 //在普通的函数调用中,new.target 的值是undefined。 //如果没有通过new调用,就抛出错误 throw new TypeError('Cannot construct Heap instance directly'); } // Array representation of the heap. this.heapContainer = [];//堆容器,是一个数组 this.compare = new Comparator(comparatorFunction);//工具比较函数 } /** * @param {number} parentIndex * @return {number} */ getLeftChildIndex(parentIndex) {//根据节点索引计算出左子节点索引,根节点从0开始 return (2 * parentIndex) + 1;//节点索引乘2加1 } /** * @param {number} parentIndex * @return {number} */ getRightChildIndex(parentIndex) {//根据节点索引计算出右子节点索引 return (2 * parentIndex) + 2;//节点索引乘2加2 } /** * @param {number} childIndex * @return {number} */ getParentIndex(childIndex) {//根据节点索引计算出父节点索引 return Math.floor((childIndex - 1) / 2);//节点索引减1除以2向下取整 } /** * @param {number} childIndex * @return {boolean} */ hasParent(childIndex) {//根据一个节点索引判断这个节点有没有父节点 return this.getParentIndex(childIndex) >= 0; } /** * @param {number} parentIndex * @return {boolean} */ hasLeftChild(parentIndex) {//根据一个节点索引判断这个节点有没有左子节点 return this.getLeftChildIndex(parentIndex) < this.heapContainer.length; //如果获取到的左子节点索引小于堆的长度,那么说明这个节点有子节点 } /** * @param {number} parentIndex * @return {boolean} */ hasRightChild(parentIndex) {//根据一个节点索引判断这个节点有没有左子节点 return this.getRightChildIndex(parentIndex) < this.heapContainer.length; //如果获取到的右子节点索引小于堆的长度,那么说明这个节点有子节点 } /** * @param {number} parentIndex * @return {*} */ leftChild(parentIndex) {//根据节点索引返回此节点的左子节点 return this.heapContainer[this.getLeftChildIndex(parentIndex)]; } /** * @param {number} parentIndex * @return {*} */ rightChild(parentIndex) {//根据节点索引返回此节点的右子节点 return this.heapContainer[this.getRightChildIndex(parentIndex)]; } /** * @param {number} childIndex * @return {*} */ parent(childIndex) {//根据节点索引返回此节点的父节点 return this.heapContainer[this.getParentIndex(childIndex)]; } /** * @param {number} indexOne * @param {number} indexTwo */ swap(indexOne, indexTwo) {//两个节点互换位置 const tmp = this.heapContainer[indexTwo]; this.heapContainer[indexTwo] = this.heapContainer[indexOne]; this.heapContainer[indexOne] = tmp; } /** * @return {*} */ peek() {//获取堆的根节点 if (this.heapContainer.length === 0) {//如果堆没有节点,直接返回null return null; } return this.heapContainer[0];//否则返回第一个节点 } /** * @return {*} */ poll() {//删除根节点 if (this.heapContainer.length === 0) {//如果堆没有节点,直接返回null return null; } if (this.heapContainer.length === 1) {//如果堆只有一个节点,就删除最后一个节点,然后返回 return this.heapContainer.pop(); } const item = this.heapContainer[0];//有多个节点,先存下根节点 // Move the last element from the end to the head. //移动最后一个元素从结尾到根部 this.heapContainer[0] = this.heapContainer.pop(); this.heapifyDown();//删除节点后使堆再次成为堆 return item;//返回被删除的根节点 } /** * @param {*} item * @return {Heap} */ add(item) {//往堆中插入新节点 this.heapContainer.push(item);//堆结尾插入新节点 this.heapifyUp();//新增节点后使堆再次成为堆 return this; } /** * @param {*} item * @param {Comparator} [comparator] * @return {Heap} */ remove(item, comparator = this.compare) {//删除与给定值相等的节点 // Find number of items to remove. const numberOfItemsToRemove = this.find(item, comparator).length;//查找所有要删除的节点的个数 for (let iteration = 0; iteration < numberOfItemsToRemove; iteration += 1) { // We need to find item index to remove each time after removal since // indices are being changed after each heapify process. //需要找到需要删除的节点索引,因为每一次删除后的heapify都会让堆结构发生变化 const indexToRemove = this.find(item, comparator).pop();//从要删除的元素索引的数组里拿出最后一个索引 // If we need to remove last child in the heap then just remove it. // There is no need to heapify the heap afterwards. if (indexToRemove === (this.heapContainer.length - 1)) {//如过是堆的最后一个节点,直接删除,不需要做heapify操作 this.heapContainer.pop(); } else {//如果删除的是中间的节点,需要做处理 // Move last element in heap to the vacant (removed) position. //移动最后一个节点到被删除节点的位置 this.heapContainer[indexToRemove] = this.heapContainer.pop(); // Get parent. const parentItem = this.parent(indexToRemove);//获取到当前被删除位置的父节点 // If there is no parent or parent is in correct order with the node // we're going to delete then heapify down. Otherwise heapify up. //如果没有父节点或者父节点的次序正确,就进行向下的heapify,否则向上heapify if ( this.hasLeftChild(indexToRemove) && ( !parentItem || this.pairIsInCorrectOrder(parentItem, this.heapContainer[indexToRemove]) ) ) { this.heapifyDown(indexToRemove); } else { this.heapifyUp(indexToRemove); } } } return this; } /** * @param {*} item * @param {Comparator} [comparator] * @return {Number[]} */ find(item, comparator = this.compare) {//查找与给定值相等的堆节点 const foundItemIndices = [];//结果数组 for (let itemIndex = 0; itemIndex < this.heapContainer.length; itemIndex += 1) { if (comparator.equal(item, this.heapContainer[itemIndex])) {//循环堆,判断如果与给定值相等,就插入结果数组 foundItemIndices.push(itemIndex); } } return foundItemIndices; } /** * @return {boolean} */ isEmpty() {//堆是否为空 return !this.heapContainer.length; } /** * @return {string} */ toString() {//返回堆数组的字符串格式 return this.heapContainer.toString(); } /** * @param {number} [customStartIndex] */ heapifyUp(customStartIndex) {//新增节点后使堆再次成为堆 // Take the last element (last in array or the bottom left in a tree) // in the heap container and lift it up until it is in the correct // order with respect to its parent element. //获取到最后一个节点,将它提升已使之和父节点在一个正确的次序 let currentIndex = customStartIndex || this.heapContainer.length - 1;//当前最后一个节点索引 while ( this.hasParent(currentIndex) && !this.pairIsInCorrectOrder(this.parent(currentIndex), this.heapContainer[currentIndex]) //当前节点有父节点,并且当前节点和父节点的次序不正确 ) { this.swap(currentIndex, this.getParentIndex(currentIndex));//交换当前节点和父节点 currentIndex = this.getParentIndex(currentIndex);//当前节点索引赋值为父节点索引,继续判断循环 } } /** * @param {number} [customStartIndex] */ heapifyDown(customStartIndex = 0) {//删除某一节点后,将堆再次形成堆 // Compare the parent element to its children and swap parent with the appropriate // child (smallest child for MinHeap, largest child for MaxHeap). // Do the same for next children after swap. //将父节点和子节点比较后,将父节点和合适的子节点交换位置(最小堆就将父节点和最小的子节点交换位置;最大堆就将父节点和最大的子节点交换位置) //交换后对后面的子节点做同样的交换操作 let currentIndex = customStartIndex;//需要最先处理的节点的索引 let nextIndex = null;//下一个节点索引 while (this.hasLeftChild(currentIndex)) {//循环条件,当前节点还有左子节点的时候就继续循环 if (//考虑右孩子 this.hasRightChild(currentIndex) && this.pairIsInCorrectOrder(this.rightChild(currentIndex), this.leftChild(currentIndex)) //如果当前节点也有右子节点,并且当前节点的左右两个子节点的次序是正确的(最小堆和最大堆) ) { nextIndex = this.getRightChildIndex(currentIndex);//那么下一个节点就是当前节点的右子节点 } else { nextIndex = this.getLeftChildIndex(currentIndex);//否则是左子节点 } if (this.pairIsInCorrectOrder(//考虑父子结点位置 this.heapContainer[currentIndex], this.heapContainer[nextIndex], )) {//如果当前节点和下一个节点的次序正确,跳出循环结束操作 break; } this.swap(currentIndex, nextIndex);//否则交换当前节点和下一个节点 currentIndex = nextIndex;//当前节点到了下一个位置,继续循环 } } /** * Checks if pair of heap elements is in correct order. * For MinHeap the first element must be always smaller or equal. * For MaxHeap the first element must be always bigger or equal. * * @param {*} firstElement * @param {*} secondElement * @return {boolean} */ /* istanbul ignore next */ //检查一对堆节点是否是正确的次序 //对于最小堆 第一个节点必须小于等于第二个节点 //对于最大堆 第一个节点必须大于等于第二个节点 //当你新实例化MinHeap和MaxHeap的时候,它们原型上的pairIsInCorrectOrder方法会隔离掉Heap类上的同名方法 pairIsInCorrectOrder(firstElement, secondElement) { throw new Error(` You have to implement heap pair comparision method for ${firstElement} and ${secondElement} values. `); } }
MaxHeap:对于最大堆 第一个节点必须大于等于第二个节点
import Heap from './Heap'; export default class MaxHeap extends Heap { /** * Checks if pair of heap elements is in correct order. * For MinHeap the first element must be always smaller or equal. * For MaxHeap the first element must be always bigger or equal. * * @param {*} firstElement * @param {*} secondElement * @return {boolean} */ pairIsInCorrectOrder(firstElement, secondElement) { return this.compare.greaterThanOrEqual(firstElement, secondElement); } }
MinHeap:对于最小堆 第一个节点必须小于等于第二个节点
import Heap from './Heap'; export default class MinHeap extends Heap { /** * Checks if pair of heap elements is in correct order. * For MinHeap the first element must be always smaller or equal. * For MaxHeap the first element must be always bigger or equal. * * @param {*} firstElement * @param {*} secondElement * @return {boolean} */ pairIsInCorrectOrder(firstElement, secondElement) { return this.compare.lessThanOrEqual(firstElement, secondElement); } }
note:
1.对于constructor(comparatorFunction)的解释
constructor(comparatorFunction) { if (new.target === Heap) { throw new TypeError('Cannot construct Heap instance directly'); } // Array representation of the heap. this.heapContainer = []; this.compare = new Comparator(comparatorFunction); }
解释代码:不能直接new一个堆出来
const instantiateHeap = () => { const heap = new Heap(); heap.add(5); }; expect(instantiateHeap).toThrow();
2.移除元素,自定义比较方法:
const maxHeap = new MaxHeap(); maxHeap.add('a'); maxHeap.add('bb'); maxHeap.add('ccc'); maxHeap.add('dddd'); expect(maxHeap.toString()).toBe('dddd,ccc,bb,a'); const comparator = new Comparator((a, b) => { if (a.length === b.length) { return 0; } return a.length < b.length ? -1 : 1; }); maxHeap.remove('hey', comparator); expect(maxHeap.toString()).toBe('dddd,a,bb');