题目描述
如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。我们使用Insert()方法读取数据流,使用GetMedian()方法获取当前读取数据的中位数。
方法一:大小堆
1、什么是堆?(参考:https://www.cnblogs.com/lanhaicode/p/10546257.html)
堆是一种非线性结构,可以把堆看作一个数组,也可以被看作一个完全二叉树,通俗来讲堆其实就是利用完全二叉树的结构来维护的一维数组
按照堆的特点可以把堆分为大顶堆和小顶堆
大顶堆:每个结点的值都大于或等于其左右孩子结点的值
小顶堆:每个结点的值都小于或等于其左右孩子结点的值
(图片来源:https://www.cnblogs.com/chengxiao/p/6129630.html)
我们对堆中的结点按层进行编号,将这种逻辑结构映射到数组中就是下面这个样子
我们用简单的公式来描述一下堆的定义就是:(读者可以对照上图的数组来理解下面两个公式)
大顶堆:arr[i] >= arr[2i+1] && arr[i] >= arr[2i+2]
小顶堆:arr[i] <= arr[2i+1] && arr[i] <= arr[2i+2]
堆排序见我之前的文章——十大经典排序算法
按这个写:
import java.util.*;
class Solution {
Queue<Integer> left = new PriorityQueue<>((o1, o2) -> (o2 - o1));大顶堆
Queue<Integer> right = new PriorityQueue<>((o1, o2) -> (o1 - o2));小顶堆
int count = 0;
public void Insert(Integer num) {
count++;
//边界条件
if (count % 2 == 1) {
left.add(num);
right.add(left.poll());
} else if (count % 2 == 0) {
right.add(num);
left.add(right.poll());
}
}
public Double GetMedian() {
if (count % 2 == 1) {
return (double)(right.peek());
} else if (count % 2 == 0) {//注意:这里如果是else,下面就不用加0.0了
// return (double)((left.peek() + right.peek())/2);//注意这里不能除2,会变成商的,应该除以2.0
return (left.peek() + right.peek())/2.0;
}
return 0.0;
}
}
public class Main {
public static void main(String[] args) {
int[] nums = {7, 5, 4, 2, 1, 6, 3};
Solution p = new Solution();
for (int i = 0; i < nums.length; i++) {
p.Insert(nums[i]);
}
Double res = p.GetMedian();
System.out.println("序列化" + res);
}
}
原来的:
package test;
import java.util.PriorityQueue;
import java.util.Queue;
class Solution {
/* 大顶堆,存储左半边元素 */
Queue<Integer> left;
/* 小顶堆,存储右半边元素,并且右半边元素都大于左半边 */
Queue<Integer> right;
public void MedianFinder() {
/* 大顶堆,存储左半边元素 */
// B = new PriorityQueue<>((x, y) -> (x - y)); 是小顶堆,顶部最小的数
// B = new PriorityQueue<>((x, y) -> (y - x)); 是大顶堆,顶部最大的数
left = new PriorityQueue<>((o1, o2) -> (o2 - o1));
/* 小顶堆,存储右半边元素,并且右半边元素都大于左半边 */
right = new PriorityQueue<>();//小顶堆
}
/* 当前数据流读入的元素个数 */
private int N = 0;
public void Insert(Integer num) {
/* 插入要保证两个堆存于平衡状态 */
if (N % 2 == 0) {
/* N 为偶数的情况下插入到右半边。
* 因为右半边元素都要大于左半边,但是新插入的元素不一定比左半边元素的大,(就是这么理解,
* 最大堆第一个放最大值,最小堆第一个放最小值)
* 因此需要先将元素插入左半边,然后利用左半边为大顶堆的特点,取出堆顶元素即为最大元素,此时插入右半边
*/
left.add(num);
right.add(left.poll());
} else {
right.add(num);
left.add(right.poll());
}
N++;
}
public Double GetMedian() {
if (N % 2 == 0)
return (left.peek() + right.peek()) / 2.0;
else
return (double) right.peek();
}
}
public class Main {
public static void main(String[] args) {
// 3 1 2
int[] temp = {1,2,5,7,3,4};//1 2 3 4 5 7 4 7 5
Solution p = new Solution();
p.MedianFinder();
for(int i = 0; i < temp.length; i++){
p.Insert(temp[i]);
}
System.out.println("输出大顶堆插入结果"+p.left);
System.out.println("输出小顶堆插入结果"+p.right);
Double b = p.GetMedian();
System.out.println("输出中位数"+b);
}
}
方法二:首先是由Collection.sort()进行升序排列,然后return中位数呗。
package test;
import java.util.Collections;
import java.util.ArrayList;
class Solution {
ArrayList<Integer> list = new ArrayList<>();
public void Insert(Integer num) {
list.add(num);
Collections.sort(list);//对list进行升序排序
}
public Double GetMedian() {
int n = list.size();
if (n % 2 == 0) {
return Double.valueOf((list.get(n / 2) + list.get(n / 2 - 1)) / 2.0);
} else {
return Double.valueOf(list.get(n / 2));
}
}
}
public class Main {
public static void main(String[] args) {
int[] temp = {1,2,5,7,3,4};
Solution p = new Solution();
for(int i = 0; i < temp.length; i++){
p.Insert(temp[i]);
}
System.out.println("输出插入结果"+p.list);
Double b = p.GetMedian();
System.out.println("输出中位数"+b);
}
}