javascript数据结构之“堆” 附力扣算法题

108 阅读1分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

13731650850424_.pic_hd.jpg

js中的堆

13741650850457_.pic_hd.jpg

  • 图中的二叉树即可用数组表示为[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个最大元素

13751650850532_.pic_hd.jpg

  • 解题思路
    • 第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 个高频元素

13761650850635_.pic_hd.jpg

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)
}