如何找到数组流中的中位数
题目
- 数据流中的中位数
- 如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值;
- 如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值;
分析
- 如果数组在容器中已经排序,那么中位数可以由p1和p2指向的数得到;如果容器中数据的数目是奇数,那么p1和p2指向同一个数据;
- 我们注意到整个数据容器被分割成两部分;位于容器左边部分的数据比右边数据小;另外,p1指向的数据是左边部分最大的数,p2指向 数据是左边部分最小的数;
- 如果能保证数据容器左边的数据都小于右边的数据,那么即使左、右两边内容的数据没有排序,也可以根据左边最大的数和右边最小的数得到中位数;
- 快速从一个数据容器中得到最大值,用大顶堆实现这个数据容器,因为位于堆顶的就是最大的数据;
- 同样,可以快速从小顶堆得到最小值;
- 用一个大顶堆实现左边的数据容器;用一个小顶堆实现右边的 数据容器;
- 往堆中插入一个数据的时间复杂度是O(logn);由于只需要O(1)时间就可以得到位于堆顶的数据,因此得到中位数的时间复杂度是O(1);
- 下表总结了使用没有排序的数组,排序的数组,排序的链表,二叉搜索树,AVL树,大顶堆和小顶堆几种不同数据结构的时间复杂度;
分析
- 为了实现平均分配,可以在数据的总数目(当前小顶堆和大顶堆的总数目,不是数组的总数目)是偶数时把新的数据插入最小堆,否则插入最大堆;
- 还要保证最大堆中的所有数据都小于最小堆中的数据;
- 当 当前的总数目是偶数时,按照前面的分配规则会把新的数据插入到小顶堆;如果此时的数据比大顶堆的第一个数小(也就是小于小顶堆的第一个数),怎么办?
- 就得把该数放到大顶堆里,但是为了平衡,得删除大顶堆的第一个数,放到小顶堆里;
- 当奇数的时候,按照前面的分配规则会把新的数据插入到大顶堆;如果此时的数据比小顶堆的第一个数大,就得放到小顶堆里,然后删除小顶堆的第一个元素,放到大顶堆里,为了平衡;
- 如果是偶数,就返回大小顶堆的第一个数,/2;
- 如果是奇数,就返回小顶堆的第一个数;
分析
- 所谓数据流,就是不会一次性读入所有数据,只能一个一个读取,每一步都要求能计算中位数。
- 定义两个全局变量,大(重写compart方法)小顶堆
- 小根堆是右边,大根堆是左边
- 1、插入insert()方法:
- 先判断是偶数还是奇数
- 偶数(放到小根堆)
- 当大根堆不为空的时候,才会有比较
- 如果大根堆不为空,并且该数 《 大根堆的第一个数(maxHeap。peek 》 num)
- 就得把这个数放到大根堆里
- 同时从大根堆里删除一个,这个数是要放进小根堆里的
- 把该数放到小根堆里(可能是原数,也可能是大根堆里的数)
- 否则
- 奇数(放到大根堆)
- 当小根堆不为空的时候,才会有比较
- 如果小根堆不为空,并且该数 》 小根堆的第一个数(minHeap。peek 《 num)
- 就得把该数放到小根堆里
- 同时从小根堆里删除一个,放到大根堆里(为了左右平衡)
- 把该数放到大根堆里(可能是原数,也可能是小根堆里的数)
- 先判断是偶数还是奇数
- 2、得到中位数getMedian():
- 鲁棒性检查(两个根加起来的长度不能等于0)
- 定义一个变量存中位数(double)
- 偶数
- 得到两个根的第一个元素
- 取中间值
- 奇数
- 得到小顶堆的第一个元素
- 最后返回中位数
package No15_优化空间和时间效率;
import java.util.Comparator;
import java.util.PriorityQueue;
//数据流中的中位数
//因为是流,所以只能一个一个的读取,依次求其中位数
public class StreamMedian {
//定义两个堆,一个大根堆,一个小根堆
//小根堆,因为默认就是小根堆
PriorityQueue<Integer> minHeap = new PriorityQueue();
//大根堆Comparator.reverseOrder(),或者是自己重写compare方法
PriorityQueue<Integer> maxHeap = new PriorityQueue(Comparator.reverseOrder());
PriorityQueue<Integer> maxHeap1 = new PriorityQueue(){
public int compare(int i1,int i2){
return i2 - i1;
}
};
public void insert(int num){
//如果是偶数
if(((minHeap.size()+maxHeap.size())&1) == 0){
//查看是否可以直接插入到小根堆里
//如果这个数小于大根堆里的一个数,就得放到大根堆里,同时大根堆得删除一个,放到小根堆里
if(!maxHeap.isEmpty() && maxHeap.peek() > num){
maxHeap.offer(num);
num = maxHeap.poll();
}
//到最后,无论如何都得插入一个数到小根堆里,为了平衡
minHeap.offer(num);
}
//奇数,插入到大根堆里
else{
if(!minHeap.isEmpty() && minHeap.peek() < num){
minHeap.offer(num);
num = minHeap.poll();
}
maxHeap.offer(num);
}
}
//找中位数
public int getMedian(){
//鲁邦性检查
if((minHeap.size() + maxHeap.size()) == 0){
return -1;
}
//偶数
if(((minHeap.size()+maxHeap.size())&1) == 0){
int n1 = minHeap.peek();
int n2 = maxHeap.peek();
return (n1+n2)/2;
}
//奇数
else{
return minHeap.peek();
}
}
// ====================测试代码====================
void test(String testName,int expected){
if(testName != null)
System.out.printf("%s begins: ", testName);
if(getMedian()==expected)
System.out.printf("Passed.\n");
else
System.out.printf("FAILED.\n");
}
public static void main( String args[]){
StreamMedian sm=new StreamMedian();
sm.insert(5);
sm.test("Test1", 5);//5
sm.insert(2);
sm.test("Test2", 3);//2 5
sm.insert(3);
sm.test("Test3", 3);//2 3 5
sm.insert(4);
sm.test("Test4", 3);// 2 3 4 5
sm.insert(1);
sm.test("Test5", 3);//1 2 3 4 5
sm.insert(6);
//这个的中位数应该是3
sm.test("Test6", 4);// 1 2 3 4 5 6
sm.insert(7);
sm.test("Test7", 4); // 1 2 3 4 5 6 7
sm.insert(0);
sm.test("Test8", 4);// 0 1 2 3 4 5 6 7
sm.insert(8);
//这个的中位数应该是3
sm.test("Test9", 4);//0 1 2 3 4 5 6 7 8
}
}