[图解算法] [LeetCode 295] 数据流中的中位数

原文链接:更好的阅读体验

[LC295] 数据流中的中位数(类设计)

题目描述:

中位数是有序列表中间的数。如果列表长度是偶数,中位数则是中间两个数的平均值。

例如:

[2,3,4] 的中位数是 3

[2,3] 的中位数是 (2 + 3) / 2 = 2.5

设计一个支持以下两种操作的数据结构:

void addNum(int num) - 从数据流中添加一个整数到数据结构中。
double findMedian() - 返回目前所有元素的中位数。
示例:

    addNum(1)
    addNum(2)
    findMedian() -> 1.5
    addNum(3) 
    findMedian() -> 2

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/find-median-from-data-stream


算法分析

维护两个堆(一个大顶堆bigger,一个小顶堆smaller)来保存数据流中流过来的数字。无论何时都要保持abs(bigger.size-smaller.size)<=1;并且保证大顶堆内的数字的值全都小于或等于小顶堆内的数字的值,这样就能保证大顶堆保存的是数据流中较小的一半数字,而小顶堆中保存的是另外较大的一半。


findMedian 函数设计

  如此一来,不难发现数据流的中位数查找函数 findMedian() 的逻辑就为(伪代码):

if smaller.size == bigger.size:
    return (smalle.top + bigger.top)/2;
else if smaller.size > bigger.size:
    return smaller.top;
else
    return bigger.top;

addNum 函数设计

  addNum() 函数在添加元素的过程中保持两个堆的动态平衡:

Condition 1.保证两堆元素个数相差不超过 1
Condition 2.保证大顶堆中的元素小于等于小顶堆中的任何元素
case 1:
  • 如果两堆中的元素个数相同。这个时候无论插入哪一个堆,条件 1 都不会被破坏,因此考虑条件 2 ,将待插入元素与两堆的堆顶比较:若待插入元素为 5,显然这个时候若插入smaller会破坏条件 2,因此因插入bigger中。而若待插入为 9 则显然应插入 smaller 中。


case 2:
  • 如果大顶堆元素个数小于小顶堆的元素个数。此时,将待插入元素与两堆堆顶比较:
  • 若小于等于Bigger.top则直接插入Bigger中;

  • 若大于smaller.top则为了保证条件1,需将smaller中的最小值(根)转存至Bigger中。


case 3:
  • 如果大顶堆的元素个数大于小顶堆的元素个数。此时,将待插入元素与两堆堆顶比较:
  • 若其大于等于Smaller.top则直接插入Smaller中;

  • 若小于Bigger.top则为了保证条件1,需将Bigger中的最大元素值(根)转存至Smaller中。


代码实现

#include "Include_all.h"
using namespace std;

class MedianFinder {
public:
	/** initialize your data structure here. */
	MedianFinder() {
	}
	
	//插入过程中维持两堆元素个数的动态平衡
	void addNum(int num) {
		//如果两堆元素个数相同
		if(smaller.size()==bigger.size())
		{
			if(!bigger.empty() && bigger.top()>=num)
			{
				cout << "Push num " << num <<" into bigger" << endl;
				bigger.push(num);
			}
			else
			{
				cout << "Push num " << num <<" into smaller" << endl;
				smaller.push(num);
			}
		}
		//小顶堆>大顶堆
		else if(smaller.size() < bigger.size())
		{
			if(bigger.top()>num)
			{
				cout << "Push bigger's top " << bigger.top() <<" into smaller" << endl;
				smaller.push(bigger.top());
				cout << "Pop bigger's top'" << bigger.top() <<" from bigger" << endl;
				bigger.pop();
				cout << "Push num " << num <<" into bigger" << endl;
				bigger.push(num);
			}
			else
			{
				cout << "Push num " << num <<" into smaller" << endl;
				smaller.push(num);
			}
		}
		//大顶堆>小顶堆
		else
		{
			if(smaller.top() < num)
			{
				cout << "Push smaller's top " << smaller.top() <<" into bigger" << endl;
				bigger.push(smaller.top());
				cout << "Pop smaller's top " << smaller.top() <<" from smaller" << endl;
				smaller.pop();
				cout << "Push num " << num <<" into smaller" << endl;
				smaller.push(num);
			}
			else
			{
				cout << "Push " << num <<" into bigger" << endl;
				bigger.push(num);
			}
		}
	}
	
	//该函数返回中位数
	double findMedian() {
		if(smaller.size()==bigger.size())
		{
			return (smaller.top()+bigger.top())/2.0;
		}
		else if(smaller.size()>bigger.size())
		{
			return smaller.top();
		}
		else
			return bigger.top();
	}
private:
	//实现两个堆的动态平衡
	priority_queue<int,vector<int>,greater<int>> smaller; //小顶堆
	priority_queue<int,vector<int>,less<int>> bigger; //大顶堆
};

/**
 * Your MedianFinder object will be instantiated and called as such:
 * MedianFinder* obj = new MedianFinder();
 * obj->addNum(num);
 * double param_2 = obj->findMedian();
 */


int main()
{
	vector<int> nums = {1,5,46,4,6,3,34,5,67,34,53,23,22,12,8,67,55,66};
	MedianFinder* obj = new MedianFinder();
	for(auto i : nums)
		obj->addNum(i);
	double param_2 = obj->findMedian();
	cout << param_2 << endl;
	return 0;
}

运行结果


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值