剑指offer()数据流中的中位数

题目描述

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

分析

如果数据在容器中已经排好序,那么中位数可以由P1和P2指向的数得到。如果数据个数为奇数个,则P1和P2指向同一个数,这个数也就是要求的中位数,如果数据个数是偶数个,那么所求的数就是P1和P2所指向的数的平均数。

通过观察发现,整个数据容器被分隔成两部分,容器左边的数比右边的数小,而且,P1指向的数据是左边部分最大数,P2指向的数据是右边部分最小数。 这一发现很重要,即使P1左边和P2右边的数据没有排序,我们也可以根据左边最大值和右边最小值来得到中位数。那么如何才能快速得到一个容器中的最大值和最小值呢?这里可以使用大顶堆和小顶堆。

也就是说,用大顶堆实现左边的的数据容器,小顶堆实现右边的数据容器。有一点需要注意,我们要保证数据被平均分配到两个堆中,因此两堆大小之差不能超过1,我这里用的是如果个数之差为1,那么是大顶堆的个数多1个。

考虑以下2种特殊情况:

(1)输入的是1、2、3、4、5、6、7、8(已经从小到大排好序的序列)。 输入1之后,假设分配到了大顶堆,此时1是大顶堆的top;
接着输入2,假设还是分配到了大顶堆,那么2成为了大顶堆的top;此时不满足两堆中数的个数之差小于等于1的要求。那么需要想方法将这个2移到小顶堆中。这里可以用对比size来实现。
依次类推。

(2)输入的是8、7、6、5、4、3、2、1(已经从大到小排好序的序列)。 输入8之后,假设分配到了大顶堆,此时8是大顶堆的top;
接着输入7,假设还是分配到了大顶堆,那么8还是大顶堆的top;此时不满足两堆中数的个数之差小于等于1的要求。那么需要想方法将这个8移到小顶堆中。)这里同样可以用对比size来实现。
以此类推。

以上两种情况都是最坏情况。

代码

class Solution{
    priority_queue<int,vector<int>,less<int>> max;
    priority_queue<int,vector<int>,greater<int>> min;
    
public:
    void Insert(int num){
        if(max.empty()||num<=max.top())
            max.push(num);
        else
            min.push(num);
        if(max.size()==min.size()-1){
            max.push(min.top());
            min.pop();
        }
        if(max.size()==min.size()+2){
            min.push(max.top());
            max.pop();
        }
    }
    double GetMedian(){
        return max.size()==min.size()?(max.top()+min.top())/2.0:max.top();
    }
};

分析(另)

另一种解法,思路类似,实现方法是数据流中奇数位的数插入小顶堆中,偶数位的数插入大顶堆中。同时还要保证大顶堆中的所有数都要小于小顶堆中的数。

考虑一种特殊情况,例如,插入偶数位的数,按照分配规则应该插入大顶堆中,但是这个数大于小顶堆的最小值怎么办?如果直接插入大顶堆,就不符合大顶堆所有值都小于小顶堆的值这一条件。可以这样解决:想把该数插入小顶堆,然后进行堆排序,接着讲小顶堆的最小值拿出来插入到大顶堆中,这样就满足所有条件了。同理插入奇数位的数时也要考虑这种特殊情况。

代码

class Solution {
public:
	//插入原则:奇数位的数插入小顶堆,偶数位数的插入大顶堆,除特殊情况外
	void Insert(int num)
	{
/*特殊情况1:当插入奇数位的数num时,应插入小顶堆里,但是当num小于大顶堆的最大值时,
应将其插入大顶堆,然后将大顶堆的最大值插入小顶堆中*/
		if (((min.size() + max.size()) & 1) == 0)                   
		{
			if (max.size() > 0 && num < max[0])
			{
				max.push_back(num);
				push_heap(max.begin(), max.end());              
				num = max[0];                                
				pop_heap(max.begin(), max.end());          
				max.pop_back();                           
			}
			min.push_back(num);
			push_heap(min.begin(), min.end(),greater<int>());      
		}
		else
		{
/*特殊情况2:当插入偶数位的数num时,应插入大顶堆里,
但是当num大于小顶堆的最小值时,应将其插入小顶堆,然后将小顶堆的最小值插入大顶堆中*/
			if (min.size() > 0 && num > min[0])
			{
				min.push_back(num);
				push_heap(min.begin(), min.end(),greater<int>());     
				num = min[0];                                           
				pop_heap(min.begin(), min.end(),greater<int>());      
				min.pop_back();                                      
			}
			max.push_back(num);
			push_heap(max.begin(), max.end());
		}
	}
 
	double GetMedian()
	{
		int size = min.size() + max.size();
		if (size == 0)
			return 0;
		if ((size & 1) == 1)
			return min[0];
		else
			return (double)(max[0] + min[0]) / 2;
	}
 
private:
	vector<int> min;           
	vector<int> max;                 
};

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值