c++实现---数据流中的中位数

题目描述:
如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。我们使用Insert()方法读取数据流,使用GetMedian()方法获取当前读取数据的中位数。
lower_bound( begin,end,num):从数组的begin位置到end-1位置二分查找第一个大于或等于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。
upper_bound( begin,end,num):从数组的begin位置到end-1位置二分查找第一个大于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。
详细介绍可以看链接:https://blog.csdn.net/u010700335/article/details/41323427?utm_medium=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase
方法一:暴力方法
对于一组数据,我们可以用vector arr来存取。如果对vector排好序,则很容易求出中位数。如果vector的大小为sz。
如果sz为奇数,假如为3,即[0 1 2],则中位数就是中间的那个数arr[1]。
如果sz为偶数,假如为4,即[0 1 2 3], 则中位数就是中间两个数的加权平均数。即 (arr[1] + arr[2]) / 2
代码如下:

class Solution {
    //这种方法最容易想到,也最容易实现,但是时间复杂度:Insert()为O(1), GetMedian()为O(nlogn)
//空间复杂度:O(n)  当面试官要求更高效时就不能满足了
public:
    #define SCD static_cast
    vector<int> v;
    void Insert(int num)
    {
        v.push_back(num);
    }
    double GetMedian()
    { 
        sort(v.begin(),v.end());//排序
        int sz=v.size();
        if(sz&1){//位运算判断是否是奇数
            return SCD(v[sz>>1]);//奇数
        }else{
            return SCD(v[sz>>1]+v[(sz-1)>>1])/2;//偶数个
        }
    }
};

时间复杂度:Insert()为O(1), GetMedian()为O(nlogn)
空间复杂度:O(n)
方法二:插入排序
对于方法一,可以发现有个优化的地方。
方法一中GetMEdian()操作,是每次都对整个vector调用排序操作。
但是其实每次都是在一个有序数组中插入一个数据。因此可以用插入排序。
所以:
Insert()操作可改为插入排序
GetMedian()操作可直接从有序数组中获取中位数
代码如下:

class Solution {
//方法二:插入排序
//对于方法一,可以发现有个优化的地方。
//方法一中GetMEdian()操作,是每次都对整个vector调用排序操作。
//但是其实每次都是在一个有序数组中插入一个数据。因此可以用插入排序。
//所以:
//Insert()操作可改为插入排序
//GetMedian()操作可直接从有序数组中获取中位数
public:
    #define SCD static_cast
    vector<int> v;
    void Insert(int num)
    {
        if(v.empty()){
            v.push_back(num);
        }else{
            auto it=lower_bound(v.begin(), v.end(), num);//lower_bound从数组的begin位置到end-1位置二分查找第一个大于或等于num的数字,找到返回该数字的地址,不存在则返回end;
            v.insert(it, num);//类似与插入排序,每一次进来的数都插入到比它大的后面,或者它就是最大的那个数
        }
    }
    double GetMedian()
    { 
        int sz=v.size();
        if(sz&1){//位运算判断是否是奇数
            return SCD(v[sz>>1]);//奇数
        }else{
            return SCD(v[sz>>1]+v[(sz-1)>>1])/2;//偶数个
        }
    }
};

时间复杂度:Insert()为O(n),即二分查找的O(logn)和挪动数据的O(n), GetMedian()为O(1)
空间复杂度:O(n)
方法三:堆
中位数是指:有序数组中中间的那个数。则根据中位数可以把数组分为如下三段:
[0 … median - 1], [median], [median … arr.size() - 1],即[中位数的左边,中位数,中位数的右边]
那么,如果我有个数据结构保留[0…median-1]的数据,并且可以O(1)时间取出最大值,即arr[0…median-1]中的最大值
相对应的,如果我有个数据结构可以保留[median + 1 … arr.size() - 1] 的数据, 并且可以O(1)时间取出最小值,即
arr[median + 1 … arr.size() - 1] 中的最小值。
然后,我们把[median]即中位数,随便放到哪个都可以。
假设[0 … median - 1]的长度为l_len, [median + 1 … arr.sise() - 1]的长度为 r_len.
1.如果l_len == r_len + 1, 说明,中位数是左边数据结构的最大值
2.如果l_len + 1 == r_len, 说明,中位数是右边数据结构的最小值
3.如果l_len == r_len, 说明,中位数是左边数据结构的最大值与右边数据结构的最小值的平均值。
说了这么多,一个数据结构可以O(1)返回最小值的,其实就是小根堆,O(1)返回最大值的,其实就是大根堆。并且每次插入到堆中的时间复杂度为O(logn)
所以,GetMedian()操作算法过程为:
初始化一个大根堆,存中位数左边的数据,一个小根堆,存中位数右边的数据
动态维护两个数据结构的大小,即最多只相差一个
代码如下:

class Solution {
public:
    #define SCD static_cast
    priority_queue<int> min_heap;//默认是大根堆
    priority_queue<int,vector<int>,greater<int>> max_heap;//小根堆
    void Insert(int num)
    {
        min_heap.push(num);//默认加入大根堆
        
        max_heap.push(min_heap.top());//将大根堆中最大值加入小根堆
        min_heap.pop();//大根堆最大值出队列
        
        if(min_heap.size()<max_heap.size()){//如果大根堆的个数小于小根堆,则将小根堆最小值加入大根堆
            min_heap.push(max_heap.top());
            max_heap.pop();
        }
    }
    double GetMedian()
    { //如果是奇数个即小根堆个数大于大根堆个数,返回大根堆首元素,否则说明是偶数,返回大根堆最大值和小根堆最小值平均值
        return min_heap.size()>max_heap.size()?SCD(min_heap.top()):SCD(min_heap.top()+max_heap.top())/2;
    }
};

时间复杂度:Insert()为O(logn), GetMedian()为O(1)
空间复杂度:O(n)

你可能感兴趣的:(c++实现---数据流中的中位数)