数据流中的中位数

题目描述

如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数排

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

间两个数的平均值。我们使用Insert()方法读取数据流,使用GetMedian()方法获取当前读取

数据的中位数。

解题思路

1. 恕我直言,这道题也确实比较变态的了,剑指offer上也给出了各种能求解的数据结构,包
括数组、链表、BST、AVL树、最大堆以及最小堆。

2. 我根据剑指offer上给出的文字描述自行实现代码,其思路过程如下。

3. 首先我用ArrayList创建两个大小根堆,关于代码中的swim和sink方法分别代表堆里面的上
浮和下沉操作,这一部分上网或者查阅相关书籍查看如何实现。

4. 我需要获取到中位数,那么必须大根堆根节点数据小于等于小根堆的根节点数据,那么中
位数就可以从这两个中位数中获得,这是其一。

5. 当我需要添加数据的时候我可以直接将数据加入到大根堆中(默认加入较小数据中嘛),
然后对大根堆进行上浮操作保证堆的有序性。

6. 当大根堆有序了那么此时我需要比较两个堆的size大小,因为我要保证数据平衡就要两者
的size差距不能大于1,也即是说当max.size()-min.size()的时候我就需要调整了。

7. 调整的方法就是将大根堆的根节点放到小根堆里面,然后分别调整大小根堆保证有序。

8. 调整完成之后还需要检查大小根堆是否满足大根堆根节点<小根堆根节点,不满足则需要用
while循环进行循环调整直到满足条件。

9. 取中位数的过程就很简单了,如果大小根堆的size相等那么就是两个根节点相加除以2(记
得转换为double),其他情况那就是取大根堆的根节点即可。

Java源代码

import java.util.*;
public class Solution {
    private static ArrayList<Integer> min;
    private static ArrayList<Integer> max;
    //静态代码块,类加载时将两个静态变量初始化
    {
        min = new ArrayList<>();
        max = new ArrayList<>();
    }
    
    //大根堆上浮操作
    private static void swim_max(ArrayList<Integer> max, int k) {
        while (k>0 && max.get((k-1)/2)<max.get(k)) {
            exch(max, (k-1)/2, k);
            k = (k-1)/2;
        }
    }
    
    //小根堆上浮操作
    private static void swim_min(ArrayList<Integer> min, int k) {
        while (k>0 && min.get((k-1)/2)>min.get(k)) {
            exch(min, (k-1)/2, k);
            k = (k-1)/2;
        }
    }
    
    //大根堆下沉
    private static void sink_max(ArrayList<Integer> max, int k, int N) {
        while (2*k+1 < N) {
            int j = 2*k + 1;
            if (max.get(j) < max.get(j+1)) j++;
            if (max.get(k) >= max.get(j)) break;
            exch(max, k, j);
            k = j;
        }
    }
    
    //小根堆下沉
    private static void sink_min(ArrayList<Integer> min, int k, int N) {
        while (2*k+1 < N) {
            int j = 2*k + 1;
            if (min.get(j) > min.get(j+1)) j++;
            if (min.get(k) <= min.get(j)) break;
            exch(min, k, j);
            k = j;
        }
    }
    
    private static void exch(ArrayList<Integer> nums, int i, int j) {
        Integer temp = nums.get(i);
        nums.set(i, nums.get(j));
        nums.set(j, temp);
    } 
    
    
    public void Insert(Integer num) {
        //总是把数据插入大根堆中
        max.add(num);
        swim_max(max, max.size()-1);
        //校验大根堆和小根堆的长度
        if (max.size()-min.size()==2) {
            //将大根堆根数据移动到小根堆中
            min.add(max.get(0));
            swim_min(min, min.size()-1);
            //调整大根堆
            exch(max, 0, max.size()-1);
            max.remove(max.size()-1);
            sink_max(max, 0, max.size()-1);
        }
        //判断大根堆的根节点是否小于小根堆的根节点
        if (max.size() != 0 && min.size() != 0) {
            while (max.get(0) > min.get(0)) {
                int temp = max.get(0);
                max.set(0, min.get(0));
                min.set(0, temp);
                sink_max(max, 0, max.size()-1);
                sink_min(min, 0, min.size()-1);
            }
        }
    }

    public Double GetMedian() {
        //相等表示偶数,此时根节点相加除以2即可
        if (max.size()==min.size()) return ((max.get(0)+min.get(0))/2.0);
        //奇数,返回大根堆根节点即可
        else return (double)(max.get(0));
    }
}
    原文作者:zhouwaiqiang
    原文地址: https://www.jianshu.com/p/d26153052a6a
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞