堆
定义
- 堆是完全二叉树
- 堆的每一个节点都大于等于(或者小于等于)子树每一个节点的值
大顶堆和小顶堆
大顶堆:每个节点的值都大于等于子树中每个节点值的堆
小顶堆:每个节点的值都小于等于子树中每个节点值的堆

堆的实现
如图,由于堆是一个完全二叉树,因此堆可以通过数组来实现。

当堆通过数组来实现时,我们就可以通过索引来快速获取它的左右节点。比如当节点i的索引为k时,它的左子树的索引为2k,右子树的索引为2k+1,它的父节点的索引为k/2.
堆的插入
// 用于存储堆中的元素
private val heapArray = mutableListOf<Int>()
// 插入元素方法
fun insert(value: Int) {
// 将元素添加到堆末尾
heapArray.add(value)
var index = heapArray.size - 1
// 从下往上调整堆,以满足堆的性质
while (index > 0) {
// 计算当前节点的父节点索引
val parentIndex = (index - 1) / 2
// 如果是大顶堆且当前节点小于等于父节点,或者是小顶堆且当前节点大于等于父节点,则停止调整
if ((isMaxHeap && heapArray[index] <= heapArray[parentIndex]) ||
(!isMaxHeap && heapArray[index] >= heapArray[parentIndex])
) {
break
}
// 交换当前节点和父节点的值
val temp = heapArray[index]
heapArray[index] = heapArray[parentIndex]
heapArray[parentIndex] = temp
// 更新当前节点索引为父节点索引,继续向上调整
index = parentIndex
}
}
堆的插入代码如上所示。可以看到,我们首先会将新元素添加到堆的末尾。然后从新元素所在的位置开始,向上与父节点比较,如果不满足堆的性质(大顶堆中当前节点小于父节点或小顶堆中当前节点大于父节点),则交换当前节点和父节点的值,并继续向上调整,直到满足堆的性质或者到达根节点。
堆的删除
// 用于存储堆中的元素
private val heapArray = mutableListOf<Int>()
// 删除堆顶元素方法
fun delete(): Int? {
// 如果堆为空,返回 null
if (heapArray.isEmpty()) {
return null
}
// 保存堆顶元素的值
val rootValue = heapArray[0]
// 将堆尾元素移到堆顶
heapArray[0] = heapArray.last()
// 删除堆尾元素
heapArray.removeAt(heapArray.size - 1)
var index = 0
// 从堆顶开始往下调整堆,以满足堆的性质
while (true) {
// 计算左子节点索引
val leftChildIndex = 2 * index + 1
// 计算右子节点索引
val rightChildIndex = 2 * index + 2
var targetIndex = index
// 如果左子节点存在
if (leftChildIndex < heapArray.size) {
// 根据堆的类型(大顶堆或小顶堆)确定目标索引
targetIndex = if ((isMaxHeap && heapArray[leftChildIndex] > heapArray[targetIndex]) ||
(!isMaxHeap && heapArray[leftChildIndex] < heapArray[targetIndex])
) {
leftChildIndex
} else {
targetIndex
}
}
// 如果右子节点存在
if (rightChildIndex < heapArray.size) {
// 根据堆的类型(大顶堆或小顶堆)确定目标索引
targetIndex = if ((isMaxHeap && heapArray[rightChildIndex] > heapArray[targetIndex]) ||
(!isMaxHeap && heapArray[rightChildIndex] < heapArray[targetIndex])
) {
rightChildIndex
} else {
targetIndex
}
}
// 如果目标索引没有变化,说明堆已满足性质,停止调整
if (targetIndex == index) {
break
}
// 交换当前节点和目标节点的值
val temp = heapArray[index]
heapArray[index] = heapArray[targetIndex]
heapArray[targetIndex] = temp
// 更新当前节点索引为目标节点索引,继续向下调整
index = targetIndex
}
// 返回删除的堆顶元素值
return rootValue
}
堆的删除代码如上所示。可以看到堆删除元素的流程为:
- 如果堆为空,返回
null。 - 保存堆顶元素的值。
- 将堆的最后一个元素移到堆顶位置。
- 删除堆的最后一个元素。
- 从堆顶开始向下调整堆,首先计算左右子节点的索引。
- 根据堆的类型(大顶堆或小顶堆)确定当前节点与其左右子节点中的最小(或最大)值所在的索引作为目标索引。
- 如果目标索引与当前节点索引不同,则交换当前节点和目标节点的值,并继续向下调整,直到满足堆的性质或者到达叶子节点。
- 最后返回删除的堆顶元素值。