题目:如何得到数据流中的中位数?
如果从数据流中读出奇数个数值,那么中位数就是所有数值排序后的中间的数值,如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后两个数的平均值。
分析:
1. 数组是最简单的数据容器,如果数组没有排序,可以使用partition函数
找到数组的中位数。在没有排序的数组中插入和找到一个中位数的时间
复杂度是 O(1) 和 O(n)
还可以往数组中插入新数据时让数组保持排序。由于可能需要移动 O(n)个、
数,因此需要 O(n) 时间才能完成插入操作。在已经排好序的数组中找到
一个中位数是一个简单的操作,只需要O(1)的时间。
2. 排序的链表是另外一种选择。我们需要 O(n) 的时间才可以在链表中找到
合适的位置插入新的数据。可以在 O(1) 的时间内得出中位数。
3. 二叉搜索树可以将插入新数据的平均时间降低到 O(logn)。但是当二叉
搜索树极度不平衡时看起来像一个排序的链表,插入新数据的时间仍然是
O(n)。为了得到中位数,可以在二叉树节点中添加一个表示子树结点数目的
字段。有了这个字段,可以在平均 O(logn) 的时间内得到中位数,但最差情况
仍然需要 O(n) 时间。
4. 为了避免二叉搜索树的最差情况,还可以使用平衡的二叉搜索树,即 AVL 树。
通常 AVL 树的平衡因子是左右子树的高度差。可以稍作修改,将 AVL 的平衡
因子改为左右子树的结点数目之差。可以用 O(logn) 的时间往 AVL 树中添加
一个新的结点,同时用 O(1) 的时间得到所有结点的中位数。
5. 如果数据在容器中已经排好序,那么中位数可以由 P1 和 P2指向的数得到。如果
容器中数据的数目是奇数,那么 P1和 P2指向同一个数据。
我们注意到整个数据容器被分割成两个部分。位于容器左半边的数据比右边的数据
小。另外, P1指向的数据是左边部分最大的数, P2指向的是数据是右边部分最小
的数,
如果能保证数据容器左边的数据都小于右边的数据,即使左右两边的数据都没有
排序,也可以根据左边最大的数和右边最小的数得到中位数。
使用最大堆和最小堆来实现:
首先保证数据平均分配到两个堆中,因此两个堆中数据之差不能超过1.为了实现
平均分配,可以在数据的总数目是偶数时把数据插入最小堆,否则插入最大堆。
还要保证最大堆中的所有数据都要小于最小堆中的数据。
当数据总数是偶数时,应当插入最小堆,但是如果此时这个新的数据比最大堆的
一些数据要小:可以先将新的数据插入最大堆,接着将最大堆中的数字拿出来插
入最小堆。由于插入最小堆的数字是原最大堆中最大的数字,这样就保证了最小
堆中的所有数字都大于最大堆中的数字。
class Solution {
public:
void Insert(int num)
{
if (((max.size() + min.size()) & 1) == 0) // 偶数应该插入最小堆
{
if (max.size() > 0 && max[0] > num){
// 将num先插入大顶堆
max.push_back(num);
push_heap(max.begin(), max.end(), less<int> ());
// 将大顶堆中的最大元素插入小顶堆
num = max[0];
// 将该元素从大顶堆删除
pop_heap(max.begin(), max.end(), less<int> ());
// pop_heap 会将该元素放到最后一位
max.pop_back();
}
// 将num插入最小堆
min.push_back(num);
// 建立最小堆
push_heap(min.begin(), min.end(), greater<int> ());
}
else // 奇数应该插入最大堆
{
if (min.size() > 0 && min[0] < num){
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(), less<int> ());
}
}
double GetMedian()
{
int size = max.size() + min.size();
double result;
double big = max[0];
double small = min[0];
if ((size % 2) == 1) result = small;
else result = (big + small) / 2;
return result;
}
private:
vector<int> max; // max为大顶堆
vector<int> min; // min为小顶堆
};