从一个持续输出数字的流实时获取中位数
思路:一个大根堆一个小根堆,依次插入数字(大小根堆的都为空时首先插入大根堆),如果小于等于大根堆堆顶就插入大根堆,大于大根堆堆顶就放入小根堆,确保它们的元素个数相差不大于1,如果两个堆元素个数相差大于1,就将多的那个堆元素弹出堆顶放入另一个堆中。大根堆堆顶是值大的N/2个元素中的最大值,小根堆堆顶是值小的N/2个元素中的最小值,因此很容易在任何时候得出中位数是多少。将数字依次存入大根和小根堆时间复杂度只有树层高度的复杂度O(logN)
这个思路的重点在于大小堆的元素个数相差不大于1,一旦不满足就要调整,调整就需要堆顶元素的弹出和堆某个节点元素值发生改变需要重新调整堆
先实现swap函数
public static void swap(int[] arr, int index1, int index2){
int tmp = arr[index1];
arr[index1] = arr[index2];
arr[index2] = tmp;
}
参考上一篇博客的堆排序,分别实现大堆和小堆
heapInsert…函数用于新增一个节点后调整为堆
…Heapify函数用于堆一个节点数值发生改变调整使其任然为堆,在这个题目中,实际作用是,弹出堆顶元素,过程简单描述就是,堆顶和最后一个元素交换,堆元素减一,此时堆的状态就是只有堆顶元素数值发生改变,通过heapify继续调整其为堆,便完成了堆顶元素的弹出
大堆
//大堆
public static void bigRootPile(int[] arr){
if(arr == null || arr.length<2)
return ;
for(int i=0; i<arr.length; ++i)
heapInsertBig(arr, i); // 0~i
}
//大堆调整新增节点
public static void heapInsertBig(int[] arr, int index) {
while(arr[index] > arr[(index-1)/2]) { //精华语句,考虑了index=0时while终止
swap(arr, index, (index - 1) >> 1);
index = (index - 1) / 2;
}
}
//数组中一个元素值(index位置)发生变化,则调整使其[0, heapSize]范围继续成为大根堆
public static void bigRootHeapify(int[] arr, int index, int heapSize) { //[0, heapSize]范围上形成的堆
int left = index*2+1;
while(left < heapSize){
int largest = left + 1 < heapSize && arr[left+1] > arr[left]
? left + 1
: left;
largest = arr[largest] > arr[index] ? largest : index;
if(largest == index)
break;
swap(arr, largest, index);
index = largest;
left = index*2+1;
}
}
小堆
//小堆
public static void littleRootPile(int[] arr){
if(arr == null || arr.length<2)
return ;
for(int i=0; i<arr.length; ++i)
heapInsertlittle(arr, i); // 0~i
}
//小堆调整新增节点
public static void heapInsertlittle(int[] arr, int index) {
while(arr[index] < arr[(index-1)/2]) { //精华语句,考虑了index=0时while终止
swap(arr, index, (index - 1) >> 1);
index = (index - 1) / 2;
}
}
//数组中一个元素值(index位置)发生变化,则调整使其[0, heapSize]范围继续成为大根堆
public static void littleRootHeapify(int[] arr, int index, int heapSize) { //[0, heapSize]范围上形成的堆
int left = index*2+1;
while(left < heapSize){
int least = left + 1 < heapSize && arr[left+1] < arr[left]
? left + 1
: left;
least = arr[least] < arr[index] ? least : index;
if(least == index)
break;
swap(arr, least, index);
index = least;
left = index*2+1;
}
}
实时获取中位数的所有代码
采用大循环模拟整数数据流,由于时间复杂度特别优秀,因此可以满足大数量级的样本
package yzy.algorithm;
public class getMidEverySec {
public static void swap(int[] arr, int index1, int index2){
int tmp = arr[index1];
arr[index1] = arr[index2];
arr[index2] = tmp;
}
//大堆
public static void bigRootPile(int[] arr){
if(arr == null || arr.length<2)
return ;
for(int i=0; i<arr.length; ++i)
heapInsertBig(arr, i); // 0~i
}
//大堆调整新增节点
public static void heapInsertBig(int[] arr, int index) {
while(arr[index] > arr[(index-1)/2]) { //精华语句,考虑了index=0时while终止
swap(arr, index, (index - 1) >> 1);
index = (index - 1) / 2;
}
}
//数组中一个元素值(index位置)发生变化,则调整使其[0, heapSize]范围继续成为大根堆
public static void bigRootHeapify(int[] arr, int index, int heapSize) { //[0, heapSize]范围上形成的堆
int left = index*2+1;
while(left < heapSize){
int largest = left + 1 < heapSize && arr[left+1] > arr[left]
? left + 1
: left;
largest = arr[largest] > arr[index] ? largest : index;
if(largest == index)
break;
swap(arr, largest, index);
index = largest;
left = index*2+1;
}
}
//小堆
public static void littleRootPile(int[] arr){
if(arr == null || arr.length<2)
return ;
for(int i=0; i<arr.length; ++i)
heapInsertlittle(arr, i); // 0~i
}
//小堆调整新增节点
public static void heapInsertlittle(int[] arr, int index) {
while(arr[index] < arr[(index-1)/2]) { //精华语句,考虑了index=0时while终止
swap(arr, index, (index - 1) >> 1);
index = (index - 1) / 2;
}
}
//数组中一个元素值(index位置)发生变化,则调整使其[0, heapSize]范围继续成为大根堆
public static void littleRootHeapify(int[] arr, int index, int heapSize) { //[0, heapSize]范围上形成的堆
int left = index*2+1;
while(left < heapSize){
int least = left + 1 < heapSize && arr[left+1] < arr[left]
? left + 1
: left;
least = arr[least] < arr[index] ? least : index;
if(least == index)
break;
swap(arr, least, index);
index = least;
left = index*2+1;
}
}
//打印
public static void showArr(int[] arr, int size){
int i = 0;
while(i<size)
System.out.print(arr[i++]);
System.out.println();
}
public static void main(String[] args) {
//整数数据流:
int[] arr = {5, 4, 6, 3, 1};
//大根
int[] bigRoot = new int[1000];
//大根堆大小
int bigRootHeapSize = 0;
//小根
int[] littleRoot = new int[1000];
//小根堆大小
int littleRootHeapSize = 0;
//循环模拟数据流
for(int i=0; i<arr.length; ++i){
//无脑插入大小对
//大根堆初始没有元素
if(0 == bigRootHeapSize){
bigRoot[0] = arr[i];
heapInsertBig(bigRoot, bigRootHeapSize++);
}
//小于等于大根堆堆顶就插入大根堆
else if(arr[i] <= bigRoot[bigRootHeapSize-1]){
bigRoot[bigRootHeapSize] = arr[i];
heapInsertBig(bigRoot, bigRootHeapSize++);
}
//大于大根堆堆顶就放入小根堆
else{
littleRoot[littleRootHeapSize] = arr[i]; //插入元素
heapInsertlittle(littleRoot, littleRootHeapSize++); //调整为小堆
}
//调整大小堆,使它们元素个数相差不大于1
//大堆元素多,弹出堆顶元素放入另一个
if(bigRootHeapSize-littleRootHeapSize>1){
//弹出大堆堆顶
swap(bigRoot, 0, bigRootHeapSize-1);
bigRootHeapify(bigRoot, 0, --bigRootHeapSize-1);
//插入小堆
littleRoot[littleRootHeapSize] = bigRoot[bigRootHeapSize];
heapInsertlittle(littleRoot, littleRootHeapSize++);
}
else if(littleRootHeapSize-bigRootHeapSize>1){
//弹出小堆堆顶
swap(littleRoot, 0, littleRootHeapSize-1);
littleRootHeapify(littleRoot, 0, --littleRootHeapSize-1);
//插入大堆
bigRoot[bigRootHeapSize] = littleRoot[littleRootHeapSize];
heapInsertBig(bigRoot, bigRootHeapSize++);
}
//每一层循环代表一个时刻状态
System.out.print("大根堆:");
showArr(bigRoot, bigRootHeapSize);
System.out.print("小根堆:");
showArr(littleRoot, littleRootHeapSize);
int notNUll = 0; //记录当有一个堆是空堆时,另一个堆必然只有一个元素,便是当前状态的中位数
//中位数就是任意时刻大根堆和小根堆的堆顶元素之和的均值
if(0 != bigRootHeapSize)
System.out.print("大根堆堆顶元素:"+bigRoot[0]);
else{
System.out.print("大根堆空 ");
notNUll = littleRoot[0];
}
if(0 != littleRootHeapSize)
System.out.print(" 小根堆堆顶元素:"+littleRoot[0]);
else{
System.out.print(" 小根堆空 ");
notNUll = bigRoot[0];
}
System.out.println("当前中位数是"+(bigRootHeapSize>0&&littleRootHeapSize>0 ? (bigRoot[0]+littleRoot[0])/2 : notNUll));
System.out.println("---------------");
}
}
}
以上代码的执行结果如下: