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