C++ 动态中位数(Dynamic Median)

题目

输入一组整数 a 1 , a 2 , … , a n a1, a2, …, an a1,a2,,an ,每输入一个整数,输出到此时为止的中位数。时间复杂度不超过 O ( n l o g n ) O(nlogn) O(nlogn)

此处中位数定义:如果数串的大小是偶数 2 j 2j 2j,中位数是从小到大排列的第 j j j个数;如果数串的大小是奇数 2 j + 1 2j+1 2j+1,中位数是从小到大排列的第 j + 1 j+1 j+1 个数。

Sample

Input

-18 -2 14 -20 -6 7 2 14 11 6

Output

-18 -18 -2 -18 -6 -6 -2 -2 2 2

思路

利用快排的变式我们有 O ( n ) O(n) O(n)的寻找给定数组中位数的方法,但从本题时间复杂度不难分析出要求的每次求中位数的代价为 O ( l o g n ) O(logn) O(logn),于是联想到树/堆。

堆具有偏序特性,可以保证所有父节点的值大于(或小于)子节点的值。找最值很方便,取根节点就行,但这里需要的是中位数。有没有什么办法让根节点与中位数产生关系?很简单,让根节点是数组中较小的一半元素的最大值,就行了,于是我们便可以用两个堆构成的漏斗型结构来处理。

漏斗的连接处是两个堆的根节点,较小的一半元素存在下部的大顶堆中,较大的一半元素存在上部的小顶堆中。在元素的输入过程中维护这两个堆。于是取中位数便很简单了,找根节点就行了。

当然,这里还有一些细节,比如如何保证两个堆确实是接近一半一半呢?考虑到中位数的定义我们知道

  • h e a p M a x . s i z e = = h e a p M i n . s i z e ( + 1 ) heapMax.size==heapMin.size(+1) heapMax.size==heapMin.size(+1)时,大顶堆的根节点是中位数
  • h e a p M a x . s i z e = = h e a p M i n . s i z e − 1 heapMax.size==heapMin.size-1 heapMax.size==heapMin.size1时,小顶堆的根节点是中位数

当两个堆的元素不满足上述关系时,中位数便不是根节点。我们可以在发现 s i z e size size相差超过1时将大顶堆的根节点上浮或者把小顶堆的根节点元素下沉,保持这个关系,最后输出就会很方便。

代码

int main()
{
	Heap hMax(true);  // 大顶堆
	Heap hMin(false); //小顶堆
	int k;
	while (scanf("%d", &k))
	{	// ***每次添加/删除元素后都应进行堆的维护***
		
		// 初始放入
		if (hMax.get_len() == 0)
		{
			hMax.add_elem(k);
			printf("%d ", hMax.get_top());
			continue;
		}
		// 判断当前元素应该放在上面还是下面
		if (k < hMax.get_top())
		{
			hMax.add_elem(k);
		}
		else
		{
			hMin.add_elem(k);
		}
		// 保证两个heap元素数相差<=1
		if (hMax.get_len() > hMin.get_len() + 1)
		{
			int maxTop = hMax.pop();
			hMin.add_elem(maxTop);
		}
		else if (hMin.get_len() > hMax.get_len() + 1)
		{
			int minTop = hMin.pop();
			hMax.add_elem(minTop);
		}
		// 输出中位数
		if (hMax.get_len() >= hMin.get_len())
		{
			printf("%d ", hMax.get_top());
		}
		else
		{
			printf("%d ", hMin.get_top());
		}
		if (getchar() == '\n')
			break;
	}
	system("PAUSE");
	return 0;
}
  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值