定义
堆是一种树形结构。堆总是满足下列性质:
- 堆中某个节点的值总是不大于或不小于其父节点的值;
- 堆总是一棵完全二叉树。
将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆
堆的插入
- 将值插入到最后一个节点
- 将节点值和根节点进行比较,不符合(最大/小堆规则的进行交换,依次,直到栈顶或者符合规则)
插入时间复杂度 O(logn) 即树的高度
// 插入 数字 5 // 把5 放入最后节点之后,和根 6比较,小于根,需要交换
1 1 1
/ \ / \ / \
3 6 ===> 3 6 ===> 3 5
/ \ / \ / \ / \ / \ / \
4 8 7 4 8 7 5 4 8 7 6
堆的取值
由于最大/小堆的特性,堆顶一定是最大/小值,直接获取最大/小值是O(1),取值之后,堆的结构需要调整。调整步骤
- 把最后一个节点放到堆顶
- 把堆顶和左右节点比较,是否符合规则,不符合进行交换
取值之后堆的调整:时间复杂度 O(logn) 即树的高度
// 获取堆顶 1 // 把最后一个元素 7 放到堆顶,然后进行比较
1 7 3 3
/ \ / \ / \ / \
3 6 ===> 3 6 ===> 7 6 ===> 4 6
/ \ / \ / \ / \ / \ / \ / \ / \
4 8 7 4 8 4 8 7 8
根据数组 [1, 2, 3, 4, 5] 建立一个堆最大堆的过程就是一个一个的插入
// 初始 // 插入2,交换,依次
1 1 2
==> / \ ==> / \
2 1
代码如下:
const createHeap = (nums) => {
const result = { parent: null, val: nums[0], left: null, right: null }
const queue = [result]
for (let i = 1; i < nums.length; i++) {
const tail = queue[0]
let node = { parent: tail, val: nums[i], left: null, right: null }
if (!tail.left) {
tail.left = node
} else if (!tail.right) {
tail.right = node
}
while(node && node.parent && node.val > node.parent.val) {
const temp = node.val
node.val = node.parent.val
node.parent.val = temp
node = node.parent
}
if (tail.left && tail.right) {
queue.shift()
queue.push(tail.left)
queue.push(tail.right)
}
}
return result
}
用数组去模拟堆的格式
const createHeapByArray = (nums) => {
const result = []
for (let i = 0; i < nums.length; i++) {
result.push(nums[i])
let currentPosition = i
let parentPoisition = (currentPosition - 1) >> 1
while (currentPosition > 0 && result[currentPosition] > result[parentPoisition]) {
const temp = result[currentPosition]
result[currentPosition] = result[parentPoisition]
result[parentPoisition] = temp
currentPosition = parentPoisition
parentPoisition = (currentPosition - 1) >> 1
}
}
return result
}
console.log(createHeapByArray([1, 2, 3, 4, 5, 6, 7, 8])) // [8, 7, 6, 4, 3, 2, 5, 1]
抽象一个最大堆的类MaxHeap
class MaxHeap {
constructor(nums) {
this.data = []
this.init(nums)
}
init (nums) {
for (let i = 0; i < nums.length; i++) {
this.insert(nums[i])
}
}
swap (index1, index2) {
const temp = this.data[index1]
this.data[index1] = this.data[index2]
this.data[index2] = temp
}
insert (num) {
this.data.push(num)
let currentPosition = this.data.length - 1
let parentPoisition = (currentPosition - 1) >> 1
while (currentPosition > 0 && this.data[currentPosition] > this.data[parentPoisition]) {
this.swap(currentPosition, parentPoisition)
currentPosition = parentPoisition
parentPoisition = (currentPosition - 1) >> 1
}
return true
}
remove () {
if (this.data.length === 0) {
return null
}
const top = this.data[0]
if (this.data.length > 1) {
this.data[0] = this.data.pop()
} else {
this.data.pop()
}
let parentPoisition = 0
let leftIndex = 2 * parentPoisition + 1
let rightIndex = leftIndex + 1
while (leftIndex < this.data.length) {
let maxChildIndex = leftIndex
if (rightIndex < this.data.length && this.data[leftIndex] < this.data[rightIndex]) {
maxChildIndex = rightIndex
}
if (this.data[parentPoisition] >= this.data[maxChildIndex]) {
break
}
this.swap(parentPoisition, maxChildIndex)
parentPoisition = maxChildIndex
leftIndex = 2 * parentPoisition + 1
rightIndex = leftIndex + 1
}
return top
}
}
堆排序
/**
* 堆排序调整
* 从index开始往下调整,index节点和子节点是否满足堆条件
* 一直往下,一直到符合条件/堆结尾
*/
const shift = (nums, index, size) => {
let parentIndex = index
let childIndex = parentIndex * 2 + 1
while(childIndex < size) {
if (childIndex + 1 < size && nums[childIndex + 1] > nums[childIndex]) {
childIndex = childIndex + 1
}
if (nums[parentIndex] >= nums[childIndex]) {
break
}
const temp = nums[parentIndex]
nums[parentIndex] = nums[childIndex]
nums[childIndex] = temp
parentIndex = childIndex
childIndex = parentIndex * 2 + 1
}
return nums
}
/**
* 堆排序流程:
* 1. 调整当前的数组,把当前的数组当成一个完全二叉树,从第一个非叶子节点,开始调整成堆
* 一步一步直到 0,当前的nums就是一个堆
* 2. 取出堆顶的元素,放到最右,然后继续,放到最右之后,不影响堆调整的下标计算,放到最左,会有偏移
* 因为堆顶是放到最右,如果从小到大排序,构建最大堆,反之最小堆
*
* 时间复杂度: O(nlogn)
* 空间复杂度: O(1)
* 不稳定排序
*/
const heapSort = (nums) => {
// 调整成堆
for (let i = Math.floor((nums.length - 1) / 2); i >= 0; i--) {
shift(nums, i, nums.length)
}
// 循环取堆顶,放到最右
for (let i = nums.length - 1; i >= 1; i--) {
const temp = nums[i]
nums[i] = nums[0]
nums[0] = temp
// 调整堆
shift(nums, 0, i)
}
return nums
}
leetcode练习题: