import java.util.ArrayList;
 import java.util.Comparator;
 import java.util.List;public class FindMedianInStream {
  
  private static class Heap<T>{
   //堆中元素存放的集合
   private List<T> data;
   //比较器
   private Comparator<T> cmp;
   //构造函数
   public Heap(Comparator<T> cmp){
    this.cmp=cmp;
    this.data=new ArrayList<>(64);
   }
   //向上调整堆, idx,被上移元素的起始位置
   public void shiftUp(int idx){
    if(idx<0||idx>=data.size()){
     throw new IllegalArgumentException(idx+"");
    }
    //获取开始调整的元素对象
    T intentT=data.get(idx);
    //如果不是根元素,则需要上移
    while(idx>0){
     int parentidx=(idx-1)/2;
     T parentT=data.get(parentidx);
     //上移条件,子节点比父节点大,此处定义的大是以比较器返回值为准
     if(cmp.compare(intentT, parentT)>0){
         //将父节点向下放
      data.set(idx, parentT);
      //记录父节点下放的位置
      idx=parentidx; 
     //子节点不比父节点大,说明父子路径已经按从大到小排好顺序了,不需要调整了
     }else{
      break;
     }
    }
    //index此时记录的是最后一个被下放的父节点的位置(也可能是自身)
    //所以将最开始的调整的元素值放入index位置即可
    data.set(idx, intentT);
   }
   //向下调整堆,idx被下移的元素的起始位置
   public void shiftDown(int idx){
    if(idx<0||idx>=data.size()){
     throw new IllegalArgumentException(idx+"");
    }
    T intentT=data.get(idx);
    int leftidex=idx*2+1;
    while(leftidex<data.size()){
     //取左节点的元素对象,并且假定其为2个子节点中最大的
     T maxchild=data.get(leftidex);
     //2个子节点中最大节点元素的位置,假定开始时为左子节点的位置
     int maxidx=leftidex;
     //获取右子节点的位置
     int rightidx=leftidex+1;
     if(rightidx<data.size()){
      T rightchildT=data.get(rightidx);
      //找出2个子节点中的最大子节点
      if(cmp.compare(rightchildT, maxchild)>0){
       maxchild=rightchildT;
       maxidx=rightidx;
      }
     }
     //如果最大子节点比父节点大,则需要向下调整
     if(cmp.compare(maxchild, intentT)>0){
      data.set(idx, maxchild);
      idx=maxidx;
      leftidex=2*idx+1;
     }
     //如果最大子节点不比父节点大,说明父子路径已经按照从大到小排好序了,不需要调整了
     else{
      break;
     }
    }
    data.set(idx, intentT);
   }
   //添加一个元素
   public void add(T item){
    //添加元素到最后
    data.add(item);
    //上移,以完成重构
    shiftUp(data.size()-1);
   }
   //删除堆顶节点
   public T deleteTop(){
    //如果堆已经为空,就抛出异常
    if(data.isEmpty()){
     throw new RuntimeException("The heap is empty.");
    }
    //获取堆顶元素
    T firstT=data.get(0);
    //删除最后一个元素
    T lastT=data.remove(data.size()-1);
    //删除元素后,如果堆为空的情况下,说明删除的元素也是堆顶元素
    if(data.size()==0){
     return lastT;
    }else{
     //将删除的元素放到堆顶
     data.set(0, lastT);
     //自上向下调整堆
     shiftDown(0);
     //返回堆顶元素
     return firstT;
    }
   }
   //获取堆顶节点,但不删除
   public T gettop(){
    if(data.isEmpty()){
     throw new RuntimeException("The heap is empty.");
    }
    return data.get(0);
   }
   //获取堆的大小
   public int size(){
    return data.size();
   }
   //判断堆是否为空
   public boolean isEmpty(){
    return data.isEmpty();
   }
   //清空堆
   public void clear(){
    data.clear();
   }
   //获取堆中的所有数据
   public List<T> getData(){
    return data;
   }
  }
  
  
  /**
   * 升序比较器
   * @param args
   */
  private static class IncComparator implements Comparator<Integer>{  @Override
   public int compare(Integer o1, Integer o2) {
    // TODO Auto-generated method stub
    return o1-o2;
   }
   
  }
  
  /**
   * 降序比较器
   * @param args
   */
  private static class DescComparator implements Comparator<Integer>{  @Override
   public int compare(Integer o1, Integer o2) {
    // TODO Auto-generated method stub
    return o2-o1;
   }
   
  }
  
  private static class DynamicArray{
   private Heap<Integer> max;
   private Heap<Integer> min;
   
   public DynamicArray(){
    max=new Heap<>(new IncComparator());
    min=new Heap<>(new DescComparator());
   }
   
   //插入数据
   public void insert(Integer num){
    //已经有偶数个数据了(可能没有数据)
    //数据总是偶数个时把新数据插入到小堆中
    if((min.size()+max.size())%2==0){
     //大堆中有数据,并且插入的元素比大堆中的元素小
     if(max.size()>0 && num<max.gettop()){
      //将num插入到大堆中
      max.add(num);
      //删除堆顶元素,大堆中的最大元素
      num=max.deleteTop();
     }
     min.add(num);
    }
    //数据总是奇数个时把新数据插入到大堆中
    else{
     //小堆中有数据,并且插入的元素比小堆中的元素大
     if(min.size()>0 && num>min.size()){
      min.add(num);
      num=min.deleteTop();
     }
     max.add(num);
    }
   }
   
   public double getMedian(){
    int size=max.size()+min.size();
    if(size==0){
     throw new RuntimeException("No number is available");
    }
    if((size&1)==1){
     return min.gettop();
    }else{
     return (max.gettop()+min.gettop())/2.0;
    }
   }
  } public static void main(String[] args) {
   DynamicArray array = new DynamicArray();
   array.insert(5);
   System.out.println(array.getMedian()); // 5  
   array.insert(2);
   System.out.println(array.getMedian()); // 3.5  
   array.insert(3);
   System.out.println(array.getMedian()); // 3  
   array.insert(4);
   System.out.println(array.getMedian()); // 3.5
   array.insert(1);
   System.out.println(array.getMedian()); // 3 
   array.insert(6);
      System.out.println(array.getMedian()); // 3.5 
      array.insert(7);
   System.out.println(array.getMedian()); // 4  
   array.insert(0);
         System.out.println(array.getMedian()); // 3.5
      array.insert(8);
         System.out.println(array.getMedian()); // 4
  }}