动态维护数列的中位数

转自 http://blog.sina.com.cn/s/blog_455b20c10100fmmb.html

问题陈述:有个需要动态更新(插入或删除)的数列L,现在需要随时获取到该数列的中位数,请设计相应的数据结构和算法。

 

算法:令L的中位数为m,用一个大顶堆存储数列L中不大于m的元素(即L按从小到大排列时的前半部分),用一个小顶堆存储数列L中不小于m的元素(即L按从小到大排列时的后半部分),其中这两个大小顶堆均不包含中位数m。每次往数列L插入新元素x时,若x<m,则将其插入大顶堆,否则插入小顶堆。若插入新元素后导致m不再是中位数(即两个堆的元素数目相差2个或2个以上),则将当前的中位数m插入到元素数量较少的那个堆中,然后令元素数量较多的那个堆的堆顶元素为新的中位数,并从该堆顶元素从堆中删除。

 

分析:容易看出,以上算法中,读取当前中位数的时间复杂度为O(1),插入和删除元素(包括调整堆)的时间复杂度为O(logN)。

 

发散:上述算法可以推广到解决维护数列的第k大(小)数的问题,只需要修改大、小顶堆的元素数目即可。


也可以用平衡树外加一个指针实时跟踪中位数变化,也是可以做到插入O(logn),提取O(1)的。
为了使用最大堆和最小堆维护数据流的中位数,需要动态地将数据分为两部分:一部分存储较小的数值(最大堆),另一部分存储较大的数值(最小堆)。通过保持两个堆的大小平衡,可以快速获取当前数据流的中位数。 ### 数据分布规则 - **最大堆**(`left`):用于存储较小的一半数据,堆顶是最大值。 - **最小堆**(`right`):用于存储较大的一半数据,堆顶是最小值。 - 两个堆的大小应满足以下条件: - 当总元素个数为偶数时,两个堆的元素数量相等。 - 当总元素个数为奇数时,最大堆比最小堆多一个元素(即 `left.size() == right.size() + 1`)。 ### 插入新元素 1. 如果最大堆为空或新元素小于等于最大堆的堆顶,则将其插入最大堆。 2. 否则,将其插入最小堆。 3. 插入后需要调整两个堆的大小,确保符合上述平衡条件: - 如果最大堆的大小比最小堆大超过1,则将最大堆的堆顶元素弹出并插入最小堆。 - 如果最小堆的大小大于最大堆的大小,则将最小堆的堆顶元素弹出并插入最大堆。 ### 获取中位数 - 如果两个堆的大小相同,则中位数是两个堆顶元素的平均值。 - 如果最大堆比最小堆大1,则中位数是最大堆的堆顶元素。 ### C++实现代码示例 ```cpp #include <iostream> #include <queue> using namespace std; class MedianFinder { private: priority_queue<int> left; // 最大堆 priority_queue<int, vector<int>, greater<int>> right; // 最小堆 public: // 添加数据流中的新数字 void addNum(int num) { if (left.empty() || num <= left.top()) { left.push(num); } else { right.push(num); } // 调整堆的大小以保持平衡 if (left.size() > right.size() + 1) { int top = left.top(); left.pop(); right.push(top); } else if (right.size() > left.size()) { int top = right.top(); right.pop(); left.push(top); } } // 返回当前数据流的中位数 double findMedian() { if (left.size() == right.size()) { return (left.top() + right.top()) / 2.0; } else { return left.top(); } } }; ``` ### 复杂度分析 - **插入操作**的时间复杂度为 `O(log n)`,因为每个元素最多只会在一次堆操作中被插入或弹出。 - **获取中位数**的时间复杂度为 `O(1)`,因为只需要访问堆顶元素。 ### 应用场景 - 实时计算数据流的中位数。 - 在线算法中对数据进行动态处理。 - 需要高效处理大量动态数据的应用,例如金融交易系统、实时数据分析工具等。 ### 示例运行过程 假设依次插入的数据为 `[1, 2, 3, 4, 5]`: 1. 插入 `1`:`left` 包含 `[1]`,`right` 为空,中位数为 `1`。 2. 插入 `2`:`left` 包含 `[1]`,`right` 包含 `[2]`,中位数为 `(1+2)/2=1.5`。 3. 插入 `3`:`left` 包含 `[2,1]`,`right` 包含 `[3]`,中位数为 `2`。 4. 插入 `4`:`left` 包含 `[2,1]`,`right` 包含 `[3,4]`,中位数为 `(2+3)/2=2.5`。 5. 插入 `5`:`left` 包含 `[3,2,1]`,`right` 包含 `[4,5]`,中位数为 `3`。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值