[路飞]-最小堆MinHeap

516 阅读1分钟

「这是我参与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()
 }