堆
在我看来,js中的堆就是一个比较特殊的数组,我们可以用这个数组中的元素,去生成一个完成的二叉树,同时需要满足,每一个节点的父节点,要大于等于或者小于等于,子节点。
全部大于等于子节点的称之为大顶堆
全部小于等于的为小顶堆
大顶堆
数组: [10,8,6,7,5,4,3]
每一个父节点都会大于等于其子节点,因此为大顶堆
小顶堆
数组: [3,5,4,9,7,6,5]
每一个父节点都会小于等于其子节点,因此为小顶堆
应用
我们可以看一道比较经典的算法题,来理解怎么用js来创建出堆的这种数据结构
这道题目的解法挺多的,可以用sort去排序,也可以用快排去筛选,我们这里就使用小顶堆。
为什么要使用小根堆而不使用大顶堆,这个我们往下看就知道了。
我们既然要使用堆,自然就需要先创建一个堆了。
创建堆
先创建一个堆的类
class Heap {
constructor(arr){
this.heapData = []; //用数组模拟堆
}
}
复制代码
新增一个插入heapData的方法,由于要满足每一个父节点都会大于等于其子节点,
因此还要一个交换的方法
function warp(arr,s1,s2){
//使用结构赋值进行交换
[arr[s2],arr[s1]] = [arr[s1],arr[s2]]
}
复制代码
一个插入的方法
insert(val){
this.heapData.push(val);
let index = this.heapData.length - 1; //获取插入的val在数组中的位置
while(index){
//获取index的父节点
let parent = Math.floor((index-1)/2);
//满足小顶堆的条件,父节点小于等于子节点,停止循环
if(this.heapData[parent] <= this.heapData[index]) {
break
}
//不满足,进行交换
warp(this.heapData,parent,index);
//同时,要让父级继续去和上面的元素进行比较
index = parent;
}
}
复制代码
然后我们在constructor时将传进来的arr,一个个插入,形成一个小顶堆,
创建小顶堆的方法还可以去找到最后一个父节点也就是let i = Math.floor(len/2),然后对比i的子代,如果比它小就进行交换,然后i–,然后继续判断i–的子代,不断重复直到i到达根节点,就结束。
我们继续刚刚的就可以得到如下代码
function warp(arr,s1,s2){
//使用结构赋值进行交换
[arr[s2],arr[s1]] = [arr[s1],arr[s2]]
}
class Heap {
constructor(arr){
this.heapData = []; //用数组模拟堆
//循环插入堆中
arr.forEach((item)=>{
this.insert(item);
})
}
insert(val){
this.heapData.push(val);
let index = this.heapData.length - 1; //获取插入的val在数组中的位置
while(index){
//获取index的父节点
let parent = Math.floor((index-1)/2);
//满足小顶堆的条件,父节点小于等于子节点,停止循环
if(this.heapData[parent] <= this.heapData[index]) {
break
}
//不满足,进行交换
warp(this.heapData,parent,index);
//同时,要让父级继续去和上面的元素进行比较
index = parent;
}
}
}
复制代码
以[3,2,1,5,6,4]
为例传入后可以得到[1,3,2,5,6,4]
由于题目要得是第k大,我们可以从二叉树里面得到的信息有 第一个节点 一定是数组中最小的
因此我们可以截取数组[3,2,1,5,6,4]
的前k位,去形成一个大顶堆。以k = 2为例
可以得到 [2,3]
,2是目前为止最大的,我们继续插入1
由于 1 < 2 ,因为我们要得是第2大的,而1已经比里面的最小的2都要更小了,因此1肯定不是第2大的了
[2,3]
5 由于比2大因此可以替换,
[3,5]
,然后类推就可以得到[5,6]
,由此5就是我们要找的第k大了
因此我们可以得到下面的代码
var findKthLargest = function(nums, k) {
let heap = new Heap(nums.slice(0,k));
for(let i = k;i<nums.length;i++){
//不满足条件直接跳过
if(heap.top()>=nums[i]){
continue;
}
//删除掉堆的根元素,并重新排列
heap.delChange();
//插入满足条件的元素
heap.insert(nums[i])
}
return heap.top();
};
复制代码
delChange如下
delChange(){
//获取最后一个元素位置
let len = this.heapData.length - 1;
let begin = 0;
//让最后一个元素和第一个进行交换,方便删除
warp(this.heapData,begin,len);
//删除最后一个元素
this.heapData.pop();
//获取第一个元素的左节点的index
let changeIndex = 2*begin+1;
//判断index是否超过了数组长度,实际就是判断存不存左节点
while(changeIndex < len){
//获取右节点
let rightIndex = changeIndex+1;
//当右节点存在时,对比左右节点的大小,找到小的那个
if(rightIndex<len && this.heapData[rightIndex]<this.heapData[changeIndex]){
changeIndex = rightIndex;
}
//用左右节点中值小的那个与他们的父节点进行对比,如果比父节点大,终止循环
if(this.heapData[changeIndex] >= this.heapData[begin]){
break
}
//将父节点与比较小的那个子节点,进行交换
warp(this.heapData,begin,changeIndex);
//继续循环判断
begin = changeIndex;
changeIndex = 2*begin+1;
}
}
复制代码
完整代码如下
function warp(arr,s1,s2){
//使用结构赋值进行交换
[arr[s2],arr[s1]] = [arr[s1],arr[s2]]
}
class Heap {
constructor(arr){
this.heapData = []; //用数组模拟堆
//循环插入堆中
arr.forEach((item)=>{
this.insert(item);
})
}
insert(val){
this.heapData.push(val);
let index = this.heapData.length - 1; //获取插入的val在数组中的位置
while(index){
//获取index的父节点
let parent = Math.floor((index-1)/2);
//满足小顶堆的条件,父节点小于等于子节点,停止循环
if(this.heapData[parent] <= this.heapData[index]) {
break
}
//不满足,进行交换
warp(this.heapData,parent,index);
//同时,要让父级继续去和上面的元素进行比较
index = parent;
}
}
delChange(){
//获取最后一个元素位置
let len = this.heapData.length - 1;
let begin = 0;
//让最后一个元素和第一个进行交换,方便删除
warp(this.heapData,begin,len);
//删除最后一个元素
this.heapData.pop();
//获取第一个元素的左节点的index
let changeIndex = 2*begin+1;
//判断index是否超过了数组长度,实际就是判断存不存左节点
while(changeIndex < len){
//获取右节点
let rightIndex = changeIndex+1;
//当右节点存在时,对比左右节点的大小,找到小的那个
if(rightIndex<len && this.heapData[rightIndex]<this.heapData[changeIndex]){
changeIndex = rightIndex;
}
//用左右节点中值小的那个与他们的父节点进行对比,如果比父节点大,终止循环
if(this.heapData[changeIndex] >= this.heapData[begin]){
break
}
//将父节点与比较小的那个子节点,进行交换
warp(this.heapData,begin,changeIndex);
//继续循环判断
begin = changeIndex;
changeIndex = 2*begin+1;
}
}
top(){
return this.heapData[0]
}
}
var findKthLargest = function(nums, k) {
let heap = new Heap(nums.slice(0,k));
for(let i = k;i<nums.length;i++){
//不满足条件直接跳过
if(heap.top()>=nums[i]){
continue;
}
//删除掉堆的根元素,并重新排列
heap.delChange();
//插入满足条件的元素
heap.insert(nums[i])
}
return heap.top();
};
复制代码