堆排序求中位数

堆排序求中位数二分查找求中位数需要数组有序 而且不能求动态数据的中位数 双堆就可以很好地解决这个问题 思路也比较简单 用大顶堆存较小的部分 用小顶堆存较大的部分 同时要维持两个堆的大小相差最多为 1 这样求中位数的时候要么取某个堆的堆顶 要么求两个堆顶的平均值

二分查找求中位数需要数组有序,而且不能求动态数据的中位数,双堆就可以很好地解决这个问题。思路也比较简单,用大顶堆存较小的部分,用小顶堆存较大的部分,同时要维持两个堆的大小相差最多为1,这样求中位数的时候要么取某个堆的堆顶,要么求两个堆顶的平均值。

其实堆的问题最容易搞混的还是到底用什么堆存什么数据,比如求topk大的数就应该用小顶堆,求topk小的数就应该用大顶堆,还是要自己好好琢磨琢磨。

上代码:(规定maxHeap要么和minHeap一样大,要么比它多一个。看个人习惯,也可以minHeap多一个)

public class DualHeap { 
    private PriorityQueue<Integer> minHeap = new PriorityQueue<>(); // 不用 (o1, o2) -> o2 - o1,防整数相减溢出 // private PriorityQueue<Integer> maxHeap = new PriorityQueue<>((o1, o2) -> Integer.compare(o2, o1); private PriorityQueue<Integer> maxHeap = new PriorityQueue<>(Comparator.reverseOrder()); public void addNum(int num) { 
    if (maxHeap.size() == minHeap.size()) { 
    minHeap.offer(num); maxHeap.offer(minHeap.poll()); } else { 
    maxHeap.offer(num); minHeap.offer(maxHeap.poll()); } } public double getMedian() { 
    if (maxHeap.size() == minHeap.size()) // 也是防整数相加溢出,leetcode还是挺爱出极端用例的 return maxHeap.peek() / 2.0 + minHeap.peek() / 2.0; else return maxHeap.peek(); } } 

最开始双堆的大小都是0,即maxHeap.seize() == minHeap.size()。所以向minHeap中添加,然后再弹出堆顶,添加到maxHeap中,保证了maxHeap比比minHeap大1。

此时maxHeap.size() != minHeap.size(),应该让下一个元素添加到minHeap中:

此时maxHeap.size() == minHeap.seize(),下一个元素还应该向maxHeap中添加,还是和上面的步骤一样,想向哪个堆中添加,先去另一个堆中转一下,然后弹出最适合添加的数。所以后续的过程就不画了,最终两个堆应该是这样的:

所有数都添加完成后maxHeap.size() == minHeap.size(),所以返回中位数是二者堆顶的均值。如果size不相等,那肯定是maxHeap多一个,那就返回maxHeap.peek()就可以。

数据大小固定情况

上面的例子是可以一直添加数的,但如果是滑动窗口的题,比如今天的每日抑题leeetcode480,堆中数据的个数是固定的,那么在向堆中添加数(窗口右边界)的时候还要删一个数(窗口左边界),所以上面的双堆代码就需要拓展,保证删除之后两个堆的大小还满足我们的要求。拓展之前先考虑一下从堆中删除数据的时候需要考虑哪些情况:

  • 删之前,maxHeap.size() == minHeap.size()
    • 要删的数在maxHeap中,删完之后maxHeap.sieze() < minHeap.size(),需要把minHeap的堆顶弹出,添加到maxHeap。
    • 要删的数在minHeap中,删完之后maxHeap.size() = minHeap.size() + 1,不需要动。
  • 删之前,maxHeap.size() = minHeap.size() + 1
    • 要删的数在maxHeap中,删完之后maxHeap.size() == minHeap.size(),不需要动。
    • 要删的数在minHeap中,删完之后maxHeap.size() = minHeap.size() + 2,需要把maxHeap的堆顶弹出,添加到minHeap中。

其实只有两种情况需要调整堆,那么代码就好写了:

public class DualHeap { 
    private PriorityQueue<Integer> minHeap = new PriorityQueue<>(); // 不用 (o1, o2) -> o2 - o1,防整数相减溢出 // private PriorityQueue<Integer> maxHeap = new PriorityQueue<>((o1, o2) -> Integer.compare(o2, o1); private PriorityQueue<Integer> maxHeap = new PriorityQueue<>(Comparator.reverseOrder()); public void addNum(int num) { 
    if (maxHeap.size() == minHeap.size()) { 
    minHeap.offer(num); maxHeap.offer(minHeap.poll()); } else { 
    maxHeap.offer(num); minHeap.offer(maxHeap.poll()); } } public double getMedian() { 
    if (maxHeap.size() == minHeap.size()) // 也是防整数相加溢出,leetcode还是挺爱出极端用例的 return maxHeap.peek() / 2.0 + minHeap.peek() / 2.0; else return maxHeap.peek(); } public void removeNum(int num) { 
    if (num <= maxHeap.peek()) maxHeap.remove(num); else minHeap.remove(num); balance() } public void balance() { 
    if (maxHeap.size() > minHeap.size() + 1) minHeap.offer(maxHeap.poll()); else if (maxHeap.size() < minHeap.size()) maxHeap.offer(minHeap.poll()); } } 
知秋君
上一篇 2024-11-10 17:02
下一篇 2024-11-13 17:55

相关推荐