在上亿级别的数当中找前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));
    }

}
原文地址:https://www.cnblogs.com/rao11/p/11979433.html