堆排序实现百万级数据取若干数量的最大数字(java)

      这些天看到了一道题,是一道比较出名的面试题,题目字面上比较简单。

      输入若干个float数字(百万级以上) ,编写一个算法从中取出指定数量(100个以内)的最大的数字。

      我们先分析一下这道题,从一堆数字里取出几个最大的数,以我们通常的思想去考虑,首先想到的是对这堆数字进行倒序排序,取出前几个就是我们要的结果,这样实现是没错的。可是注意看括号中的注释,输入的数字量级是百万级以上的,如果单纯为了实现结果,排序是没错的,但是对百万级的数据进行排序,不管使用任何排序都会花费很多时间。并且float在java中占4个字节,1000000*4/1024/1024=3.8 G,这样庞大的数据同样需要考虑到空间复杂度的问题,排序算法基本都需要额外的空间,如果对这样大的数据进行排序是不可能的。

      程序员在很多人的印象中都是呆板,两眼无神,目光呆滞(也不乏有特别帅气的),可我们的脑子是很灵活的。我们需要转变一下思路,从需要取出数字中下手。

      我们基本确定一下我们的思路,我们可以指定一个额外的数组,这个数组就是我们需要取出的数字,因为这个数组的量级很小(100以内),我们每次去维护这个小量级的数组肯定比直接排序大量级的数组好,所以我们遍历百万级的数组去和这个数组比较,如果比这个数组中最小的数字大,我们就替换它,最终会得到一组最大数字。

      由于取出的数字是最大的,所以我们首先想到的是选择排序,选择排序就是每次都会取出最大或者最小的数字,这看起来有点符合我们的需求。我们先考虑到了直接选择排序,直接选择排序需要对数组的所有元素进行两两比较,时间复杂度为O(n^2),由于选择排序都是就地排序的算法,不需要额外的空间,所以空间复杂度是满足的。堆排序也属于选择排序,本文使用堆排序来实现。

      我们先需要熟悉一下堆排序,堆其实就是一个完全二叉树,满足中序遍历的顺序,堆排序根据小根堆(大根堆)的性质进行排序,满足二叉树中的任何子树都为父节点最小(最大),每当堆中数字改变都需要去维护这个性质

                

                         逻辑图                                                                     数组结构

     我们指定获取前M个最大的数字,所以我们构建一有M个节点的小根堆,遍历百万级数组,用数组去和小根堆的根节点进行比较,如果比根节点大,则替换根节点,然后重建小根堆,维护小根堆的性质,保证根节点在堆中最小,遍历完成后,堆中数字则为数组中最大数字。下面上代码!!

/**
 * Created by zym on 2018/3/7.
 */

/**
 * 思路:
 * 构建一个包含M个节点的小根堆,之后循环数组,用数组的元素和小根堆的根作比较
 * 如果比根大,则替换根,重新维护小根堆,直到循环完毕数组,剩下的为小根堆
 * 这样不需要对整个数组进行堆排序,每次只需要维护一个100节点以内的堆,提升效率
 */
public class HeapUtils {
    /**
     * 替换元素
     * @param data 指定数组
     * @param i 位置1
     * @param j 位置2
     */
    private static void replace(float[] data, int i, int j) {
        if (i == j) {
            return;
        }
        float temp=data[i];
        data[i]=data[j];
        data[j]=temp;
    }

    /**
     * 小根堆排序
     * 利用小根堆的性质,最终的根节点为树中节点的最小值,将最小值放在最后
     * 剩下的节点继续进行上述操作,直至倒数第二个节点,最后一个节点为最大节点无需判断
     * @param data 指定数组
     * @return
     */
    public static float[] heapSort(float[] data) {
        for (int i = 0; i < data.length-1; i++) {
            createMindHeap(data, data.length - 1 - i);
            //每次都将最小的数字放到最后,然后用剩下的数组继续排序
            replace(data, 0, data.length - 1 - i);
//            print(data);
        }
        return data;
    }
    /**
     * 当前节点 i==0? null:(i-1)/2
     * 左孩子节点 2*i+1
     * 右孩子节点2*i+2
     *
     * 创建小根堆,只保证树的根为小根堆
     * @param data 指定数组
     * @param lastIndex 最后一个节点位置
     */
    private static void createMindHeap(float[] data, int lastIndex) {
        //从最后一个节点作为左节点的树开始
        for (int i = (lastIndex - 1) / 2; i >= 0; i--) {
            // 记录当前节点,根据当前节点进行判断操作
            int k = i;
            // 判断左子节点是否存在,如果小于等于最大节点数则存在
            /**
             * 这里使用while循环是为了保证每个子树的根节点为小根堆
             * 如果使用if只能保证最终根为最小根
             */
            while (2 * k + 1 <= lastIndex) {
                //暂且定为最小的为左子节点
                int smallIndex = 2 * k + 1;
                if (smallIndex+1 <= lastIndex) {
                    //如果右子节点存在,判断左右大小(不保证左右节点满足小根堆的性质,因为只需使最小节点为根节点)
                    if (data[smallIndex] > data[smallIndex + 1]) {
                        // 若右子节点值比左子节点值小,则samllIndex记录的是右子节点的值
                        smallIndex++;
                    }
                }
                if (data[k] > data[smallIndex]) {
                    // 若当前节点值比子节点最小值大,则交换2者得值,交换后将smallIndex值赋值给k
                    replace(data, k, smallIndex);
                    //标记当前节点为最小节点,继续判断此节点作为根的堆是否为小根堆
                    k = smallIndex;
                } else {
                    break;
                }
            }
        }
    }

    /**
     * 获得指定数量的最大值数组
     * @param count 指定数量
     * @param data 指定数组
     * @return
     */
    public static float[] getMaxNumber(int count,float []data){
        float[] maxNubmerArr = new float[count];
        for(int i=0;i<data.length;i++){
             if(data[i]>maxNubmerArr[0]){
                 maxNubmerArr[0]=data[i];
                 createMindHeap(maxNubmerArr,count-1);
                 //查看每次取得的数字
//                 print(maxNubmerArr);
             }
        }
        return maxNubmerArr;
    }

    /**
     * 输出数组
     * @param data
     */
    public static void print(float[] data) {
        for (int i = 0; i < data.length; i++) {
            System.out.print(data[i] + "\t");
        }
        System.out.println();
    }
}

测试代码!!!!

/**
 * Created by zym on 2018/3/7.
 */
public class TestDemo {

    @Test
    public void test1(){
        float arr[]= new float[1000000];
        for (int i=0;i<1000000;i++){
            Random random=new Random();
            float v = random.nextFloat() * 50f;
            arr[i]=v;
        }
        long start=System.currentTimeMillis();
        float[] maxNumber = HeapUtils.getMaxNumber(100, arr);
        HeapUtils.heapSort(maxNumber);
        HeapUtils.print(maxNumber);
        System.out.println(System.currentTimeMillis()-start);
    }

}
执行时间为10ms




  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值