堆:
- 堆是一种完全二叉树,复习一下完全二叉树的定义,完全二叉树的形式是指除了最后一层之外,其他所有层的结点都是满的,而最后一层的所有结点都靠左边。
- 若设二叉树的深度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第 h 层所有的结点都连续集中在最左边,这就是完全二叉树。

完全二叉树:对于任意一个父节点的序号n来说(这里n从0算),它的子节点的序号一定是2n+1,2n+2,因此我们可以直接用数组来表示一个堆。
堆的创建:
1 2 3 4 5 6
| class Heap{ constructor(){ this.array = [] this.length = 0 } }
|

大顶堆/最大堆:
对于任意一个父节点来说,其子节点都小于这个父节点
最大堆的插入:
- 当一个元素要插入时,先放到堆尾,根据堆的特性,对位置进行调整。
- 父节点要大于直接点,找到插入节点的父节点,比父节点值大则交换位置,否则插入成功
- 交换后,继续检测该节点与现在父节点的大小,如果大则交换位置,一直上浮到根节点

JavaScript实现:length可以用this.array.length替代
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| insert(x){ this.array.push(x) if(this.array.length===0){ return } let x_index = this.array.length-1 let parent_index = x_index while(parent_index!==0){ let parent_index = x_index%2===0?(x_index-2):(x_index-1) parent_index = parent_index/2 if(this.array[parent_index]<x){ this.array[x_index] = this.array[parent_index] this.array[parent_index] = x }else{ break } x_index = parent_index } return }
|
最大堆的删除
- 堆的删除一般是值删除堆顶元素,先删除堆顶元素
- 要保证堆一直为完全二叉树,不能直接拿子节点的较大值,而是将末尾元素拿到堆顶,再下沉
- 下沉即先比较该节点与左子节点,如果子节点更大,则交换,如果子节点更小,则与右节点比较
- 直至到最深层,或者比左右子节点都大
JavaScript实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| pop(){ this.array[0] = this.array[this.array.length-1] this.array.pop() let x_index = 0 while(true){ let left_index = 2 * x_index + 1 let right_index = 2 * x_index + 2 left_index = left_index>=this.array.length?x_index:left_index right_index = right_index>=this.array.length?x_index:right_index let large_child_index = this.array[x_index]<this.array[left_index]?left_index:x_index large_child_index = large_child_index!==x_index?large_child_index:(this.array[x_index]<this.array[right_index]?right_index:x_index) if(large_child_index===x_index) break let temp = this.array[x_index] this.array[x_index] = this.array[large_child_index] this.array[large_child_index] = temp x_index = large_child_index }
}
|
小顶堆/最小堆
插入与删除与最大堆类似
插入JavaScript实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| insert(x){ debugger this.array.push(x) if(this.array.length===0){ return } let x_index = this.array.length-1 let parent_index = x_index while(parent_index!==0){ let parent_index = x_index%2===0?(x_index-2):(x_index-1) parent_index = parent_index/2 if(this.array[parent_index]>x){ this.array[x_index] = this.array[parent_index] this.array[parent_index] = x }else{ break } x_index = parent_index } return }
|

删除JavaScript实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| pop(){ debugger this.array[0] = this.array[this.array.length-1] this.array.pop() let x_index = 0 while(true){ let left_index = 2 * x_index + 1 let right_index = 2 * x_index + 2 left_index = left_index>=this.array.length?x_index:left_index right_index = right_index>=this.array.length?x_index:right_index let small_child_index = this.array[x_index]>this.array[left_index]?left_index:x_index small_child_index = small_child_index!==x_index?small_child_index:(this.array[x_index]>this.array[right_index]?right_index:x_index) if(small_child_index===x_index) break let temp = this.array[x_index] this.array[x_index] = this.array[small_child_index] this.array[small_child_index] = temp x_index = small_child_index }
}
|

解决前K个高频元素/低频元素题
LeetCode链接
给定一个非空的整数数组,返回其中出现频率前 k 高的元素。
示例 1:
- 输入: nums = [1,1,1,2,2,3], k = 2
- 输出: [1,2]
示例 2:
- 输入: nums = [1], k = 1
- 输出: [1]
提示:
- 你可以假设给定的 k 总是合理的,且 1 ≤ k ≤ 数组中不相同的元素的个数。
- 你的算法的时间复杂度必须优于 $O(n \log n)$ , n 是数组的大小。
- 题目数据保证答案唯一,换句话说,数组中前 k 个高频元素的集合是唯一的。
- 你可以按任意顺序返回答案。
步骤:
- 获取到每个数据出现的频率,使用哈希表,这里可以用map,key是数字,value是次数
- 对频率排序
- 找出前K个高频元素
注:
- 如果使用快速排序的时间复杂度为O(n)
- 仅维护大小为K的小顶堆,小顶堆的堆顶始终是最小值,如果堆长度小于K则继续往队礼添加,如果长度大于K则弹出堆顶的最小值

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94
| class Heap { constructor(compareFn) { this.compareFn = compareFn; this.queue = []; }
push(item) { this.queue.push(item);
let index = this.size() - 1; let parent = Math.floor((index - 1) / 2);
while (parent >= 0 && this.compare(parent, index) > 0) { [this.queue[index], this.queue[parent]] = [this.queue[parent], this.queue[index]];
index = parent; parent = Math.floor((index - 1) / 2); } }
pop() { const out = this.queue[0];
this.queue[0] = this.queue.pop();
let index = 0; let left = 1; let searchChild = this.compare(left, left + 1) > 0 ? left + 1 : left;
while (searchChild !== undefined && this.compare(index, searchChild) > 0) { [this.queue[index], this.queue[searchChild]] = [this.queue[searchChild], this.queue[index]];
index = searchChild; left = 2 * index + 1; searchChild = this.compare(left, left + 1) > 0 ? left + 1 : left; }
return out; }
size() { return this.queue.length; }
compare(index1, index2) { if (this.queue[index1] === undefined) return 1; if (this.queue[index2] === undefined) return -1;
return this.compareFn(this.queue[index1], this.queue[index2]); }
}
const topKFrequent = function (nums, k) { const map = new Map();
for (const num of nums) { map.set(num, (map.get(num) || 0) + 1); }
const heap= new Heap((a, b) => a[1] - b[1]);
for (const entry of map.entries()) { heap.push(entry);
if (heap.size() > k) { heap.pop(); } }
const res = [];
for (let i = heap.size() - 1; i >= 0; i--) { res[i] = heap.pop()[0]; }
return res; };
|
由于key和value是要绑定的,所以元素不能是简单的数组,而应该是包含key和value的二维数组,同时为了使得代码重用,将比较函数抽取出来,当做参数传递