数据流中的中位数

本文探讨了在数据流中实时计算中位数的有效方法,包括使用平衡二叉树、大顶堆和小顶堆的策略,以及优先队列的实现方式。提供了详细的代码示例和解释。

剑指OFFER题40------按牛客网通过率排序

时间:2019.1.4.2206
作者:Waitt

题目

如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。我们使用Insert()方法读取数据流,使用GetMedian()方法获取当前读取数据的中位数。

时间限制:1秒 空间限制:32768K 热度指数:99031

解法

解法1

利vector保存数值,每次插入数值之后重新对向量进行排序,取出中间的数据

解法2

利用平衡二叉树保存数据,但是一般AVL树并无标准库实现,自己写插入和删除操作比较繁琐。

解法3

利用一个大顶堆和一个小顶堆存放数据,大顶堆的数据均比小顶堆小。大顶堆的容量<=小顶堆容量+1。
数据数目为奇数时,返回大顶堆的堆顶;
数据数目为偶数时,返回(大顶堆堆顶+小顶堆堆顶)/2.0;由于返回时数据类型可能为double,所以此处应除2.0

利用优先队列实现大顶堆和小顶堆:priority_queue

class Solution {
public:
    priority_queue<int, vector<int> > a;//大顶堆
    priority_queue<int, vector<int>, greater<int> > b;//小顶堆
    int c=0;//计数
    void Insert(int num)
    {
        c++;
        a.push(num);//每次都先存入大顶堆中
        if(c%2==0)//当为偶数时,将大顶堆的堆顶存入小顶堆
        {
            b.push(a.top());
            a.pop();
        }
        if(c>2)//由于每次都是存入大顶堆,故总数为奇数时,需要查验大顶堆的堆顶是否比小顶堆的堆顶小
        {
            if(c%2!=0)//当为奇数时,进行查验,确保小顶堆的所有数据都比大顶堆的大
            {
                if(a.top()>b.top())
                //当大顶堆的堆顶大于小顶堆的堆顶时,进行数据替换。确保小顶堆的所有数据都比大顶堆的大
                {
                    b.push(a.top());
                    a.pop();
                    a.push(b.top());
                    b.pop();
                }
            }
        }
    }

    double GetMedian()
    { 
        if(c%2==0)
            return (a.top()+b.top())/2.0;//为了确保返回的为double,此处应除以2.0
        else
            return a.top();
    }

};

利用make_heap创建堆进行实现
由于此题是一个一个输入数据,因此无需用make_heap,用push_heap即可

class Solution {
public:
    vector<int> a,b;//大顶堆a,小顶堆b
    void Insert(int num)
    {
        if(a.empty()||num<a[0])//小数放入小顶堆
        {
            a.push_back(num);
            //由于此题是一个一个输入数据,因此无需用make_heap,用push_heap即可
            push_heap(a.begin(),a.end());
        }
        else//大数放入大顶堆
        {
            b.push_back(num);
            push_heap(b.begin(),b.end(),greater<>());
        }
        if(a.size()>(b.size()+1))//数组容量判断
        {
            b.push_back(a[0]);
            push_heap(b.begin(),b.end(),greater<>());
            pop_heap(a.begin(),a.end());
            a.pop_back();
        }
        if(b.size()>a.size())//数组容量判断
        {
            a.push_back(b[0]);
            push_heap(a.begin(),a.end());
            pop_heap(b.begin(),b.end(),greater<>());
            b.pop_back();
        }
    }
    double GetMedian()
    { 
        if(a.size()>b.size())//说明此时为奇数个数
            return a[0];
        return (a[0]+b[0])/2.0;
    }

};

TIPS

优先队列的本质是用堆实现的,其拥有队列的所有属性。
其默认顺序与堆一样:less<>为大顶堆。当需要小顶堆时:greater<>.
优先队列priority_queue:https://blog.youkuaiyun.com/AAMahone/article/details/82787184
优先队列priority_queue:https://blog.youkuaiyun.com/weixin_36888577/article/details/79937886

计算数据流中位数可以通过Flink的ProcessFunction来实现。 具体实现步骤如下: 1. 将数据流按照大小排序 2. 计算数据流的长度,如果是奇数,则中位数为第 (length+1)/2 个元素;如果是偶数,则中位数为第length/2个元素和第(length/2+1)个元素的平均值。 3. 在ProcessFunction的实现中,可以使用状态变量来保存数据流的有序列表,并计算中位数。 以下是一个简单的示例代码: ```java public class MedianFunction extends ProcessFunction<Integer, Double> { private ListState<Integer> values; @Override public void open(Configuration parameters) throws Exception { super.open(parameters); values = getRuntimeContext().getListState(new ListStateDescriptor<Integer>("values", Integer.class)); } @Override public void processElement(Integer value, Context ctx, Collector<Double> out) throws Exception { values.add(value); List<Integer> sortedValues = new ArrayList<>(); for (Integer v : values.get()) { sortedValues.add(v); } Collections.sort(sortedValues); int length = sortedValues.size(); if (length % 2 == 0) { double median = (sortedValues.get(length/2) + sortedValues.get(length/2 - 1)) / 2.0; out.collect(median); } else { double median = sortedValues.get(length/2); out.collect(median); } } } ``` 在上述代码中,我们使用了ListState来保存数据流中的元素,并在每次处理新元素时重新排序并计算中位数。注意,这只是一个简单的示例,实际应用中需要考虑更多的问题,比如数据倾斜、数据丢失等。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值