Appearance
堆的插入、删除与建堆
本节实现堆的核心操作:插入、删除和从数组建堆。
插入操作
新元素加到数组末尾,然后向上调整(siftUp):
javascript
class MinHeap {
// ... 基础方法
insert(val) {
this.heap.push(val);
this.siftUp(this.heap.length - 1);
}
siftUp(i) {
while (i > 0) {
const p = this.parent(i);
if (this.heap[p] <= this.heap[i]) break;
this.swap(i, p);
i = p;
}
}
}过程演示:
插入 2 到最小堆 [3, 5, 4, 8, 7]
1. 加到末尾:[3, 5, 4, 8, 7, 2]
2. 2 < 4,交换:[3, 5, 2, 8, 7, 4]
3. 2 < 3,交换:[2, 5, 3, 8, 7, 4]时间复杂度:O(log n)
删除堆顶
把最后一个元素移到堆顶,然后向下调整(siftDown):
javascript
class MinHeap {
// ... 基础方法
extract() {
if (this.heap.length === 0) return undefined;
if (this.heap.length === 1) return this.heap.pop();
const min = this.heap[0];
this.heap[0] = this.heap.pop();
this.siftDown(0);
return min;
}
siftDown(i) {
const n = this.heap.length;
while (this.left(i) < n) {
let smallest = i;
const l = this.left(i);
const r = this.right(i);
if (this.heap[l] < this.heap[smallest]) {
smallest = l;
}
if (r < n && this.heap[r] < this.heap[smallest]) {
smallest = r;
}
if (smallest === i) break;
this.swap(i, smallest);
i = smallest;
}
}
}过程演示:
删除堆顶 [2, 5, 3, 8, 7, 4]
1. 用4替换2:[4, 5, 3, 8, 7]
2. 比较 4, 5, 3,3最小,交换:[3, 5, 4, 8, 7]
3. 4 已是叶子,结束时间复杂度:O(log n)
建堆
从无序数组建堆,有两种方式:
方式一:逐个插入
javascript
function buildHeap1(arr) {
const heap = new MinHeap();
for (const val of arr) {
heap.insert(val);
}
return heap;
}时间复杂度:O(n log n)
方式二:原地建堆(推荐)
从最后一个非叶子节点开始,依次向下调整:
javascript
function buildHeap(arr) {
const heap = new MinHeap();
heap.heap = [...arr];
// 从最后一个非叶子节点开始
const start = Math.floor(arr.length / 2) - 1;
for (let i = start; i >= 0; i--) {
heap.siftDown(i);
}
return heap;
}时间复杂度:O(n)
为什么是 O(n)?因为大部分节点在底层,调整距离很短。
完整实现
javascript
class MinHeap {
constructor(arr = []) {
this.heap = [...arr];
this.heapify();
}
heapify() {
const start = Math.floor(this.heap.length / 2) - 1;
for (let i = start; i >= 0; i--) {
this.siftDown(i);
}
}
parent(i) { return Math.floor((i - 1) / 2); }
left(i) { return 2 * i + 1; }
right(i) { return 2 * i + 2; }
swap(i, j) {
[this.heap[i], this.heap[j]] = [this.heap[j], this.heap[i]];
}
peek() { return this.heap[0]; }
size() { return this.heap.length; }
insert(val) {
this.heap.push(val);
this.siftUp(this.heap.length - 1);
}
siftUp(i) {
while (i > 0 && this.heap[this.parent(i)] > this.heap[i]) {
this.swap(i, this.parent(i));
i = this.parent(i);
}
}
extract() {
if (this.size() === 0) return undefined;
if (this.size() === 1) return this.heap.pop();
const min = this.heap[0];
this.heap[0] = this.heap.pop();
this.siftDown(0);
return min;
}
siftDown(i) {
const n = this.size();
while (this.left(i) < n) {
let smallest = i;
const l = this.left(i), r = this.right(i);
if (this.heap[l] < this.heap[smallest]) smallest = l;
if (r < n && this.heap[r] < this.heap[smallest]) smallest = r;
if (smallest === i) break;
this.swap(i, smallest);
i = smallest;
}
}
}