数据流中的中位数

作者: zhouwaiqiang | 来源:发表于2019-03-25 19:12 被阅读42次

    题目描述

    如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数排
    
    序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之中
    
    间两个数的平均值。我们使用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));
        }
    }
    

    相关文章

      网友评论

        本文标题:数据流中的中位数

        本文链接:https://www.haomeiwen.com/subject/jkobvqtx.html