zoukankan      html  css  js  c++  java
  • 在上亿级别的数当中找前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));
        }
    
    }
  • 相关阅读:
    Android 之 Android目录
    Android之新建项目
    Android 所遇问题(一)
    Android运行机制
    C#编程语法积累(二)
    C#编程的语法积累(一)
    MVC4.0网站发布
    SQLServer 之 树查询
    Linux回收站[改写rm防止误删文件无法恢复]
    音视频同步(播放)原理
  • 原文地址:https://www.cnblogs.com/rao11/p/11979433.html
Copyright © 2011-2022 走看看