「这是我参与11月更文挑战的第13天,活动详情查看:2021最后一次更文挑战」
此算法思想来源与数据流中第k大的数字。 用传统思想考虑的话,有多少就遍历多少,排序一遍,然后取k-1的位置就是第k大的数字了。这样的时间复杂度很高。
把前k名的元素升序,也不行,我只需要第k名,前面的元素排序对于我们取第k个完全没必要。 我们有没有办法降低复杂度呢?
这个时候我们需要实现一个最小堆。最小堆维护了一个完全二叉树。
完全二叉树:设二叉树的深度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第 h 层所有的结点都连续集中在最左边
实现方法
/**
根据父节点索引算出子节点索引
leftIndex = (parentIndex + 1) * 2 - 1
rightIndex = leftIndex + 1
*/
class MinHeap {
constructor(compare = (a, b) => a -b ) {
this.data = [];
this.compare = compare
}
peek() {
if(this.size() == 0) return null
return this.data[0];
}
size() {
return this.data.length;
}
push(x) {
this.data.push(x);
this.liftUp(this.data.length - 1);
}
swap(i, j) {
[this.data[i], this.data[j]] = [this.data[j], this.data[i]];
}
liftUp(ind) {
while (ind > 0) {
if (this.compare(this.data[ind], this.data[(ind - 1) >> 1])) {
this.swap(ind, (ind - 1) >> 1);
ind = (ind - 1) >> 1;
} else {
break;
}
}
}
pop() {
if(this.size() == 0)return null
const last = this.data.pop();
if (this.size() != 0) {
this.data[0] = last;
this.liftDown(0);
}
return last
}
liftDown(ind) {
// 先和左比
let n = this.data.length;
let halfLen = n >> 1;
while (ind < halfLen) {
let left = (ind + 1) * 2 - 1;
let right = left + 1;
if (ind < n && this.compare(this.data[left], this.data[ind])) {
if (right < n && this.compare(this.data[right], this.data[left])) {
this.swap(ind, right);
ind = right;
} else {
this.swap(ind, left);
ind = left;
}
} else if (right < n && this.compare(this.data[right], this.data[ind])) {
this.swap(ind, right);
ind = right;
} else {
break;
}
}
}
}
到此最小堆的讲述就结束了。
我们如何算数据流中第k大的数字呢?
const KLargest = (k , nums) => {
this.k = k
this.heap = new MinHeap()
for(const num of nums){
this.add(num)
}
}
KLargest.prototype.add = function(node){
// 添加
this.heap.push(node)
//如果heap超过了k 需要边加边删
if(this.heap.size() > this.k){
this.heap.pop()
}
// 返回最小元素
return this.heap.peek()
}