堆
堆(heap)是计算机科学中一类特殊的数据结构统称。通常是一个可被看做一棵树的数组对象。
当一个数据结构满足:
- 任意节点 P 和 C,若 P 是 C 的父节点,那么一定有 P 的值小于(或大于)C 的值。
- 总是一棵完全二叉树
则称这个数据结构为最小堆(最大堆)。
堆是一个非线性结构,即一维数组有两个直接后继。
不难看出,上图中堆的数据结构,可用数组模拟,既:
// 最小堆
const minHeap = [
10, 11, 12, 13,
14, 15, 16
];
// 最大堆
const maxHeap = [
10, 9, 8, 7,
6, 5, 4
]
root 节点子节点索引分别为1, 2
,而索引等于 1 的节点,其子节点所以则是3, 4
,由此推得:父节点为 i 时,子节点的索引分别为:
2 * i + 1, 2 * i + 2
。
子节点索引为 j 时,父节点索引 i 为:
i = Math.floor(j / 2)
堆的操作
首先,初始化一个最小堆:
class MinHeap {
constructor() {
this.heap = []
}
getMin() {
return this.heap[0]
}
getParentNode(index) {
return
this.heap[Math.floor(index / 2)]
}
swap(indx1, indx2) {
const temp = this.heap[indx1]
this.heap[indx1] = this.heap[indx2]
this.heap[indx2] = temp
}
}
插入数据
- heap 为空时,键值
10
作为根节点 - 23 > 10,可作为 10 的 子节点
- 36 > 10,可作为 10 的 子节点
- 18 < 23,不可作为 23 的子节点,调换 23 与 18 的位置。18 > 23 符合条件,构造完成
代码实现如下:
...
insert(node) {
this.heap.push(node)
if(this.heap.length > 1) {
let currentIdx = this.heap.length - 1
// 依次向上查找
while(currentIdx > 1 &&
this.getParentNode(node) >
this.heap[currentIdx]) {
// 最小堆子节点大于父节点时,需互换父子节点
const parentIndex = Math.floor(currentIndex / 2)
this.swap(parentIndex,currentIndex)
currentIndex = parentIndex
}
}
}
...
删除数据
最小堆删除根节点时,将最后一位移动至根节点,依次比较根节点与子节点大小,若根节点大于子节点,则调换位置,重复至比较完成。
...
remove() {
let min = this.getMin()
if(this.heap.length === 2) {
this.heap.splice(1, 1)
} else if (this.heap.length > 2){
// 最后一位移动至根节点
const len = this.heap.length
this.heap[1] = this.heap[len - 1]
this.heap.splice(len - 1)
if (len === 3) {
if(this.heap[1] > this.heap[2]) {
// 交换位置函数
this.swap(1, 2)
}
return min
}
let currentIndex = 1
// 首位是null,所以不加1
let leftChildIndex = currentIndex * 2
let rightChildIndex = currentIndex * 2 + 1
while(
this.heap[leftChildIndex] &&
this.heap[rightChildIndex] &&
(this.heap[currentIndex] > this.heap[leftChildIndex] ||
this.heap[currentIndex] > this.heap[rightChildIndex])
) {
if(this.heap[leftChildIndex] < this.heap[rightChildIndex]) {
this.swap(currentIndex, leftChildIndex)
} else {
this.swap(currentIndex, rightChildIndex)
}
leftChildIndex = currentIndex * 2
rightChildIndex = currentIndex * 2 + 1
}
} else {
return null
}
}
...
总的来说堆的插入与删除,主要与子节点与父节点索引关系相关,大家多理解一下上方的推算。
建堆效率
n 个节点的堆,其高度为 h=logn
。根为第 0 层,第 i 层节点个数为 2^i
,由于堆高度与节点数目相关,因此插入节点,删除普通元素,删除最小元素的平均时间复杂度都为O(logn)
。
不要问数据结构在前端有什么用,学计算机的同学去了解基本算法与数据结构难道不是应该的吗。
我不能理解一些外企用“算法”定义一个人业务能力好坏的方式,但也不认为写代码的人理解学习基础知识是“卷”。
所以放下对算法的偏见,这只是一种思考方式而已。