大数据求中位数(插值计算)

如今在大量数据(至少以亿计)铺面而来的情况下对于计算的要求也越来越高,因此需要一个较好的算法对数据进行处理。由于本人初入大数据领域写的不好敬请见谅。

常规(数据量不大的情况)求中位数和基于插值计算求中位数的方法

在学数学时我们学到过求中位数的方法,在数据个数为偶数时找最中间的两个数然后求平均数如果数据个数为奇数时则只需找数据个数一半的那位上的数字即可代表中位数(前提是数组是有序的)

java代码如下:

	public static double findMedianVersion1(int[] intArray) {
    
    
		if(intArray == null || intArray.length == 0) {
    
    
			return -1;
		}
		//1.从小到大排序
		Arrays.sort(intArray);
		//2.获取中间数的位置
		int medianIndex = 0;
		medianIndex = intArray.length/2;
		if(intArray.length%2 == 0) {
    
    
			return (intArray[medianIndex] + intArray[medianIndex-1])/2.0;
		}else {
    
    
			return intArray[medianIndex];
		}
	}

这种计算中位数的方式很简单但是会存在一个问题就是先对数组进行排序在遇到大量数据的情况下会极大地增加算法的时间复杂度。
因此有学者就提出了如下的算法:
在这里插入图片描述
其中L1为中位数区间的下界,N/2为中位数在整个数组中的位置,(∑freq)l 表示中位数区间之前的频数(数据值个数)的和,(N/2 - (∑freq)l) 表示中位数在中位数分组中的位置, freq median 表示中位数所在区间的频数,width表示组距。【注:图中的加号应改为减号】
该公式的好处在于可以不经过排序即可确定中位数。代码如下:

	public static double findMedianVersion2(int[] intArray) {
    
    
		if(intArray == null || intArray.length == 0) {
    
    
			return -1;
		}
		//1.分组
		//1.1 获取最大值和最小值便于分组
		int max = Integer.MIN_VALUE;
		int min = Integer.MAX_VALUE;
		int groupWidth = 0;
		for(int i = 0;i<intArray.length;i++) {
    
    
			if(intArray[i] > max) {
    
    
				max = intArray[i];
			}
			if(intArray[i] <min) {
    
    
				min = intArray[i];
			}
		}
		//1.2 获取每组的宽度
		if(max-min <= groupMaxSize) {
    
    
			groupWidth = 1;
		}else {
    
    
			groupWidth = (max-min)/groupMaxSize;
			//对组距进行适当的放大以方便统计
			if(groupWidth > 100) {
    
    
				groupWidth = 100 + groupWidth - groupWidth%100;
			}else {
    
    
				groupWidth = 10 + groupWidth - groupWidth%10;
			}
		}
		//1.3 获取真实的组数
		int groupSize = max%groupWidth != 0 ? (1+max/groupWidth):max/groupWidth;
		if(groupWidth == 1) {
    
    //为了防止下标越界
			groupSize++;
		}
		int[] group = new int[groupSize];
		//1.4 将数据放入组中
		for(int i:intArray) {
    
    
			group[i/groupWidth]++;
		}
		//2.根据插值计算公式估计中位数
		//2.1获取低于中位数区间所有区间的频率和i以及中位数区间下界low以及中位数区间的频率i1
		int medianIndex = 0,i = 0,i1 = 0,j=0,low = 0;
		medianIndex = intArray.length/2;
		while(true) {
    
    
			if(i+group[j]>=medianIndex) {
    
    
				low = j*groupWidth;
				i1 = group[j];
				break;
			}
			i += group[j];
			j++;
		}
		double median = low + (medianIndex-i)/(i1*1.0)*groupWidth;
		return median;
	}

实验结果

1.测试大量数据情况下两种算法的性能
实验代码如下

	public static void main(String[] args) {
    
    
		//随机生成1亿个3到100000的整数
		int i[] = IntegerUtils.createIntArray(100000000, 3, 100000);
		TimeCounter c1 = new TimeCounter();
		System.out.println(findMedianVersion2(i));
		c1.countTime();
		c1.reset();
		System.out.println(findMedianVersion1(i));
		c1.countTime();
	}

实验结果如下
在这里插入图片描述
上面这个是基于插值计算得出的结果而下面这个则是常规的求中位数的方法得出的结果,从该结果我们看出在数据量较大的情况下使用基于插值计算的算法更为高效。

2.测试少量数据情况下两种算法的性能
实验代码如下

	public static void main(String[] args) {
    
    
		int i[] = IntegerUtils.createIntArray(10000, 3, 100000);
		TimeCounter c1 = new TimeCounter();
		System.out.println(findMedianVersion2(i));
		c1.countTime();
		c1.reset();
		System.out.println(findMedianVersion1(i));
		c1.countTime();
	}

实验结果如下
在这里插入图片描述
从该结果我们看出在数据量较少的情况下两者的性能差异不大,但就计算精度来说建议使用常规方法。

————————————分割线——————————————————
欢迎大家留言

Guess you like

Origin blog.csdn.net/qq_31236027/article/details/106758949