使用堆和优先队列求解数据流中的中位数

剑指 Offer 41. 数据流中的中位数

题目

  • 从一个数据流中求解当前已读数据的中位数。
  • 如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。

题解

  • 维护两个堆,大根堆LargeRootHeap中保存所有比当前中位数小的数,小根堆SmallRootHeap中保存所有比当前中位数大的数。所以,这个两堆的堆顶元素一定是距离中位数最近的两个数。
    在这里插入图片描述
    • 根据上图可以看到,只要满足堆的定义即可,不需要满足二叉搜索树。之前一直纠结是不是要排序,但是对于大根堆,只要维护最大的数在堆顶;对于小根堆,维护最小的数在堆顶即可。
  • 对堆进行增加元素和删除元素时,要基于队列操作,即只能从队尾插入元素,从队头删除元素,同时要保持堆性质不变。
    在这里插入图片描述
    在这里插入图片描述
  • 当LargeRootHeap和SmallRootHeap长度一样时,将元素插入SmallRootHeap;否则,插入LargeRootHeap。
  • 插入规则:
    • 为了满足SmallRootHeap中的元素都比中位数大,读取的当前元素入队LargeRootHeap,将LargeRootHeap的堆顶元素(即LargeRootHeap中最大值)入队SmallRootHeap。
    • LargeRootHeap同理。
  • 根据以上策略可以维持: 0 < = S m a l l R o o t H e a p . s i z e − L a r g e R o o t H e a p . s i z e < = 1 0 <= SmallRootHeap.size - LargeRootHeap.size <= 1 0<=SmallRootHeap.sizeLargeRootHeap.size<=1,对于奇数个元素,中位数是: S m a l l R o o t H e a p . h e a d SmallRootHeap.head SmallRootHeap.head;偶数个元素,中位数是: ( S m a l l R o o t H e a p . h e a d + L a r g e R o o t H e a p . h e a d ) / 2 (SmallRootHeap.head + LargeRootHeap.head) / 2 (SmallRootHeap.head+LargeRootHeap.head)/2
    在这里插入图片描述

JS代码实现

实现堆队列

  • 由于JS内部没有封装相关的数据结构,这块得自己写。
// 基类,实现二叉树的相关方法
class BasicBinaryTree {
  constructor (list = []) {
    this.list = list
  }
  size () {
    return this.list.length
  }
  parent (i) {
    let index = -1
    if (i & 1) {
      index = (i - 1) / 2
    } else {
      index = (i - 2) / 2
    }
    return index >= 0 ? index : -1 
  }
  leftChild (i) {
    return 2 * i + 1
  }
  rightChild (i) {
    return 2 * i + 2
  }
  isEmpty () {
    return this.size() === 0
  }
  head () {
    return this.isEmpty() ? null : this.list[0]
  }
  isGreater (i, j) {
    if (this.get(i) > this.get(j)) {
      return true
    }
    return false
  }
  swap(i, j) {
    const v = this.get(i)
    this.set(i, this.get(j))
    this.set(j, v)
  }
  set (i, e) {
      this.list[i] = e
  }
  get (i) {
    if (i < 0 || i >= this.size()) {
      return null
    }
    return this.list[i]
  }
}
// 大根堆
class LargeRootHeap extends BasicBinaryTree {
  // 递归
  heapifyDownRecursion (i, size = this.size) {
    const rightChild = this.rightChild(i)
    const leftChild = this.leftChild(i)
    let swapIndex = this.isGreater(rightChild, leftChild) ? rightChild : leftChild
    if (swapIndex < size && this.isGreater(swapIndex, i)) {
      this.swap(swapIndex, i)
      this.heapifyDownRecursion(swapIndex, size)
    }
  }

   // 非递归
   heapifyDown (i, size = this.size()) {
    while (i < size) {
      const rightChild = this.rightChild(i)
      const leftChild = this.leftChild(i)
      let swapIndex = -1
      if (rightChild < size && leftChild < size) {
          swapIndex = this.isGreater(rightChild, leftChild) ? rightChild : leftChild
      } else if (rightChild >= size) {
          swapIndex = leftChild
      } else {
          swapIndex = rightChild
      }
      if (swapIndex >= size || !this.isGreater(swapIndex, i)) {
        return
      }
      this.swap(swapIndex, i)
      i = swapIndex
    }
  }
  
  heapifyUp (i) {
    const parent = this.parent(i)
    if (i >= 0 && parent >=0 && this.isGreater(i, parent)){
      this.swap(i, parent)
      this.heapifyUp(parent)
    }
  }
}
// 大根堆队列
class LargeRootQueue extends LargeRootHeap {
  enQueue (e) {
    this.list.push(e)
    this.heapifyUp(this.size() - 1)
  }
  deQueue () {
    const head = this.head()
    if (head !== null) {
      const last = this.list.pop()
      if (head !== last) {
        this.list[0] = last
        this.heapifyDown(0, this.size())
      }
    }
    return head
  }
}
// 小根堆队列
class SmallRootQueue extends LargeRootQueue {
  // 小根堆只是每次比较并操作最小的数,只修改isGreater中的判断逻辑,其余直接复用即可
  isGreater (a, b) {
    if (this.get(a) < this.get(b)) {
      return true
    }
    return false
  }
}

从数据流中获取中位数

/**
 * initialize your data structure here.
 */
var MedianFinder = function() {
  this.largeRootQueue = new LargeRootQueue()  // 大根堆,保存比中位数小的元素
  this.smallRootQueue = new SmallRootQueue() // 小根堆,保存比中位数大的元素
};

/** 
 * @param {number} num
 * @return {void}
 * 说明:
 * - 如果largeSize === smallSize,将元素插入largeHeap中,largeHeap堆顶出队,插入smallHeap中
 * - 否则,将元素插入smallHeap中,smallHeap堆顶出队,插入largeHeap中
 */
MedianFinder.prototype.addNum = function(num) {
  const largeRootQueueSize = this.largeRootQueue.size()
  const smallRootQueueSize = this.smallRootQueue.size()
  if (largeRootQueueSize === smallRootQueueSize) { // 向small中添加元素
    this.largeRootQueue.enQueue(num)
    this.smallRootQueue.enQueue(this.largeRootQueue.deQueue())
  } else { // 向large中添加元素
    this.smallRootQueue.enQueue(num)
    this.largeRootQueue.enQueue(this.smallRootQueue.deQueue())
  }
};

/**
 * @return {number}
 * 说明:
 * - 当已读奇数个元素,即largeSize === smallSize,中位数 = smallHeap堆顶元素
 * - 否则,中位数 = (smallHeap堆顶元素 + largeHeap堆顶元素) / 2
 */
MedianFinder.prototype.findMedian = function() {
  if (this.largeRootQueue.size() === this.smallRootQueue.size()) {
    return (this.largeRootQueue.head() + this.smallRootQueue.head()) / 2
  }
  return this.smallRootQueue.head()
};
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值