@TOC
堆是什么?
堆是一种特殊的完全二叉树
所有的节点都大于等于(最大堆)或小于等于(最小堆)它的子节点
JS中的堆
JS中通常用数组表示堆 左侧子节点的位置是2 * index + 1 右侧子节点的位置是2 * index + 2 父节点位置是( index - 1 ) / 2
堆的应用
堆能高效、快速地找出最大值和最小值,时间复杂度:O(1) 找出第K个最大(最小)元素
第K个最大元素
构建一个最小堆,并将元素依次插入堆中 当堆的容量超过K,就删除堆顶 插入结束后,堆顶就是第K个最大元素
实现步骤 在类里,生命一个数组,用来装元素 主要方法:插入、删除堆顶、获取堆顶、获取堆大小 插入 将值插入堆的底部,即数组的尾部 然后上移:将这个值和它的父节点进行交换,直到父节点小于等于这个插入的值 大小为k的堆中插入元素的时间复杂度为O(logk) 删除堆顶 用数组尾部元素替换堆顶(直接删除堆顶会破坏堆结构) 然后下移:将新堆顶和它的子节点进行交换,直到子节点大于等于这个新堆顶 大小为k的堆中删除堆顶的时间复杂度为O(logk) 获取堆顶和堆的大小 获取堆顶:返回数组的头部 获取堆的大小:返回数组的长度
class MinHeap {
constructor() {
this.heap = [];
}
swap(i1, i2) {
const temp = this.heap[i1];
this.heap[i1] = this.heap[i2];
this.heap[i2] = temp;
}
getParentIndex(i) {
//return Math.floor((i - 1) / 2);
return (i - 1) >> 1;
}
getLeftIndex(i) {
return i * 2 + 1;
}
getRightIndex(i) {
return i * 2 + 2;
}
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);
}
}
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);
}
}
//插入
insert(value) {
this.heap.push(value);
this.shiftUp(this.heap.length - 1);
}
//删除堆顶
pop() {
this.heap[0] = this.heap.pop();
this.shiftDown(0);
}
//获取堆顶
peek() {
return this.heap[0];
}
//获取堆的大小
size() {
return this.heap.length;
}
}
const h = new MinHeap();
h.insert(3);
h.insert(2);
h.insert(1);
h.pop();
LeetCode:215.数组中的第K个最大元素
解题思路
看到“第K个最大元素”
考虑选择使用最小堆
解题步骤
构建一个最小堆,并依次把数组的值插入堆中
当堆的容量超过K,就删除堆顶
插入结束后,堆顶就是第K个最大元素
时间复杂度O(n * logk),空间复杂度O(k)
LeetCode:347.前K个高频元素
法一:
时间复杂度O(n * logn)
法二
class MinHeap {
constructor() {
this.heap = [];
}
swap(i1, i2) {
const temp = this.heap[i1];
this.heap[i1] = this.heap[i2];
this.heap[i2] = temp;
}
getParentIndex(i) {
//return Math.floor((i - 1) / 2);
return (i - 1) >> 1;
}
getLeftIndex(i) {
return i * 2 + 1;
}
getRightIndex(i) {
return i * 2 + 2;
}
shiftUp(index) {
if (index == 0) { return; }
const parentIndex = this.getParentIndex(index);
if (this.heap[parentIndex] && this.heap[parentIndex].value > this.heap[index].value) {
this.swap(parentIndex, index);
this.shiftUp(parentIndex);
}
}
shiftDown(index) {
const leftIndex = this.getLeftIndex(index);
const rightIndex = this.getRightIndex(index);
if (this.heap[leftIndex] && this.heap[leftIndex].value < this.heap[index].value) {
this.swap(leftIndex, index);
this.shiftDown(leftIndex);
}
if (this.heap[rightIndex] && this.heap[rightIndex].value < this.heap[index].value) {
this.swap(rightIndex, index);
this.shiftDown(rightIndex);
}
}
//插入
insert(value) {
this.heap.push(value);
this.shiftUp(this.heap.length - 1);
}
//删除堆顶
pop() {
this.heap[0] = this.heap.pop();
this.shiftDown(0);
}
//获取堆顶
peek() {
return this.heap[0];
}
//获取堆的大小
size() {
return this.heap.length;
}
}
时间O(n * logk),空间O(n)
LeetCode:23.合并K个排序链表
解题思路
新链表的下一个节点一定是k个链表头中的最小节点
考虑选择使用最小堆
解题步骤
构建一个最小堆,并依次把链表头插入堆中
弹出堆顶接到输出链表,并将堆顶所在链表的新链表头插入堆中
等堆元素全部弹出,合并工作就完成了
class MinHeap {
constructor() {
this.heap = [];
}
swap(i1, i2) {
const temp = this.heap[i1];
this.heap[i1] = this.heap[i2];
this.heap[i2] = temp;
}
getParentIndex(i) {
//return Math.floor((i - 1) / 2);
return (i - 1) >> 1;
}
getLeftIndex(i) {
return i * 2 + 1;
}
getRightIndex(i) {
return i * 2 + 2;
}
shiftUp(index) {
if (index == 0) { return; }
const parentIndex = this.getParentIndex(index);
if (this.heap[parentIndex] && this.heap[parentIndex].val> this.heap[index].val) {
this.swap(parentIndex, index);
this.shiftUp(parentIndex);
}
}
shiftDown(index) {
const leftIndex = this.getLeftIndex(index);
const rightIndex = this.getRightIndex(index);
if (this.heap[leftIndex] && this.heap[leftIndex].val< this.heap[index].val) {
this.swap(leftIndex, index);
this.shiftDown(leftIndex);
}
if (this.heap[rightIndex] && this.heap[rightIndex].val< this.heap[index].val) {
this.swap(rightIndex, index);
this.shiftDown(rightIndex);
}
}
//插入
insert(value) {
this.heap.push(value);
this.shiftUp(this.heap.length - 1);
}
//删除堆顶
pop() {
if(this.size() === 1) return this.heap.shift();
const top = this.heap[0]
this.heap[0] = this.heap.pop();
this.shiftDown(0);
return top;
}
//获取堆顶
peek() {
return this.heap[0];
}
//获取堆的大小
size() {
return this.heap.length;
}
}
时间复杂度O(n * logk),n是所有链表的节点数之和,k是排序链表数,空间复杂度是O(k)
思考题
1、请用堆画出一场比赛的运动员排名情况,无需 Coding。 2、在 LeetCode 找到一道堆的题目,并通过测试