1、介绍
2、js实现最小堆类
- 首先新建一个堆类
// 新建一个最小堆类
class MinHeap {
constructor() {
this.heap = [];
}
}
- 写
插入堆的方法 - 也需要理清楚 思路先
1、想写insert()方法 先把插入值value 放入到heap中
2、写上移方法 shiftUp 拿到父节点 考虑堆顶 index==0情况
3、需要拿到当前父节点位置 getParentIndex
4、需要写一个 swrap()方法 父节点 > 当前节点 两个值交换
-
需要特别注意 构造函数 this.heap = [] 不能写成 heap()
-
数组索引方式使用数组 应该用 heap[xxx] 中括号
-
插入方法 需要注意 this.heap.push(value);
-
this.shiftUp(this.heap.length - 1); 这两个不能少
// 新建一个最小堆类
class MinHeap {
constructor() {
this.heap = []; //特别注意
}
//交换方法
swrap(i1, i2) {
var temp = this.heap[i1];
this.heap[i1] = this.heap[i2];
this.heap[i2] = temp;
}
// 拿到父节点
getParentIndex(i) {
// 父节点的位置 (i-1)/2
// return Math.floor((i-1)/2)
// 二进制右移 操作 一位操作 取商 看起来更加简洁
return (i - 1) >> 1;
}
shiftUp(index) {
// 当前位置在堆顶 不再操作
if (index == 0) {
return;
}
const parentIndex = this.getParentIndex(index);
//对比当前节点和上一节点的值 如果当前节点小于上一节点 交换
if (this.heap[parentIndex] > this.heap[index]) {
//执行swrap 交换函数
this.swrap(parentIndex, index);
//持续执行上移操作 由于当前已经交换过了 所以传入 parentIndex
this.shiftUp(parentIndex);
}
}
// 插入方法
insert(value) {
this.heap.push(value);
this.shiftUp(this.heap.length - 1);
console.log(this.heap);
}
}
// 测试
const h = new MinHeap();
h.insert(3);
h.insert(2);
h.insert(1);
-
我们可以使用 nodejs查看测试结果
-
这样 后一个 确保了
顶点节点是最小的节点 -
插入节点操作完成
4、删除堆顶
- 先理清一下 思路 避免比较懵逼
1、新建pop()方法
2、新建shiftDown 下移方法
3、得到左右节点 getLeftIndex getRightIndex
4、比较左节点和当前节点值及右节点与当前节点值
// 新建一个最小堆类
class MinHeap {
constructor() {
this.heap = [];
}
//交换方法
swrap(i1, i2) {
var temp = this.heap[i1];
this.heap[i1] = this.heap[i2];
this.heap[i2] = temp;
}
// 拿到父节点
getParentIndex(i) {
// 父节点的位置 (i-1)/2
// return Math.floor((i-1)/2)
// 二进制右移 操作 一位操作 取商 看起来更加简洁
return (i - 1) >> 1;
}
// 获取左节点
getLeftIndex(i) {
return 2 * i + 1;
}
//获取右节点
getRightIndex(i) {
return 2 * i + 2;
}
shiftUp(index) {
// 当前位置在堆顶 不再操作
if (index == 0) {
return;
}
const parentIndex = this.getParentIndex(index);
//对比当前节点和上一节点的值 如果当前节点小于上一节点 交换
if (this.heap[parentIndex] > this.heap[index]) {
//执行swrap 交换函数
this.swrap(parentIndex, index);
//持续执行上移操作 由于当前已经交换过了 所以传入 parentIndex
this.shiftUp(parentIndex);
}
}
//定义 下移函数
shiftDown(index) {
// 获取左右节点
const leftIndex = this.getLeftIndex(index);
const rightIndex = this.getRightIndex(index);
//比较左节点和当前节点值及右节点与当前节点值
if (this.heap[leftIndex] < this.heap[index]) {
this.swrap(leftIndex, index);
this.shiftDown(leftIndex);
}
if (this.heap[rightIndex] < this.heap[index]) {
this.swrap(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); //执行下移 函数
console.log(this.heap);
}
}
// test insert()
const h = new MinHeap();
h.insert(3);
h.insert(2);
h.insert(1);
//test pop()
h.pop();
- 结果
5、获取 堆顶和 堆的大小
- 这个地方很清楚明确 只展示 部分代码
// 获取堆顶 获取数组头部
peek(){
return this.heap[0]
}
//获取堆的大小 返回数组的长度
size(){
return this.heap.length
}
- 结果 比较详细的展示出来
6、leetcode 215 数组中第K个最大元素
- 来来来 上面的逻辑比较简单 我们理清楚后实战一下吧
//当然 这前面省略了 最小堆构建的过程
// 时间复杂度 O(nlogk) 空间复杂度主要是堆 O(k)
var findKthLargest = function(nums, k) {
// 实例化最小堆
const h = new MinHeap()
//循环遍历并将数组值插入堆中
nums.forEach(n=>{
h.insert(n)
//如果容量大于k 删除堆顶
if(h.size() > k){
h.pop()
}
})
// 返回堆顶
return h.peek()
};
7 、leetcode 317 前k个高频元素
- 当然 此处代码 省略了 构建 MinHeap
/*
* 时间复杂度 O(nlogk) 空间复杂度 O(n) 因为字典可能是O(n) 堆是O(k) 两者相加为O(n)
*/
var topKFrequent = function(nums, k) {
// 1、新建字典
const map =new Map()
// 2、统计每个元素出现的频率
nums.forEach(n=>{
map.set(n,map.has(n) ? map.get(n)+1:1)
})
console.log(map) //Map(3) { 1 => 3, 2 => 2, 3 => 1 }
// 3、实例化最小堆
const h = new MinHeap()
//遍历堆数值和次数
map.forEach((value,key)=>{
h.insert({value,key})
//当容量大于k 删除堆顶
if(h.size()>k){
h.pop()
}
})
return h.heap.map(a=>a.key)
};
- 需要注意
由于 下面 插入堆中的结构发生了 变化 相应的 堆也要变化 - 也要确保 类型 这样的this.heap[parentIndex] 有值
8、LeetCode:23. 合并K个升序链表
- 改造一下最小堆
- 当然 此处代码 省略了 构建 MinHeap
/**
* @param {ListNode[]} lists
* @return {ListNode}
* 时间复杂度 O(nlogk) 循环有内层循环 pop()和insert()
* 空间复杂度 O(k) k表示链表
*/
var mergeKLists = function(lists) {
// 1、新建链表
const res = new ListNode(0)
// 2、实例化最小堆
const h = new MinHeap()
// 3、新建一个指针 指向链表
let p = res
// 循环遍历 将链表放入堆中
lists.forEach(l =>{
if(l) h.insert(l)
})
// 堆里有值时 循环
while(h.size()){
// 弹出堆顶
const n = h.pop()
// 将弹出的堆顶接到 输出链表
p.next = n
//指针向下移动
p = p.next
// 放入堆中
if(n.next) h.insert(n.next)
}
return res.next
};
9、总结一下