这是我参与11月更文挑战的第7天,活动详情查看:2021最后一次更文挑战
二叉堆和堆排序
今天我们学一个特殊的二叉树,就是我们的堆数据结构,也叫做二叉堆,他能高效的、快速的找出最大最小值,常被用于优先队列,也用于堆排序算法中。
这一节的内容包括二叉堆数据结构、最大和最小堆、堆排序算法。
二叉堆数据结构
二叉堆是一种特殊的二叉树,有以下两个特性:
- 他是一颗完全二叉树,表示树的每一层都有左侧和右侧子节点(除了最后一层的叶子节点),并且最后一层的叶节点尽量是左侧节点,这叫做结构特性
- 二叉堆不是最小堆就是最大堆,最小堆允许你快速找到树的最小值,最大堆允许你快速找到树的最大值。所有的节点都大于等于(最大堆)或小于等于(最小堆)每个他的子节点,这叫做堆特性。
实现最小堆
先来定义我们堆的数据结构,他同样需要compareFn函数来比较两个数的值,还需要heap属性来存储我们的堆元素。
const defaultCompare = (a, b) => a > b
class MinHeap {
constructor(compareFn = defaultCompare) {
this.heap = [];
this.compareFn = compareFn;
}
}
在这里我们用数组来表示二叉堆,通过索引值来检测父节点、左侧和右侧子节点的值,而对于给定index的节点,他的左侧子节点的索引时2 * index + 1,右侧是2 * index + 2,父节点是Math.floor((index - 1) / 2),所以我们可以得到以下函数。
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)
}
接下来我们来实现最小堆需要的几个基本方法
- insert(value):像堆中插入一个值,成功返回true,否则返回false
- extract():移除最小值(最小堆)或最大值(最大堆),并返回该值
- findMininum():返回最小值(最小堆)或最大值(最大堆),不移除该值
insert
实现步骤:
- value 不合法时返回false;合法时插入到数组最后,然后上移
- 新插入的值(数组最后一个值)与其父节点对比,要是小于父节点,则二者交换,循环下去,直到大与父节点或者index超出了数组索引。具体实现如下:
insert(value) {
// value 合法的时候插入,否则插入不成功返回 false
if (value != null) {
// 先直接插入到最后,
this.heap.push(value);
// 然后进行上移操作9(与父节点比较,小与父节点则交换)
this.siftUp(this.heap.length - 1)
return true;
}
return false
}
siftUp(index) {
// 获取父节点,与之比较,小于则交换,直到大于父节点或超出数组为止
let parent = this.getParentIndex(index);
// while(index > 0 && this.heap[parent] > this.heap[index]) {
while (index > 0 && this.compareFn(this.heap[parent], this.heap[index])) {
[this.heap[parent], this.heap[index]] = [this.heap[index], this.heap[parent]]
index = parent;
parent = this.getParentIndex(index);
}
}
写个例子测一下
const minHeap = new MinHeap()
minHeap.insert(2)
minHeap.insert(3)
minHeap.insert(4)
minHeap.insert(5)
minHeap.insert(1)
console.log(minHeap); // { heap: [ 1, 2, 4, 5, 3 ] }
findMininum
在最小堆中,最小值永远在数组的第一个值,所以函数实现如下:
size() {
return this.heap.length;
}
isEmpty() {
return this.size() === 0
}
findMininum() {
// 或者直接返回 this.heap[0];
return this.isEmpty() ? undefined : this.heap[0];
}
extract()
extract()函数用来移除最小值(最小堆)或最大值(最大堆),并返回该值。这也是我们接下来要实现的方法。
实现步骤:
- 堆为空时返回undefined;只有一个元素时,删除第一个元素并返回
- 多个元素时,删除第一个元素并返回,然后下移重新构建最小堆
- 删除最小元素后,将第一个元素作为堆顶,对比他的子节点
- 分别比较左右节点,找到他比他小的最小的值,然后交换,
- 循环第二步
extract() {
if (this.isEmpty()) return undefined;
if (this.size() === 1) return this.heap.shift();
const removeVal = this.heap.shift();
this.siftDown(0);
return removeVal;
}
siftDown(index) {
let element = index;
const left = this.getLeftIndex(index)
const right = this.getRightIndex(index);
const size = this.size()
if (left < size && this.compareFn(this.heap[element], this.heap[left])) {
element = left;
}
if (right < size && this.compareFn(this.heap[element], this.heap[right])) {
element = right;
}
if (index !== element) {
[this.heap[index], this.heap[element]] = [this.heap[element], this.heap[index]]
this.siftDown(element);
}
}
实现最大堆
最大堆和最小堆的算法一模一样,不同的是要把所有的大于换成小于的比较。
const reverseCompare = compareFn => (a, b) => compareFn(b, a)
class MaxHeap extends MinHeap {
constructor(compareFn) {
super(compareFn);
this.compareFn = reverseCompare(compareFn);
}
}
测试用例
const maxHeap = new MaxHeap()
maxHeap.insert(2)
maxHeap.insert(3)
maxHeap.insert(4)
maxHeap.insert(5)
maxHeap.insert(1)
console.log(maxHeap); // { heap: [ 5, 4, 3, 2, 1 ]
console.log(maxHeap.findMininum()); // 5