数据流中的中位数

398 阅读2分钟

描述

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

数据范围:数据流中数个数满足 1≤n≤1000 1≤n≤1000  ,大小满足 1≤val≤1000 1≤val≤1000 

进阶: 空间复杂度 O(n) O(n)  , 时间复杂度 O(nlogn) O(nlogn) 

示例1

输入:

[5,2,3,4,1,6,7,0,8]

返回值:

"5.00 3.50 3.00 3.50 3.00 3.50 4.00 3.50 4.00 "

说明:

数据流里面不断吐出的是5,2,3...,则得到的平均数分别为5,(5+2)/2,3...   

示例2

输入:

[1,1,1]

返回值:

"1.00 1.00 1.00 "

解题思路

  1. 本题是对顶堆算法的一个经典题,所谓对顶堆算法,实际上就是使用一个小顶堆和一个大顶堆
  2. 对于求动态中位数的问题,我们可以利用堆的性质,可以在logn的时间复杂度取出最大值或最小值,那么我们可以用大顶堆维护前一半的数,用小顶堆维护后面一半的数
  3. 我们规定,若元素总数为奇数,那么大顶堆的元素个数要比小顶对的个数多1,这样,当总数为奇数时,中位数就是大顶堆的堆顶元素;当总数为偶数时,中位数就大顶堆堆顶和小顶堆堆顶元素的平均值

代码

public:
    priority_queue<int> max_heap;    // 大顶堆
    priority_queue<int, vector<int>, greater<int>> min_heap;    // 小顶堆

    void Insert(int num) {
        if(max_heap.empty() || max_heap.top() > num) max_heap.push(num);    // 大顶堆为空或大顶堆堆顶大于插入元素
        else min_heap.push(num);    // 否则加入小顶堆

        // size()方法得到的类型size_type是无符号的, 切记不要直接放在判断语句中, 要先转成int型
        int a = max_heap.size(), b = min_heap.size();

        if(a - b > 1){    // 大顶堆元素数量过多,保证大顶堆大小-小顶堆大小大于等于1
            int p = max_heap.top(); max_heap.pop();
            min_heap.push(p);
        }
        else if(b > a){    // 小顶堆元素数量过多
            int p = min_heap.top(); min_heap.pop();
            max_heap.push(p);
        }
    }

    double GetMedian() { 
        if(max_heap.si***_heap.size()){    // 偶数长度,返回两数的平均值
            return (max_heap.top() + min_heap.top()) / 2.0;
        }
        return (double)max_heap.top();    // 奇数长度,返回大顶堆堆顶
    }
};