本文已参与「新人创作礼」活动,一起开启掘金创作之路。
堆
js中的堆
- 图中的二叉树即可用数组表示为[1,3,6,5,9,8]
- 1的左侧子节点位置是 2 * 0 + 1 = 1 (节点3)
- 1的右侧子节点位置是 2 * 0 + 2 = 2 (节点6)
- 5的父节点的位置是(3 - 1)/ 2 = 1 (节点3)
堆的应用
- 高效、快速地找出最大值和最小值,时间复杂度O(1)
- 找出第K个最大(小)元素
- 构建一个最小堆,将元素一次插入堆中
- 当堆的容量超过K,就删除堆顶
- 插入结束后,堆顶就是第K个最大元素
js实现最小堆类
- 实现步骤
- 在类里,声明一个数组,用来装元素
- 主要方法:插入、删除堆顶、获取堆顶、获取堆大小
class MinHeap{
constructor() {
this.heap = []
}
// 交换
swap(i1, i2) {
const temp = this.heap[i1]
this.heap[i1] = this.heap[i2]
this.heap[i2] = temp
}
// 获取父节点索引
getParentIndex() {
return (i - 1) >> 1 // Math.floor((i - 1) / 2)
}
// 上移操作
shiftUp(index) {
// 如果是堆顶
if (index == 0) return
const parentIndex = this.getParentIndex(index)
if (this.heap(parentIndex) > this.heap[index]) {
this.swap(parentIndex, index)
this.shiftUp(parentIndex)
}
}
getLeftIndex(){
return i * 2 + 1
}
getRightIndex(){
return i * 2 + 2
}
/**
* [下移方法]
*
*/
shiftDown(index) {
const leftIndex = this.getLeftIndex(index)
const rightIndex = this.getRightIndex(index)
if (this.heap[leftIndex] < this.heap[index]) {
this.swap(leftIndex, index)
this.shiftDown(leftIndex)
}
if (this.heap[rightIndex] < this.heap[index]) {
this.swap(rightIndex, index)
this.shiftDown(rightIndex)
}
}
/**
* [插入方法]
* 将值插入堆的底部,即数组的尾部
* 然后上移:将这个值和它的父节点进行交换,知道父节点小于等于这个插入值
* 大小为k的堆中 插入元素的时间复杂度为O(logk)
*/
insert(value) {
this.heap.push(value)
this.shiftUp(this.heap.length - 1)
}
/**
* [删除堆顶]
* 用数组尾部元素替换堆顶(直接删除堆顶会破坏堆结构)
* 然后下移:将新堆顶和它的子节点进行交换,直到子节点大于等于这个新堆顶
* 大小为k的堆中删除堆顶的时间复杂度为O(logk)
*/
pop() {
this.heap[0] = this.heap.pop()
this.shiftDown(0)
}
// 获取堆顶
peek() {
return this.heap[0]
}
// 堆的大小
size() {
return this.heap.length
}
}
const h = new MinHeap()
//插入方法
h.insert(3) // [3]
h.insert(2) // [2, 3]
h.insert(1) // [1, 3, 2] 虽然不是递增关系,但保证了最小堆的逻辑
// 删除堆顶
h.pop() // [2, 3]
// 获取堆顶和堆的大小
h.peek() // 2
h.size() // 2
leetcode 215: 数组中的第K个最大元素
- 解题思路
- 第k个最大元素
- 考虑使用最小堆解决
- 解题步骤
- 构建一个最小堆,依次把数组的值插入堆中
- 当堆的容量超过k,就删除堆顶
- 插入后,堆顶就是第k个最大元素
// 时间复杂度O(n * logk) 空间复杂度O(k)
var findKthLargest = function(nums, k) {
const h = new MinHeap()
nums.forEach(n => {
h.insert(n)
if (h.size() > k) {
h.pop()
}
})
return h.peek()
}
leetcode 347: 前 K 个高频元素
class MinHeap{
constructor() {
this.heap = []
}
// 交换
swap(i1, i2) {
const temp = this.heap[i1]
this.heap[i1] = this.heap[i2]
this.heap[i2] = temp
}
// 获取父节点索引
getParentIndex() {
return (i - 1) >> 1 // Math.floor((i - 1) / 2)
}
// 上移操作
shiftUp(index) {
// 如果是堆顶
if (index == 0) return
const parentIndex = this.getParentIndex(index)
if (this.heap(parentIndex).value > this.heap[index].value) {
this.swap(parentIndex, index)
this.shiftUp(parentIndex)
}
}
getLeftIndex(){
return i * 2 + 1
}
getRightIndex(){
return i * 2 + 2
}
/**
* [下移方法]
*
*/
shiftDown(index) {
const leftIndex = this.getLeftIndex(index)
const rightIndex = this.getRightIndex(index)
if (this.heap[leftIndex].value < this.heap[index].value) {
this.swap(leftIndex, index)
this.shiftDown(leftIndex)
}
if (this.heap[rightIndex].value < this.heap[index].value) {
this.swap(rightIndex, index)
this.shiftDown(rightIndex)
}
}
/**
* [插入方法]
* 将值插入堆的底部,即数组的尾部
* 然后上移:将这个值和它的父节点进行交换,知道父节点小于等于这个插入值
* 大小为k的堆中 插入元素的时间复杂度为O(logk)
*/
insert(value) {
this.heap.push(value)
this.shiftUp(this.heap.length - 1)
}
/**
* [删除堆顶]
* 用数组尾部元素替换堆顶(直接删除堆顶会破坏堆结构)
* 然后下移:将新堆顶和它的子节点进行交换,直到子节点大于等于这个新堆顶
* 大小为k的堆中删除堆顶的时间复杂度为O(logk)
*/
pop() {
this.heap[0] = this.heap.pop()
this.shiftDown(0)
}
// 获取堆顶
peek() {
return this.heap[0]
}
// 堆的大小
size() {
return this.heap.length
}
}
// 时间复杂度O(nlogk) 满足题中小于O(nlogn) 因为k小于n
// 空间复杂度O(n)
var topKFre = function (nums, k) {
const map = new Map()
nums.forEach(n => {
map.set(n, map.has(n) > map.get(n) + 1: 1)
})
const h = new MinHeap()
map.forEach((value, key) => {
h.insert({value, key})
if (h.size() > k) {
h.pop()
}
})
return h.heap.map(a => a.key)
}