题目
输入一组整数 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.size−1时,小顶堆的根节点是中位数
当两个堆的元素不满足上述关系时,中位数便不是根节点。我们可以在发现 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;
}