LeetCode题解「 剑指 Offer 41 」:数据流的中位数

题目描述:

        如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数平均值

解题核心:

        划重点了嗷,排序之后数据流中间......有啥思路吗?

        排序,位于中间,那便是由一个大根堆和一个小根堆组合成的对顶堆

解题思路:

        堆(Heap),是一种由数组或序列(其项的值在小根堆中必须满足K_{i}\leqslant K_{2i}K_{i}\leqslant K_{2i+1},在大根堆中必须满足K_{i}\geqslant K_{2i}K_{i}\geqslant K_{2i+1}构成,可以看为一棵完全二叉树的数据结构。

        堆的定义请参考百度百科:堆(数据结构)_百度百科

        堆有以下特性,并且特性一和二是互斥的:

                一、堆中某个节点的值总是不大于其父节点的值,这样的堆称为大根堆

                二、堆中某个节点的值总是不小于其父节点的值,这样的堆称为小根堆

                三、堆总是一棵完全二叉树

        故可以利用堆的性质一和性质二来进行排序,这种排序方法称为堆排序

        为什么堆可以看做二叉树?因为若其结点按照第一行一个元素,每一行元素的数量为上一行的两倍一直罗列下去(最后一行可以不排满),将会得到一个规律:当前结点的左孩子索引是当前结点索引的两倍,当前结点的右孩子索引是当前结点索引的两倍加一(假设第一个元素的索引是1)。这样我们便可以通过当前结点来得到左右孩子结点了,这和二叉树,只能说是完全一样。

        下面是两个堆的实例(包含二叉树形态及数组形态):

        小根堆:

         大根堆:

        根据堆的性质,堆顶元素必为数组(或序列)中的最值,故若是数据在加入堆中的时候可以按照某种方法,在平衡两个堆的元素数量(两堆元素数量差的绝对值小于等于1)的同时保证元素值较大的那一半数据放置在小根堆中,元素值较小的那一半数据放置在大根堆中,我们便可以知道,若小根堆元素数量多,则中位数是小根堆的堆顶元素。而若大根堆元素多,则中位数是大根堆的堆顶元素(若两堆元素数量相等则为这两个元素的平均值)

        如下图所示:

         问题在于:如何在每次往堆里添加元素的时候保证两个堆数量平衡的同时保证每个元素都去到了应该去的堆

        若要保证堆的元素数量平衡,则应将数据轮流插入两个堆中。但若是直接轮流插入数据,则不能保证较小的元素去到了大根堆,也不能保证较大的元素去到了小根堆,这样便没有了“两堆顶元素必可得中位数”的规律。(如数据流中输入:5,6,7,8,9,11,12,先将元素放入小根堆中再放入大根堆中,则大根堆中会有三个元素:12,5,8。小根堆中会有四个元素:6,7,9,11。明显中位数应当是8而非元素数量更多的小根堆的堆顶元素6)

        而若是要保证每次插入的元素都能去到应去的堆,则应在每次插入时都与当前两堆堆顶得到的中位数比较大小,再根据比较结果确定去哪个堆,这样的话如果连续出现多个较大或较小数据导致两堆元素数量不平衡,则会导致两个堆顶元素不会出现中位数。(比如大根堆里只有10个元素,而小根堆里有20个元素,中位数显然会出现在小根堆里而不是两个堆顶上)

         这时候要想到,堆有排序性,若想保证两堆数量平衡并且保证元素去到了该去的位置,就应向两堆中轮流插入数据,若想将元素插入堆A,则应先将该元素插入堆B堆B维护堆内元素顺序后将堆顶元素出堆然后再插入堆A。这样,若当前元素为堆B的最值时,该元素会真正的插入堆A中,而若不是堆B的最值时,经过调整,堆B中的最值会到达堆A中,从而防止了元素进不到该进的堆中。

        这样说不好理解,我以上图中的堆为例,假设大根堆中有13个元素,堆顶元素为15,小根堆中有14个元素,堆顶元素为19,大根堆的元素数量少,为维持平衡,应将下一个元素插入插入大根堆。但下一个元素值为23,很明显该元素值比小根堆的堆顶元素都要大,不应当插入大根堆。故应先将23插入小根堆,重新维护关系后将小根堆的堆顶插入大根堆,并且两个堆会自动再维护关系。这样便可以实现两堆数量平衡的同时元素位置正确。

函数设计:

主函数中通过STL优先队列来实现堆:

priority_queue<int,vector<int>,less<int> > big_heap;        //大根堆
priority_queue<int,vector<int>,greater<int> > small_heap;   //小根堆

功能为添加数据的函数:

返回类型:

        void

参数:

        int num,待输入的数。 

 具体设计:

        判断两堆元素数量是否相等。若相等,则将元素先加入小根堆中,然后维护关系后将小根堆堆顶元素再加入大根堆中。若不相等,则必为大根堆元素数量更多(因为在两堆为空时将第一个元素插入了大根堆中),将元素先加入大根堆中,然后维护关系后将大根堆堆顶元素再加入小根堆中

功能为查找中位数的函数:

返回类型:

        double

参数:

        可有可无,题目中无参,但在实际应用中还是建议使用参数,若有参数,则传入两个代表大根堆和小根堆的优先队列。

 具体设计:

        判断两堆元素数量是否相等。若不相等,则返回大根堆堆顶元素(按照上一个函数的算法,大根堆元素必比小根堆多)。若相等,则返回两堆堆顶元素平均值

代码实现:

        C++: 

priority_queue<int,vector<int>,less<int> > big_heap;
priority_queue<int,vector<int>,greater<int> > small_heap; 

void addNum(int num) {
    if(big_heap.size() == small_heap.size())
    {
        small_heap.push(num);
        int temp = small_heap.top();
        small_heap.pop();
        big_heap.push(temp);
    }
    else
    {
        big_heap.push(num);
        int temp = big_heap.top();
        big_heap.pop();
        small_heap.push(temp);
    }
}
    
double findMedian() 
{
    if(big_heap.size() != small_heap.size())
        return big_heap.top();
    else
        return (big_heap.top() + small_heap.top()) / 2.0;
}

 本题出处 :

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

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值