数据流中的中位数

题目描述

如何得到一个数据流中的中位数?

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

思路1

维护一个数组,每次加入后,进行排序,当总元素个数为奇数时,中位数就是数组中间的元素;当总元素个数为偶数时,中位数就是数组中间元素和前一个元素的平均数。

思路2

Java 1.5版本后就提供了一个具备了小根堆性质的数据结构也就是优先队列PriorityQueue。

PriorityQueue的数据结构
在这里插入图片描述
PriorityQueue的逻辑结构是一棵完全二叉树,存储结构其实是一个数组。逻辑结构层次遍历的结果刚好是一个数组。

PriorityQueue的操作
①add(E e) 和 offer(E e) 方法
add(E e) 和 offer(E e) 方法都是向PriorityQueue中加入一个元素
② poll() 和 remove() 方法
poll 方法每次从 PriorityQueue 的头部删除一个节点,也就是从小顶堆的堆顶删除一个节点,而remove()不仅可以删除头节点而且还可以用 remove(Object o) 来删除堆中的与给定对象相同的最先出现的对象。

PriorityQueue实现大顶堆
PriorityQueue默认是一个小顶堆,然而可以通过传入自定义的Comparator函数来实现大顶堆。如下代码实现了一个初始大小为11的大顶堆:

private static final int DEFAULT_INITIAL_CAPACITY = 11;
PriorityQueue<Integer> maxHeap=new PriorityQueue<Integer>(DEFAULT_INITIAL_CAPACITY, new Comparator<Integer>() {
        @Override
        public int compare(Integer o1, Integer o2) {                
            return o2-o1;
        }
    });

这个题另外一种思路就是通过维护一个大顶堆,一个小顶堆,且保证两点:

  • 小顶堆里的元素全大于大顶堆里的元素,即小顶堆里维护的是数据流中后一部分较大值,大顶堆维护的是数据流中前一部分的较小值;
  • 两个堆个数的差值小于等于1,元素个数为偶数让两个堆中元素个数相同,为奇数时让小顶堆元素个数比大顶堆多1个。则当总元素个数为奇数时,中位数就是小顶堆堆顶;当总元素个数为偶数时,中位数就是两个堆堆顶平均数。
    在这里插入图片描述

为了让大顶堆的元素都小于小顶堆中元素,在将元素依次插入两个堆中时需要注意:

  • n为偶数时插入元素到小顶堆,因为小顶堆元素都要大于大顶堆,但是新插入的元素不一定比大顶堆元素大,因此要先将元素插入大顶堆,然后利用大顶堆的特点,取出堆顶元素即为最大元素,此时再插入小顶堆!
  • n为奇数时同理先插入到小顶堆中,然后取出小顶堆堆顶即最小元素插入到大顶堆中。

这样既保证了小顶堆和大顶堆元素个数相差不大于1,又能保证大顶堆的元素都小于小顶堆中元素。

整体代码

package com.zhumq.leetcode;
import java.util.ArrayList;
import java.util.Collections;
import java.util.PriorityQueue;
import java.util.Scanner;

import org.junit.Test;

public class GetMedian {
	ArrayList<Integer> list = new ArrayList<>();
    // 大顶堆(直接用lambda表达式)
    PriorityQueue<Integer> bigHeap = new PriorityQueue<>((o1, o2) -> o2 - o1);
    // 小顶堆,并且小顶堆元素都大于大顶堆
    PriorityQueue<Integer> smallHeap = new PriorityQueue<>();
    // 当前数据流读入的元素个数
    int N = 0;
    
    /**
     * 解法1:维护两个堆,一个大顶堆、一个小顶堆
     */
    public void insert1(Integer num) {
        // 插入要保证两个堆存于平衡状态
        if (N % 2 == 0) {
            // N 为偶数插入到小顶堆,奇数插入大顶堆
            // 因为小顶堆元素都要大于大顶堆,但是新插入的元素不一定比大顶堆元素大,因此先将元素插入大顶堆,然后利用大顶堆的特点,取出堆顶元素即为最大元素,此时再插入小顶堆!
            bigHeap.offer(num);
            smallHeap.offer(bigHeap.poll());
        } else {
            //同理
        	smallHeap.offer(num);
            bigHeap.offer(smallHeap.poll());
        }
        N++;
    }
    /*
     * 总元素个数为奇数时,中位数就是数组中间的元素;当总元素个数为偶数时,中位数就是数组中间元素和前一个元素的平均数。
     */
    public Double getMedian1() {
        if (N % 2 == 0) {
            return (bigHeap.peek() + smallHeap.peek()) / 2.0;
        } else {
            return (double) smallHeap.peek();
        }
    }

    /**
     * 解法2:维护数组,加入时排序
     */
    public void insert2(Integer num) {
        list.add(num);
        Collections.sort(list);
    }
    /*
     * 总元素个数为奇数时,中位数就是数组中间的元素;当总元素个数为偶数时,中位数就是数组中间元素和前一个元素的平均数。
     */
    public Double getMedian2() {
        int n = list.size();
        if (n % 2 == 0) {
            return Double.valueOf((list.get(n / 2) + list.get(n / 2 - 1)) / 2.0);
        } else {
            return Double.valueOf(list.get(n / 2));
        }
    }
    
    @Test
    public void test1() {
    	Scanner sc = new Scanner(System.in);
    	while(sc.hasNext()) {
    		int len = sc.nextInt();
    		for(int i = 0;i<len;i++) {
    			int num = sc.nextInt();
    			insert1(num);
    			insert2(num);
    			System.out.println(getMedian1());
    			System.out.println(getMedian2());
    		}
    	}
    	sc.close();
    }
}

运行结果如下:
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
计算数据流位数可以通过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、付费专栏及课程。

余额充值