64 - 数据流中的中位数 || STL 堆

本文介绍了一种高效计算数据流中位数的方法,利用最大堆和最小堆动态存储数据,确保实时获取中位数。适用于数据量不断增长的场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目:
如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。


本文链接:http://blog.youkuaiyun.com/quzhongxin/article/details/47208927

思路解析

数据是从数据流读出,因此数组的个数是再逐渐的增加。如何选用一个容器,能够存储数据,并能够给出中位数。

  • 无序数组:插入O(1) partation操作找出中位数 O(n)
  • 有序数组:插入O(n) 找出中位数O(1)
  • 有序链表:插入O(n) 找出中位数O(1)
  • 搜索二叉树:插入O(logn)~O(n),找出中位数O(logn)~O(n)
  • AVL:插入O(logn),在节点中添加计数信息,找出中位数O(1)

如果将容器中的数据按照顺序排列,那么最中间的两个数或者一个数,可以将数据平均分成2部分。
那么以偶数个数据节点为例:最中间的两个数中的左侧数字,是前一半数据的最大值;中间数右侧数字,是后一半数据的最小值。因此只要保证,数据能够平均分成2部分,前一部分都小于后一部分,那么中位数是前一部分的最大值和后一部分的最小值的平均值。

为了实现O(1)得到最大最小值,采用最大堆保存前一半数据,最小堆保存后一半数据
为了实现平均:当容器中数据个数是偶数:插入到最小堆;总数是奇数,插入到最大堆。

当总数是偶数时,应该插入最小堆,但如果此时的数据小于最大堆的最大值,即按值大小来说,它应该属于前一部分(最大堆);因此我们将该值插入到最大堆,然后取出最大堆中的最大值,插入到最小堆。
同理,奇数时,数据大于最小堆的最小值,也应该先如最小堆,将最小堆中的最小值,移动到最大堆。

关于 C++ STL 堆的知识:

heap 的头文件 #include<algorithm>
注意:_First, _Last 是容器的迭代器,如vector。
参考:c++中STL之heap, priority_queue使用

1. make_heap

make_heap(_First, _Last)
make_heap(_First, _Last, _Comp) // 对2个迭代器之间的元素建堆

默认是建立最大堆的。对int类型,可以在第三个参数传入greater<int>()得到最小堆

2. push_heap

push_heap(_First, _Last) // 首先在容器中添加一个元素,然后push_heap,处理最后添加的一个元素

新添加一个元素在末尾,然后重新调整堆序。也就是把元素添加在底层vector的end()处。每次只能处理 1 个。

3. pop_heap

pop_heap(_First, _Last) // 先pop_heap,然后在容器中pop

这个算法跟push_heap类似,参数一样。不同的是我们把堆顶元素取出来,放到了数组或者是vector的末尾,用原来末尾元素去替代,然后end迭代器减1

4. sort_heap

.sort_heap(_First, _Last) // 堆排序

template<class T>
class DynamicArray {
private:
    vector<T> max; // 数组的前一半一半最大堆,最大堆中的数全部小于最小堆
    vector<T> min; // 数组的后一半构成最小堆,
public:
    void Insert(T num) {
        // 已有元素总数是偶数,插入最小堆
        if (((min.size() + max.size()) & 0x01) == 0) {
            if (max.size() > 0 && num < max[0]) {
                // num 小于最小堆的最小值,按值看应该插入最大堆,因此,先插入max,将max中的最大值,移动到min
                max.push_back(num);
                push_heap(max.begin(), max.end(), less<T>());
                num = max[0]; // 取出最大堆的最大值,插入到最小堆
                pop_heap(max.begin(), max.end(), less<T>());
                max.pop_back(); // pop 已经待删除元素交换到数组尾
            }
            min.push_back(num);
            push_heap(min.begin(), min.end(), greater<T>());
        } else {
            // 已有元素总数是奇数,插入最大堆
            if (min.size() > 0 && num > min[0]) {
                // num 大于最大堆的最大值,按值看应该插入最小堆
                min.push_back(num);
                push_heap(min.begin(), min.end(), greater<T>());
                num = min[0];
                pop_heap(min.begin(), min.end(), greater<T>());
                min.pop_back();
            }
            max.push_back(num);
            push_heap(max.begin(), max.end(), less<T>());
        }
    };
    T GetMedian() {
        int size = min.size() + max.size();
        if (size == 0)
            throw exception("No numbers are avliable");
        if ((size & 0x01) == 1)
            return min[0];
        else
            return (min[0]+max[0])/2;
    }
};

测试案例:《剑指offer》 GitHub

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值