在上亿级别的数当中找前1000个数
方法1:
对于这道题第一个想到的方法可能是把所有的数都排好序,然后从所有的数中取前1000个数。
但是对于亿级别的数排序可不是一件容易的事,所以这个方法肯定行不通。
方法2:
采用分治的思想,在这些数中随便选择一个数,然后以这个数为界限,把所有的数分成两堆,左边的部分都大于这个数,右边的部分都小于这个数。
如果左边的数的个数大于1000,那么便可以从左边的数进行查找,
如果左边的数的个数小于1000,那么就从右边的数中在随机选择一个数,再把数分成两堆,从剩下的数中找出(1000-上一次左边的数的个数)这么多个数字。
依次进行下去,直到找齐1000个数。
这样做的时间复杂度我们可以计算一下:
第一次排序所要用的时间为O(n),以后每次排序的时候数据量都减半,时间记为n/2,所有的时间加起来为n+n/2+n/4+...,最后所得的时间肯定小于2n
所以用这种方法的时间复杂度为n
但是空间复杂度呢???
因为数据量过大,我们无法将所有的数据一次性读到内存中进行排序,所以第一次排序时可能造成内存溢出的情况。
方法3:
采用二叉堆的方法,关于二叉堆,可以在我的 https://www.cnblogs.com/rao11/p/11976960.html 博客中查看代码。
因为二叉堆中的小顶堆的顶元素一定时所有元素中最小的,所以可以让这上亿个数中的前1000个组成一个小顶堆,然后用这个1000个数在内存中进行计算,那么怎么进行计算呢?
把所有的数存放到硬盘中, 从1001个数开始,每个数传入内存与小顶堆的堆顶元素进行比较,如果这个数比堆顶元素大,那么就让这个数与堆顶元素进行交换,得到新的小顶堆。
然后把新的小顶堆进行有序处理,使得它组成一个有序的小顶堆,然后依次进行手术操作,直到比较完所有的数。
这样做可以不用占用大量的内存,时间复杂度也为O(n)。
下面时代码:
package com.rao.linkList;
import java.util.Arrays;
import java.util.Random;
/**
* @author Srao
* @className TopN
* @date 2019/12/3 19:30
* @package com.rao.linkList
* @Description 查找亿级别数中的前1000个数
*/
public class TopN {
/**
* 构建小顶堆,从最后一个非叶子节点开始构建
* @param arr:要查找的数组
* @param length:要构建堆的大小,不用把全部的数组都参与构建
* @return
*/
public int[] buildHeap(int[] arr, int length){
for (int i = (length-1)/2; i>=0; i--){
upAdjust(arr, i, length);
}
return arr;
}
/**
* 下沉函数
* @param arr:要操作的数组
* @param parent:以哪个节点作为父节点
* @param length:要操作的数组的长度
* @return
*/
public int[] upAdjust(int[] arr, int parent, int length){
//先把父节点保存起来
int temp = arr[parent];
//获得子节点的下标
int child = 2*parent+1;
//如果父节点比子节点大,那么进行子节点上浮
while (child < length){
//选择左右孩子中小的那个进行比较,因为每次都要判断是用左孩子还是右孩子进行比较,所以这个判断放循环里面
if (child+1 < length && arr[child+1] < arr[child]){
child++;
}
//以根节点为中心,每次都是让根节点与子节点进行比较
if (arr[child] > temp){
break;
}else {
arr[parent] = arr[child];
parent = child;
child = parent*2+1;
}
}
//这时arr[child] > arr[parent]
arr[parent] = temp;
//返回数组
return arr;
}
/**
* 把小顶堆中堆顶的数与数组中第i个数进行交换
* @param arr:要筛选的数组
* @param i:要交换的数的下标
* @param length:小顶堆的长度
*/
public void swap(int[] arr, int i, int length){
if (arr[i] < arr[0]){
return;
}
//两数交换
int temp = arr[i];
arr[i] = arr[0];
arr[0] = temp;
//然后重新调整小顶堆
upAdjust(arr, 0, length);
}
/**
*
* @param arr:要进行筛选的数
* @param length:要查找前n个数,即小顶堆的大小
* @return
*/
public int[] findTopN(int[] arr, int length){
//先构建一个小顶堆
int[] heap = buildHeap(arr, length);
//再依次循环所有数组中的数,如果有比小顶堆堆顶大的数,就把这个数换成堆顶的数
for (int i = length; i < heap.length; i++){
swap(heap, i, length);
}
//返回小顶堆
return heap;
}
public static void main(String[] args) {
TopN topN = new TopN();
// 第一组测试
int[] arr1 = new int[]{56, 30, 71, 18, 29, 93, 44, 75, 20, 65, 68, 34};
System.out.println("原数组:");
System.out.println(Arrays.toString(arr1));
topN.findTopN(arr1, 5);
System.out.println("调整后数组:");
System.out.println(Arrays.toString(arr1));
// 第二组测试
int[] arr2 = new int[1000];
for(int i=0; i<arr2.length; i++) {
arr2[i] = i + 1;
}
System.out.println("原数组:");
System.out.println(Arrays.toString(arr2));
topN.findTopN(arr2, 50);
System.out.println("调整后数组:");
System.out.println(Arrays.toString(arr2));
// 第三组测试
Random random =new Random();
int[] arr3 = new int[1000];
for(int i=0; i<arr3.length; i++) {
arr3[i] = random.nextInt();
}
System.out.println("原数组:");
System.out.println(Arrays.toString(arr3));
topN.findTopN(arr3, 50);
System.out.println("调整后数组:");
System.out.println(Arrays.toString(arr3));
}
}