63.数据流中的中位数
如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。我们使用Insert()方法读取数据流,使用GetMedian()方法获取当前读取数据的中位数。
方法1:用java集合PriorityQueue来设置两个堆,一个大顶堆,一个小顶堆来过滤数据。
主要的思想是:因为要求的是中位数,那么这两个堆,大顶堆用来存较小的数,从大到小排列;小顶堆存较大的数,从小到大的顺序排序*,显然中位数就是大顶堆的根节点与小顶堆的根节点和的平均数。
⭐保证:小顶堆中的元素都大于等于大顶堆中的元素,所以每次塞值,并不是直接塞进去,而是从另一个堆中poll出一个最大(最小)的塞值
⭐当数目为偶数的时候,将这个值插入大顶堆中,再将大顶堆中根节点(即最大值)插入到小顶堆中;
⭐当数目为奇数的时候,将这个值插入小顶堆中,再讲小顶堆中根节点(即最小值)插入到大顶堆中;
⭐取中位数的时候,如果当前个数为偶数,显然是取小顶堆和大顶堆根结点的平均值;如果当前个数为奇数,显然是取小顶堆的根节点
//PriorityQueue 是从JDK1.5开始提供的新的数据结构接口,默认内部是自然排序,结果为小顶堆,就是小的在前面;
//也可以自定义排序器实现Comparator接口,重写compare(object o1,object o2)方法
//默认o1>o2返回正数1,新添加的元素都要比已有的o2大,否则调整堆结构,
//重写后o2>o1返回1,新增加元素o1要比已有的o2小,否则调整堆结构。
//因为用了PriorityQueue,这里不自己写堆排序了。最大堆放比中位数小的值,最小堆放比中位数大的值
//当当前序列为偶数(即大小堆总数为偶),则给最小堆添加一元素,
//此时判断新元素是否比最大堆的堆顶元素小,若是则调整最大堆,把新元素放入最大堆(PriorityQueue会自己调整)
//再把堆顶元素取出放入最小堆(为了保持两堆元素平均分配,因为此时序列为奇数了,下一回合添加的元素是添加到最大堆)。
//后面以此类推。。。
import java.util.Comparator;
import java.util.PriorityQueue;
public class Solution {
int count;//count用于控制小顶堆和大顶堆个数相差不超过1
PriorityQueue<Integer> minHeap=new PriorityQueue<Integer>();
//Creates a PriorityQueue 默认的初始容量设为11
PriorityQueue<Integer> maxHeap=new PriorityQueue<Integer>(11,new Comparator<Integer>(){
@Override
public int compare(Integer o1,Integer o2){
//PriorityQueue默认是小顶堆,实现大顶堆,需要反转默认排序器
return o2.compareTo(o1);
}
});
public void Insert(Integer num) {
count++;
if((count&1)==0){// 判断偶数的高效写法
//当数据总数为偶数时,新加入的元素,应当进入小根堆
//(注意不是直接进入小根堆,而是经大根堆筛选后取大根堆中最大元素进入小根堆)
//1.新加入的元素先入到大根堆,由大根堆筛选出堆中最大的元素
if(!maxHeap.isEmpty()&&num<maxHeap.peek()){
maxHeap.offer(num);
num=maxHeap.poll();
}
//2.筛选后的【大根堆中的最大元素】进入小根堆
minHeap.offer(num);
}else{
//当数据总数为奇数时,新加入的元素,应当进入大根堆
//(注意不是直接进入大根堆,而是经小根堆筛选后取小根堆中最大元素进入大根堆)
//1.新加入的元素先入到小根堆,由小根堆筛选出堆中最小的元素
if(!minHeap.isEmpty()&&num>minHeap.peek()){
minHeap.offer(num);
num=minHeap.poll();
}
maxHeap.offer(num);
}
}
public Double GetMedian() {
if(count==0)
throw new RuntimeException("no available number!");
double res;
//总数为奇数时,大顶堆堆顶就是中位数
if((count&1)==1)
res=maxHeap.peek();
else
res=(minHeap.peek()+maxHeap.peek())/2.0;
return res;
}
}
方法2:
import java.util.*;
public class Solution {
ArrayList<Integer> res=new ArrayList<>();
public void Insert(Integer num) {
res.add(num);
Collections.sort(res);
}
public Double GetMedian() {
int n=res.size();
if(n%2==0){
return Double.valueOf((res.get(n/2)+res.get(n/2-1))/2.0);
}else{
return Double.valueOf(res.get(n/2));
}
}
}
堆知识点了解:
堆(二叉堆):是一种用于实现优先队列模型的数据结构
堆排序:堆积是一个近似完全二叉树的结构(树的所有内部节点都被完全填充,最后一层可以完全填充的或部分填充)
堆排序的平均时间复杂度为Ο(nlogn) 。
堆排序算法步骤:
**步骤一 :**构造初始堆。将无需序列构建成一个堆,根据升序降序需求选择大顶堆或小顶堆;–新插入的数据与其父结点比较
步骤二 :将堆顶元素与末尾元素交换,将最大元素"沉"到数组末端;
**步骤三:**重新调整结构,使其满足堆定义,然后继续交换堆顶元素与当前末尾元素,反复执行调整+交换步骤,直到整个序列有序。
大顶堆:每个结点的值都大于其左孩子和右孩子结点的值
小顶堆:每个结点的值都小于其左孩子和右孩子结点的值