目录
一、是什么
1.最大堆 (大顶堆/大根堆): 完全二叉树,每个节点元素都大于他的直接左节点和右节点,根节点是最大值
2.最小堆 (小顶堆/小根堆):完全二叉树,每个节点元素都小于他的直接左节点和右节点,根节点是最小值
二、实现方式
1.数据结构 :一维数组
当前索引 index
父节点索引 Math.floor( index-1)/2
左节点index*2+1
右节点 index*2+2
2.需要实现的方法
- add 添加元素 :push+ heapifyUp(上浮到合适的位置,满足最大堆/最小堆定义)
2)heapifyUp方法(把添加的元素上浮到合适的位置):从数组末尾开始找父元素,如果当前元素和父元素的大小关系不满足定义就交换,满足就停止循环。
最小堆:当前的元素比父元素还小交换,当前元素索引变成父元素索引;直到当前元素大于父元素停止循环
最大堆:当前的元素比父元素还大交换,当前元素索引变成父元素的索引;直到当前元素小于父元素停止循环。
3)remove取出堆顶元素 :copy last +pop+heapifyDown暂存数组第一个元素最后返回;用最后一个元素覆盖第一个元素当作根节点;删除最后一个元素;heapifyDown把新的根节点(值为最后一个元素)下沉到合适的位置
4)heapifyDown下沉:把堆顶的元素和 左右子节点比较放到合适的位置使得 最大堆/最小堆 符合定义。
最小堆:如果左右子节点的最小值比当前元素小则交换,当前元素索引变成左右子节点的最小值对应的索引;直到当前元素小于左右子节点的最小值停止循环
最大堆:如果左右子节点的最大值比 当前元素大交换,当前元素索引变成左右子节点最大值对应的索引;直到当前元素大于 左右子节点的最大值停止循环
3.代码实现:
最小堆
javascript
/**
* 最小堆
* 定义: 完全二叉树,每个节点的值都小于或等于其子节点的值
* 实现方式: 数组
* 应用场景: 优先队列、TopK问题、中位数问题等
* 优点: 快速获取最小值,快速插入,快速删除
* 缺点: 不能快速获取最大值
*/
class MinHeap {
constructor() {
this.heap = []
}
// 添加元素
add(value) {
this.heap.push(value)
this.heapifyUp()
}
// 上浮操作
heapifyUp() {
let index = this.heap.length - 1
while (index > 0) {
let parentIndex = Math.floor((index - 1) / 2)
// 如果当前元素小于父元素,则交换位置
if (this.heap[index] < this.heap[parentIndex]) {
;[this.heap[index], this.heap[parentIndex]] = [this.heap[parentIndex], this.heap[index]]
index = parentIndex
} else {
// 如果当前元素已经大于等于父元素,则停止上浮
break
}
}
}
// 下沉操作
heapifyDown() {
let index = 0
while (index < this.heap.length) {
let leftChildIndex = index * 2 + 1
let rightChildIndex = index * 2 + 2
// 找出当前元素的左右子元素中较小的那个(最小的要放到顶部)
let minIndex = index
if (leftChildIndex < this.heap.length) {
if (this.heap[leftChildIndex] < this.heap[index]) {
minIndex = leftChildIndex
}
}
if (rightChildIndex < this.heap.length) {
if (this.heap[rightChildIndex] < this.heap[minIndex]) {
minIndex = rightChildIndex
}
}
if (minIndex !== index) {
;[this.heap[index], this.heap[minIndex]] = [this.heap[minIndex], this.heap[index]]
index = minIndex
} else {
// 如果当前元素已经大于等于左右子元素,则停止下沉
break
}
}
}
//取出最小值
remove() {
let value = this.heap[0]
// 将最后一个元素放到顶部
this.heap[0] = this.heap[this.heap.length - 1]
this.heap.pop()
if (!this.isEmpty()) {
this.heapifyDown()
}
return value
}
getMin() {
if (this.isEmpty()) {
return null
}
return this.heap[0]
}
size() {
return this.heap.length
}
isEmpty() {
return this.heap.length === 0
}
}
const minHeap = new MinHeap()
minHeap.add(3)
minHeap.add(2)
minHeap.add(1)
minHeap.add(4)
minHeap.add(5)
console.log('minHeap start=============', minHeap.heap)
console.log(minHeap.getMin()) // 1
minHeap.remove()
console.log(minHeap.getMin()) // 2
minHeap.remove()
console.log(minHeap.getMin()) // 3
minHeap.remove()
console.log(minHeap.getMin()) // 4
minHeap.remove()
console.log(minHeap.getMin()) // 5
minHeap.remove()
console.log(minHeap.getMin()) // null
console.log('minHeap end=============')
最大堆
javascript
/**
* 最大堆
* 定义: 完全二叉树,每个节点的值都大于或等于其子节点的值
* 实现方式: 数组
* 应用场景: 优先队列、TopK问题、中位数问题等
* 优点: 快速获取最大值,快速插入,快速删除
* 缺点: 不能快速获取最小值
*/
class MaxHeap {
heap = []
constructor() {
this.heap = []
}
add(value) {
this.heap.push(value)
this.heapifyUp()
}
// 上浮操作
heapifyUp() {
let index = this.heap.length - 1
while (index > 0) {
let parentIndex = Math.floor((index - 1) / 2)
// 如果当前元素大于父元素,则交换位置
if (this.heap[index] > this.heap[parentIndex]) {
;[this.heap[index], this.heap[parentIndex]] = [this.heap[parentIndex], this.heap[index]]
index = parentIndex
} else {
// 如果当前元素已经小于等于父元素,则停止上浮
break
}
}
}
//取出最大值
remove() {
let value = this.heap[0]
this.heap[0] = this.heap[this.heap.length - 1]
this.heap.pop()
if (!this.isEmpty()) {
this.heapifyDown()
}
return value
}
// 下沉操作
heapifyDown() {
let index = 0
while (index < this.heap.length) {
let leftChildIndex = index * 2 + 1
let rightChildIndex = index * 2 + 2
// 找出当前元素的左右子元素中较大的那个(最大的要放到顶部)
let maxIndex = index
if (leftChildIndex < this.heap.length) {
if (this.heap[leftChildIndex] > this.heap[maxIndex]) {
maxIndex = leftChildIndex
}
}
if (rightChildIndex < this.heap.length) {
if (this.heap[rightChildIndex] > this.heap[maxIndex]) {
maxIndex = rightChildIndex
}
}
if (maxIndex !== index) {
;[this.heap[index], this.heap[maxIndex]] = [this.heap[maxIndex], this.heap[index]]
index = maxIndex
} else {
// 如果当前元素已经大于左右子元素,则停止下沉
break
}
}
}
getMax() {
if (this.isEmpty()) {
return null
}
return this.heap[0]
}
size() {
return this.heap.length
}
isEmpty() {
return this.heap.length === 0
}
}
const maxHeap = new MaxHeap()
maxHeap.add(3)
maxHeap.add(2)
maxHeap.add(1)
maxHeap.add(4)
maxHeap.add(5)
console.log('maxHeap start=============', maxHeap.heap)
console.log(maxHeap.getMax()) // 5
maxHeap.remove()
console.log(maxHeap.getMax()) // 4
maxHeap.remove()
console.log(maxHeap.getMax()) // 3
maxHeap.remove()
console.log(maxHeap.getMax()) // 2
maxHeap.remove()
console.log(maxHeap.getMax()) // 1
maxHeap.remove()
console.log(maxHeap.getMax()) // null
console.log('maxHeap end=============')
三、总结
1.最大堆和最小堆都是基于一维数组实现的数据结构。添加和取出堆顶数据时都需要保证父节点和直接左右子节点的大小关系,所以需要 heapifyUp和heapifyDown操作来维持堆的的结构
2.heapifyUp :添加 时,添加的元素和父元素比较交换直到满足定义
3.heapifyDown :取出堆顶元素 时,新的根节点(最后一个元素)和左右子节点比较交换直到满足定义
4.应用场景 :取最值;前TopK(依次取k次堆顶元素);
/*
希望对你有帮助!
如有错误,欢迎指正,谢谢!
*/