定义
二叉堆是一个完全二叉树,父节点与子节点要保持固定的序关系,并且每个节点的左子树和右子树都是一个二叉堆。
完全二叉树,即除最后一层节点外,其他层节点都必须要有两个子节点,并且最后一层节点都要左排列。
特性
- 最大堆:根节点的键值是所有堆节点键值中最大者,且每个父节点的值都比子节点的值大
- 最小堆:根节点的键值是所有堆节点键值中最小者,且每个父节点的值都比子节点的值小
思考点
首先需要实现一个 最小堆的class, 初始化一个数组, 用来存放最小堆。 同时, parent 和 child 的关系
- left child: i*2 +1
- right child: i*2 +2,
- parent: Math.floor((i -1) / 2)
最小堆封装
class MinHeap {
constructor() {
this.heap = [];
}
swap(i, j) {
// [this.heap[i], this.heap[j]] = [this.heap[j], this.heap[i]];
const temp = this.heap[i];
this.heap[i] = this.heap[j];
this.heap[j] = temp;
}
/**
* @description 子元素获取父元素节点
* @param {*} i
* @return {*}
*/
getParentIndex(i) {
return (i - 1) >> 1;
}
/**
* @description 父元素获取左节点
* @param {*} i
* @return {*}
*/
getLeftIndex(i) {
return i * 2 + 1;
}
/**
* @description 父元素获取右节点
* @param {*} i
* @return {*}
*/
getRightIndex(i) {
return i * 2 + 2;
}
/**
* @description 元素上移,一直找到最小元素位置
* @param {*} index
* @return {*}
*/
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);
}
}
/**
* @description 元素下移, 当删除元素的时候, 需要用最后的元素覆盖根元素, 然后从上到下移动
* @param {*} index
*/
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)
}
}
/**
* @description 插入元素
* @param {*} value
*/
insert(value) {
this.heap.push(value);
this.shiftup(this.heap.length - 1);
}
/**
* @description 删除元素
*/
pop() {
// 将最后一个元素挪到第一个元素,相当于删除第一个元素(最小堆的最小值)
this.heap[0] = this.heap.pop();
this.shiftDown(0);
}
/**
* @description 获取根元素
* @return {*}
*/
peek() {
return this.heap[0];
}
/**
* @description 当前长度
* @return {*}
*/
size() {
return this.heap.length;
}
}
封装的另一种写法
class MinHeap {
constructor(data = []) {
// 最小堆
this.data = data
}
// 计算长度
size() {
return this.data.length
}
// 比较
compare(a, b) {
return a - b
}
// 交换两个变量的值
swap(index1, index2) {
// [a, b] = [b, a]
;[this.data[index1], this.data[index2]] = [
this.data[index2],
this.data[index1],
]
}
// 获取最小堆的值
peek() {
return this.size() === 0 ? null : this.data[0]
}
// 添加元素
push(node) {
this.data.push(node)
// 调整位置
this.siftUp(node, this.size() - 1)
}
siftUp(node, i) {
let index = i
while (index > 0) {
const parentIndex = (index - 1) >>> 1 // 除以2
const parent = this.data[parentIndex]
if (this.compare(node, parent) < 0) {
// 子节点 < 父节点
this.swap(index, parentIndex)
index = parentIndex
} else {
break
}
}
}
// 删除最小堆的最小值
pop() {
if (this.size() === 0) {
return null
}
const first = this.data[0]
const last = this.data.pop()
// if(first !== last) {
if (this.size() !== 0) {
this.data[0] = last
// 向下调整
this.siftDown(last, 0)
}
}
siftDown(node, i) {
let index = i
const length = this.size()
const halfLength = length >>> 1
while (index < halfLength) {
const leftIndex = (index + 1) * 2 - 1
const rightIndex = leftIndex + 1
const left = this.data[leftIndex]
const right = this.data[rightIndex]
if (this.compare(left, node) < 0) {
// left < 父节点
if (rightIndex < length && this.compare(right, left) < 0) {
// right < left ,right 最小
this.swap(rightIndex, index)
index = rightIndex
} else {
// right >= left,left最小
this.swap(leftIndex, index)
index = leftIndex
}
} else if (rightIndex < length && this.compare(right, node) < 0) {
// left > node, right < node
// right 最小
this.swap(rightIndex, index)
index = rightIndex
} else {
// 根节点最小
break
}
}
}
}