题目:设计一个方法,要求这个方法可以不断输出一个数据流中的中位数(数据流中的数字整型并且随机)
思想:构建两个小根堆,其中一个小根堆存储的数据相对较小(堆B),另外一个小根堆存储的数据较大(堆A),并且构建两个变量,其中一个存储堆B的最大值,另一个存储堆A的最小值。每次接收一个数字的时候,先与堆A中的最小值比较;如果这个数字大于堆A中的最小值,则存储在堆A中。然后调整堆A和堆B中的数据量,使得他它们中的数据数目小于等于1。最后调整堆A为小根堆。如果这个数据小于堆A中最小值,则将这个数据存储在堆B中,然后调整堆A和堆B中的数据量,使得他它们中的数据数目小于等于1。调整完之后,将堆B调整为小根堆,并且同步堆B的最大值。输出:如果两个堆中的数据量相等,则中位数就是堆A的最小值和堆B的最大值相加除以2,如果堆A的数据量多于堆B,则中位数就是堆A的最小值,如果堆B的数据量多于堆A,则中位数就是堆B的最大值。
public class MedianRealTime {
//存储大数据的小根堆(堆A)
List<Integer> Max_heap = new ArrayList<>();
//存储小数据的小根堆(堆B)
List<Integer> Min_heap = new ArrayList<>();
public int MaxHeap_min; //A堆的最小值
public int MinHeap_Max; //B堆的最大值
public static final int Max_Value = Integer.MAX_VALUE;
/**
* 实时获取中位数的方法
* @param data
* @return
*/
public int getMiddleData(int data) {
//第一个数据进来存储在A堆,并且将A堆的最小值设置为第一个进来的数据
if (Max_heap == null) {
Max_heap.add(data);
MaxHeap_min = data;
} else {
balanceTwoHeaps(data);
}
//如果两个堆中的数据数目相等,那么中位数就是A堆的最小值与B堆的最大值除以2
if (Max_heap.size() == Min_heap.size()) {
return (MaxHeap_min + MinHeap_Max) / 2;
//如果A堆比B堆中数据的数目多,那么中位数就是A堆中的最小值
} else if (Max_heap.size() > Min_heap.size()) {
return MaxHeap_min;
//如果B堆比A堆中数据的数目多,那么中位数就是B堆中的最大值
} else
return MinHeap_Max;
}
/**
* 新加入一个数之后,使得两个堆的长度之差不超过1
* @param data
*/
private void balanceTwoHeaps(int data) {
//如果后来进入的数据大于A堆的最小值,则将这个数放在A堆
if (data > MaxHeap_min) {
Max_heap.add(data);
//调整两个堆中数据的数目,使两个堆中数据的数目的绝对值小于等于1
if (Math.abs(Max_heap.size() - Min_heap.size()) > 1) {
//将大根堆的最小值取出来
int x = MaxHeap_min;
//移除大根堆的最小值
Max_heap.remove(1);
//将这个最小值加入到小根堆里面
Min_heap.add(x);
//同步大根堆的最小值
MaxHeap_min = x;
}
//调整A堆为小根堆
HeapSort(Max_heap);
//如果后来进入的数据小于A堆的最小值,则将这个数放在B堆
} else {
Min_heap.add(data);
//调整两个堆中数据的数目,直到两个堆中数据的数目的绝对值小于等于1
if (Math.abs(Max_heap.size() - Min_heap.size()) > 1) {
//将小根堆的最大值取出来
int x = MinHeap_Max;
//并将它移除
Min_heap.remove(Min_heap.size() - 1);
//将这个数添加到A堆
Max_heap.add(x);
//同步A堆的最小值
MaxHeap_min = x;
HeapSort(Max_heap);
}
//调整完之后,将B堆调整为小根堆
HeapSort(Min_heap);
//并且同步B堆的最大值
MinHeap_Max = Min_heap.get(Min_heap.size() - 1);
}
}
/**
* 堆排序方法采用大根堆排序
* 首先构建大根堆,使得根元素为最大的,
* 堆排序时,将根元素与最后一个元素进行交换,再对前n-1个元素进行堆排序
*
* @param list
* @return
*/
private static List<Integer> buildMaxHeap(List<Integer> list) {
if (list.size() <= 1)
return list;
for (int i = (list.size() - 2) / 2; i >= 0; i--)
{
adjust(list, i, list.size());
}
return list;
}
private static void adjust(List<Integer> list, int k, int length) {
int tmp = list.get(k);
for (int i = 2 * k + 1; i < length; i = 2 * i + 1) {
if (i + 1 < length && list.get(i) < list.get(i + 1))
i++;
if (tmp > list.get(i)) {
break;
} else {
list.set(k, list.get(i));
k = i;
}
}
list.set(k, tmp);
}
private static void HeapSort(List<Integer> list) {
List<Integer> a = buildMaxHeap(list);
for (int i = a.size() - 1; i >= 0; i--) {
int tmp = a.get(0);
a.set(0, a.get(i));
a.set(i, tmp);
adjust(a, 0, i);
}
}
}
这个方法空间复杂度相对来说比较高,因此有优化的地方可以降低空间复杂度。
优化后的总体思路:对于每个不同数字构造一个节点。这个节点具有两个成员属性,其中一个是Value(数据),一个是Num(数据出现的次数)。每次接收一个数据,原来是直接比较后存储堆中,现在就是多了一个步骤,需要首先查找堆看是否已经存在关于这个数据的节点。还有一点,就是其实完全没有必要将两个堆中的数据量调整得非得相等,或者非得相差至多为1。可以适当地加大差值,只要求出中位数即可。