优先队列的实现比较
实现 | 插入 | 删除 | 查找最大/最小关键字 | 缺点 |
---|---|---|---|---|
数组 | 1 | n | n | - |
链表 | 1 | n | 1 | - |
有序数组 | n 或 logn | n | 1 | - |
有序链表 | n | 1 | 1 | - |
二叉搜索树 | logn | logn | logn | 多次删除后容易导致树倾斜 |
二叉堆 | logn | logn | 1 |
二叉堆
二叉堆的性质
- 节点 k 的父节点下标为 k / 2(向下取整)
- 已某节点为根节点的子树,该节点是这颗树的极值
二叉堆的操作
插入
二叉堆的插入非常简单,只需要在二叉堆的最后添加要插入的内容,并将其“上浮”到正确位置。
尝试在上面的二叉堆中插入新元素9,过程如下:
在尾部插入元素9,与父节点进行对比,有序性被破坏,与父元素替换位置。
替换成功后,继续上一轮操作,与父节点进行对比,仍然无法满足有序性,继续调换位置。
实现
function push(heap: Heap, node: Node): void {
const index = heap.length;
heap.push(node); // 在堆尾部添加元素
siftUp(heap, node, index); // 进行上浮操作
}
function siftUp(heap, node, i) {
let index = i;
while (true) {
const parentIndex = (index - 1) >>> 1; // 父节点位置: parentIndex = childIndex / 2
const parent = heap[parentIndex];
if (parent !== undefined && compare(parent, node) > 0) {
// The parent is larger. Swap positions.
heap[parentIndex] = node;
heap[index] = parent;
index = parentIndex;
} else {
// The parent is smaller. Exit.
return;
}
}
}
删除
取出根节点的值对比插入稍微复杂一点,归纳起来可以分为三步:
- 取出根节点的值。
- 将最后一个元素与根节点进行替换,并删除最后一个元素。
- 下沉;
取出根节点。
将最后一个元素与根节点调换,并删除。对比发现有序性被破坏,进行对调。
完成删除。
程序框架
function pop {
* 设定 minItem 保存根节点
* 取出最后一个节点与根节点替换,并删除最后一个节点
* 执行下沉循环
* 将根元素与左右子节点对比,挑选较小的与父节点替换位置
return minItem
}
实现
export function pop(heap: Heap): Node | null {
const first = heap[0]; // 取出根节点
if (first !== undefined) {
const last = heap.pop(); // 取出最后一位元素,并删除
if (last !== first) {
heap[0] = last; // 与根节点对调
siftDown(heap, last, 0); // 下沉
}
return first;
} else {
return null;
}
}
function siftDown(heap, node, i) {
let index = i;
const length = heap.length;
while (index < length) {
const leftIndex = (index + 1) * 2 - 1;
const left = heap[leftIndex];
const rightIndex = leftIndex + 1;
const right = heap[rightIndex];
// If the left or right node is smaller, swap with the smaller of those.
// 寻找左右儿子较小的那一个替换
if (left !== undefined && compare(left, node) < 0) { //左子节点小于根节点
if (right !== undefined && compare(right, left) < 0) {
heap[index] = right;
heap[rightIndex] = node;
index = rightIndex;
} else {
heap[index] = left;
heap[leftIndex] = node;
index = leftIndex;
}
} else if (right !== undefined && compare(right, node) < 0) { // 左子接点大于根节点,右子节点小于根节点
heap[index] = right;
heap[rightIndex] = node;
index = rightIndex;
} else {
// Neither child is smaller. Exit.
return;
}
}
}
以下是 react 源码中 scheduler/src/SchedulerMinHeap.js
关于最小堆的完整实现:
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
*/
type Heap = Array<Node>;
type Node = {|
id: number,
sortIndex: number,
|};
export function push(heap: Heap, node: Node): void {
const index = heap.length;
heap.push(node);
siftUp(heap, node, index);
}
export function peek(heap: Heap): Node | null {
const first = heap[0];
return first === undefined ? null : first;
}
export function pop(heap: Heap): Node | null {
const first = heap[0];
if (first !== undefined) {
const last = heap.pop();
if (last !== first) {
heap[0] = last;
siftDown(heap, last, 0);
}
return first;
} else {
return null;
}
}
function siftUp(heap, node, i) {
let index = i;
while (true) {
const parentIndex = (index - 1) >>> 1; // 父节点位置: parentIndex = childIndex / 2
const parent = heap[parentIndex];
if (parent !== undefined && compare(parent, node) > 0) {
// The parent is larger. Swap positions.
heap[parentIndex] = node;
heap[index] = parent;
index = parentIndex;
} else {
// The parent is smaller. Exit.
return;
}
}
}
function siftDown(heap, node, i) {
let index = i;
const length = heap.length;
while (index < length) {
const leftIndex = (index + 1) * 2 - 1;
const left = heap[leftIndex];
const rightIndex = leftIndex + 1;
const right = heap[rightIndex];
// If the left or right node is smaller, swap with the smaller of those.
if (left !== undefined && compare(left, node) < 0) { //
if (right !== undefined && compare(right, left) < 0) {
heap[index] = right;
heap[rightIndex] = node;
index = rightIndex;
} else {
heap[index] = left;
heap[leftIndex] = node;
index = leftIndex;
}
} else if (right !== undefined && compare(right, node) < 0) {
heap[index] = right;
heap[rightIndex] = node;
index = rightIndex;
} else {
// Neither child is smaller. Exit.
return;
}
}
}
function compare(a, b) {
// Compare sort index first, then task id.
const diff = a.sortIndex - b.sortIndex;
return diff !== 0 ? diff : a.id - b.id;
}