10G的文件里面全是无序数字,内存只有2G,如何找到里面的中位数

一、分析

因为内存只有2g,而文件有10g,所有不能一次性将磁盘文件的所有数据都读进来,而是通过将10g文件分成内存能处理的多个小文件进行处理,或者每次读取2g大小进行处理。以下分享两种方式。

二、分块排序并合并

为了方便理解,这里以数据{5 11 0 18 4 14 9 7 6 8 12 17 16 13 19 10 2 1 3 15}为例,暂时当作这么多数据的大小为10G。

首先将数据分为5块,然后对每块通过快排进行排序,得到以下结果:

A1 {0 5 11 18}
A2 {4 7 9 14}
A3 {6 8 12 17}
A4 {10 13 16 19}
A5 {1 2 3 15}

要对以上数据进行合并,如果直接全都取到内存进行合并,那2g的内存自然是不够,因此只能限制每次取的数量,此时需要引入多路归并算法。

多路归并算法:
①首先取每块数据的第一个元素,组成一个最小堆。
②堆顶元素(即最小元素)弹出,插入新的数据文件
③找到弹出元素所在的块,将该块的第二个元素插入最小堆。
重复②③,直到每块的元素都遍历完。

以上算法的思想,首先对每块数据的第一个元素组成最小堆,自然而然可以直接拿到最小元素,关键点在步骤③,为什么要将弹出元素所在块的下个元素放入最小堆呢?因为在所有块的第一个元素比较完后,已经得到最小值,需要找到第二小的元素。就需要拿其他四个块中的第一个元素和最小元素所在块的第二个元素进行比较,此时就能找到第二小元素,以此类推,就能找到第三小第四小…

在这里插入图片描述

最终,通过一个只有五个数据大小的最小堆,即可得到一个经过排序好的数据文件,不过每次读取块内的元素其实都要经过一次磁盘I/O,内存是够了,但磁盘I/O是很密集的。

三、堆排序

要理解堆排序之前,必须理解最大堆求前n个小,最小堆求前n个大,这两个大同小异,就拿最大堆求前n个小举例。

首先还是刚刚的数据{5 11 0 18 4 14 9 7 6 8 12 17 16 13 19 10 2 1 3 15},我们需要求前一半小的数有哪些,也就是求前10个小的数有哪些。

解法:取前10个数(随便取10个都行,求前n个就取n个)组成最大堆,剩下的数,依次和堆顶元素比较,如果大于堆顶元素,则舍弃;如果小于堆顶元素,则堆顶元素舍弃,加入当前元素。最终所有堆内元素即为前10个最小的数。

为什么可以这样?因为第一次所组成的最大堆在和剩下的数做比较时,小于堆内最大值就入堆,就是在筛选最小的前n个数,剩余元素和n个最大堆元素的最大值比较,每次入堆时会抛弃最大值,从而留下最小值,并且通过最大堆的特性,得到下一个最大值,继而与下一个元素比较。最终其实所有数据都比较了一遍,把最小的n个数留在了最大堆内。

有了以上基础,就可以通过最大堆求前n小来找到10g数据中的前5g小,从而找到中位数。而我们只有2g内存大小,所以还不能一次性构建一个5g的最大堆,这里我们构建一个1g的最大堆。

步骤一、通过1g的最大堆和剩下的9g数据比较,即可得到前1g最小的数,也即第1g个数即为第1g大的元素。

步骤二、拿到第1g大的数(这里设为D1)之后,再从10g中取1g大于D1的数据组成一个新的最大堆,
接下来将10g数据中大于D1的数与新最大堆比较插入,最终新最大堆的堆顶元素即为第2g大的元素(设为D2)。

步骤三、重复步骤二,取1g大于D2的数组成最大堆,然后与大于D2的数比较,得到D3。最终得到D5,也即第5g大的数,也即中位数。

同样,每次比较都需要从10g文件中取数据,因此最终磁盘I/O次数达到5*10g次。

  • 5
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
在Java,可以使用PriorityQueue来实现无序数组的位数。PriorityQueue是一个基于优先级堆的无界优先级队列,默认是一个最小堆。具体实现可以参考以下代码: ``` import java.util.PriorityQueue; public class Median { public static double median(int[] array) { int heapSize = array.length / 2 + 1; PriorityQueue<Integer> heap = new PriorityQueue<>(heapSize); for (int i = 0; i < heapSize; i++) { heap.add(array[i]); } for (int i = heapSize; i < array.length; i++) { if (heap.peek() < array[i]) { heap.poll(); heap.add(array[i]); } } if (array.length % 2 == 1) { return (double) heap.peek(); } else { return (double) (heap在Java,可以使用PriorityQueue来实现无序数组的位数。PriorityQueue是一个基于优先级堆的无界优先级队列,默认是一个最小堆。具体实现可以参考以下代码: ``` import java.util.PriorityQueue; public class Median { public static double median(int[] array) { int heapSize = array.length / 2 + 1; PriorityQueue<Integer> heap = new PriorityQueue<>(heapSize); for (int i = 0; i < heapSize; i++) { heap.add(array[i]); } for (int i = heapSize; i < array.length; i++) { if (heap.peek() < array[i]) { heap.poll(); heap.add(array[i]); } } if (array.length % 2 == 1) { return (double) heap.peek(); } else { return (double) (heap.poll() + heap.peek()) / 2.0; } } public static void main(String[] args) { int[] array = new int[]{12, 34, 1, 209, 17, 900, -10}; System.out.println(median(array)); } } ``` 其,首先定义了一个大小为数组长度一半加一的最小堆,然后将数组的前一半元素加入堆。接着,遍历数组的后一半元素,如果当前元素比堆顶元素大,则将堆顶元素弹出,将当前元素加入堆。最后,如果数组长度为奇数,则位数就是堆顶元素;如果数组长度为偶数,则位数就是堆顶元素和次顶元素的平均值。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值