剑指Offer(63):数据流中的中位数

本文介绍了一种利用大顶堆和小顶堆求解数据流中位数的方法,通过实时调整两个堆的数据,确保能高效准确地获取任意时刻数据流的中位数。

一、题目描述

如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。我们使用Insert()方法读取数据流,使用GetMedian()方法获取当前读取数据的中位数。


二、解题思路

大顶堆:根结点的关键字大于等于左右子树的关键字值。
小顶堆:根结点的关键字小于等于左右子树的关键字值。

采用大顶堆 + 小顶堆,满足两点:
1)两个堆中数据的数目之差不能超过1,这样中位数只会出现在两个堆的交接处。
规定插入前总数目是偶数时将新数据插入到大顶堆中,奇数时插入到小顶堆中。
2)保证大顶堆中所有数据小于小顶堆中数据。
在插入新的数据时,需先和大顶堆堆顶或者小顶堆堆顶进行比较。
a. 插入前总数为偶数时,数据会被插入到大顶堆中。首先判断新数据和小顶堆堆顶大小,如果小顶堆堆顶数据小,那么将新数据插入小顶堆,然后弹出小顶堆堆顶再插入到大顶堆中;如果新数据小,则直接插入大顶堆成为堆顶。
b. 插入前总数为奇数时,数据会被插入到小顶堆中。首先判断新数据和大顶堆堆顶大小,如果大顶堆堆顶数据大,那么将新数据插入大顶堆,然后弹出大顶堆堆顶再插入到小顶堆中;如果新数据大,则直接插入小顶堆成为堆顶。

例如:
插入前总数为偶数,此时新数据为4
 大顶堆  小顶堆
  [3]    [5]
 [2] [1]  [7] [8]
4与5进行比较,4<5,将4直接插入大顶堆
 大顶堆  小顶堆
  [4]    [5]
 [3] [2]  [7] [8]
[1]
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
插入前总数为偶数,此时新数据为6
 大顶堆  小顶堆
  [3]    [5]
 [2] [1]  [7] [8]
6与5进行比较,6>5,将6插入小顶堆,弹出堆顶5插入大顶堆
 大顶堆  小顶堆
  [5]    [6]
 [3] [2]  [7] [8]
[1]
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
插入前总数为奇数,此时新数据为5
 大顶堆  小顶堆
  [4]    [6]
 [2] [1]  [7] 
5与4进行比较,5>4,将5直接插入小顶堆
 大顶堆  小顶堆
  [4]    [5]
 [2] [1]  [6] [7]
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
插入前总数为奇数,此时新数据为3
 大顶堆  小顶堆
  [4]    [6]
 [2] [1]  [7] 
3与4进行比较,3<4,将3插入大顶堆,弹出堆顶4插入小顶堆
 大顶堆  小顶堆
  [3]    [4]
 [2] [1]  [6] [7]


三、编程实现

import java.util.Comparator;
import java.util.PriorityQueue;

public class Solution {
    int count;
    PriorityQueue<Integer> minHeap = new PriorityQueue<>();
    // private static final int DEFAULT_INITIAL_CAPACITY = 11;
    PriorityQueue<Integer> maxHeap = new PriorityQueue<>(11, new Comparator<Integer>() {
        @Override
        public int compare(Integer o1, Integer o2) {
            // PriorityQueue默认是小顶堆,实现大顶堆,需要反转默认排序器
            return o2.compareTo(o1);
        }
    });

    public void Insert(Integer num) {
        count++;
        // 当插入前总数为奇数时,count为偶数
        if (count % 2 == 0) {
            // 如果大顶堆堆顶较大
            if (!maxHeap.isEmpty() && num < maxHeap.peek()) {
                // 将新的数据插入大顶堆并且弹出堆顶
                maxHeap.offer(num);
                num = maxHeap.poll();
            }
            // 将值插入到小顶堆
            minHeap.offer(num);
        } else {
            // 当插入前总数为偶数时,如果小顶堆堆顶较小
            if (!minHeap.isEmpty() && num > minHeap.peek()) {
                // 将新的数据插入小顶堆并且弹出堆顶
                minHeap.offer(num);
                num = minHeap.poll();
            }
            // 将值插入大顶堆
            maxHeap.offer(num);
        }
    }

    public Double GetMedian() {
        double result;
        if (count % 2 == 1) {
            // 总数为奇数时,中位数就是大顶堆堆顶
            result = maxHeap.peek();
        } else {
            // 总数为偶数时,中位数为大顶堆小顶堆堆顶的平均值
            result = (minHeap.peek() + maxHeap.peek()) / 2.0;
        }
        return result;
    }
}

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值