实现最小堆:
const Compare = {
LESS_THAN: -1,
BIGGER_THAN: 1,
};
//默认比较函数
function defalutCompare(a, b) {
if (a === b) {
return 0;
}
return a > b ? Compare.BIGGER_THAN : Compare.LESS_THAN;
}
class MinHeap {
//当堆的节点是引用类型时,可以传入自定义的compareFn作为比较函数来判断节点之间的大小
constructor(compareFn = defalutCompare) {
this.compareFn = compareFn;
this.heap = []; //用数组存储堆元素
}
//取左子节点下标
getLeftIndex(index) {
return 2 * index + 1;
}
//取右子节点下标
getRightIndex(index) {
return 2 * index + 2;
}
//取父节点下标
getParentIndex(index) {
if (index === 0) {
return undefined;
}
//如果当前节点不是根节点:
return Math.floor((index - 1) / 2); //因为下标从0开始,所以index要减1,//因为下标从0开始,所以index要减1,奇数除以2会有余数,所以要向下取整
}
//插入节点
insert(value) {
if (value != null) {
this.heap.push(value); //添加到数组末尾
this.siftUp(this.heap.length - 1); //调整堆序性
return true;
}
return false;
}
//插入节点后调整堆序性
siftUp(index) {
let parent = this.getParentIndex(index);
while (
index > 0 &&
this.compareFn(this.heap[parent], this.heap[index]) ===
Compare.BIGGER_THAN
) {
//交换当前节点与其父节点,利用es6的数组解构语法
[this.heap[parent], this.heap[index]] = [
this.heap[index],
this.heap[parent],
]; //交换当前节点与其父节点 此语法的性能要低于正常的交换赋值算法
index = parent;
parent = this.getParentIndex(index);
}
}
//返回最小值
findMininum() {
return this.heap.length === 0 ? undefined : this.heap[0];
}
//删除最小值,返回被删除的值
extract() {
if (this.heap.length === 0) {
return undefined;
}
if (this.heap.length === 1) {
return this.heap.shift(); //shift方法删除原数组的一个元素,并返回删除元素
}
const removedValue = this.heap[0];
this.heap[0] = this.heap[this.heap.length - 1]; //将数组的最后一个元素赋值给第一个元素
this.heap.pop(); //删除最后一个元素
this.siftDown(0); //删除后调整堆序性
return removedValue;
}
//删除最小值后,调整堆序性
siftDown(index) {
let element = index; //最后一个元素的当前下标
const left = this.getLeftIndex(element);
const right = this.getRightIndex(element);
let child = left;
//求最小子节点下标
if (right < this.heap.length) {
child =
this.compareFn(this.heap[left], this.heap[right]) === Compare.LESS_THAN
? left
: right;
}
if (
child < this.heap.length &&
this.compareFn(this.heap[element], this.heap[child]) ===
Compare.BIGGER_THAN
) {
element = child;
}
if (element != index) {
//如果当前节点有小于它的子节点,就交换其位置
[this.heap[element], this.heap[index]] = [
this.heap[index],
this.heap[element],
];
this.siftDown(element);
}
}
}
测试代码与结果:
const heap = new MinHeap();
heap.insert(2);
heap.insert(3);
heap.insert(4);
heap.insert(5);
heap.insert(9);
console.log([...heap.heap]);
heap.extract();
console.log([...heap.heap]);
堆排序:
//堆排序:先将待排序记录构造成堆,然后不断从堆中选取最值,完成排序。
function heapSort(arr, compareFn = defalutCompare) {
let heapSize = arr.length;
buildMinHeap(arr, compareFn); //将arr构造为最小堆
while (heapSize > 1) {
//交换最大节点与最小节点
[arr[0], arr[heapSize - 1]] = [arr[heapSize - 1], arr[0]];
//待排序的元素数量-1
heapSize--;
//交换节点后,调整堆序性.需要传入heapSize,因为每次排序时待排序元素都在减少
heapify(arr, 0, heapSize, compareFn);
}
return arr;
}
function buildMinHeap(arr, compareFn) {
//从下标为前半部分元素开始从下往上构造堆,因为后半部分元素一定为前半部分元素的子节点,当前半部分元素满足堆序性时,整颗树也就满足
for (let i = Math.floor(arr.length / 2); i >= 0; i--) {
heapify(arr, i, arr.length, compareFn);
}
return arr;
}
function heapify(arr, index, length, compareFn = defalutCompare) {
let element = index;
let left = element * 2 + 1;
let right = element * 2 + 2;
let child = left;
if (
right < length &&
compareFn(arr[left], arr[right]) === Compare.BIGGER_THAN
) {
child = right;
}
if (
child < length &&
compareFn(arr[child], arr[element]) === Compare.LESS_THAN
) {
element = child;
[arr[element], arr[index]] = [arr[index], arr[element]];
heapify(arr, element, length);
}
}
测试代码与结果:
const array = [7, 6, 3, 5, 4, 1, 2];
console.log(heapSort(array));