转:如何在 10 亿数中找出前 1000 大的数?

原文链接:https://mp.weixin.qq.com/s/SHmoBNduRYOf5yO87Q9yWQ

典型的TopN问题,有以下几种思路,详细描述请参考原文链接:

1)全部排序后取前N个数:时间复杂度太高

2)部分排序:时间复杂度太高

3)分治法:时间复杂度为O(n),但Partition时占用内存空间过大

4)分布式计算:先将数据分组,每个分组中计算TopN,然后汇总所有的TopN,继续分组计算,直至得到满足条件的TopN。

缺点:需要多台机器同时计算,不能满足单台机器计算的要求

5)小顶堆:先建立包含前N个数的小顶堆(堆排序)。从第N+1个数开始读取直至末尾。每一个读取的数与堆顶的数比较,若堆顶较小,则替换堆顶并调整堆;若堆顶较大,则直接丢弃输入的数。

小顶堆对应的代码如下,key point如下:

a)数组可以看做是数据在内存中存储的物理结构,其对应的逻辑结构可理解为一颗二叉树。对于数组索引为n的元素,在二叉树中,其父节点索引为(n-1)/2,左孩子索引为(2*n+1),右孩子索引为(2*n+2)

b)BuildHeap(): 构建堆时,可理解为:对原始的二叉树进行广度优先遍历(逐层遍历),对当前遍历到的节点,与父节点进行交换,并继续向上与父节点比较。所有节点都遍历完成后,每个节点都调整到了正确的位置。

c)adjust():每加入一个数,如果需要调整堆顶,调整后的堆顶与左、右子节点中较小的进行交换,交换之后,继续与下一级的左、右子节点比较,直至比左、右子节点都小或子节点索引都超出N的范围。

/**
 * @author xiaoshi on 2018/10/14.
 */
public class TopN {

    // 父节点
    private int parent(int n) {
        return (n - 1) / 2;
    }

    // 左孩子
    private int left(int n) {
        return 2 * n + 1;
    }

    // 右孩子
    private int right(int n) {
        return 2 * n + 2;
    }

    // 构建堆
    private void buildHeap(int n, int[] data) {
        for(int i = 1; i < n; i++) {
            int t = i;
            // 调整堆
            while(t != 0 && data[parent(t)] > data[t]) {
                int temp = data[t];
                data[t] = data[parent(t)];
                data[parent(t)] = temp;
                t = parent(t);
            }
        }
    }

    // 调整data[i]
    private void adjust(int i, int n, int[] data) {
        if(data[i] <= data[0]) {
            return;
        }
        // 置换堆顶
        int temp = data[i];
        data[i] = data[0];
        data[0] = temp;
        // 调整堆顶
        int t = 0;
        while( (left(t) < n && data[t] > data[left(t)])
            || (right(t) < n && data[t] > data[right(t)]) ) {
            if(right(t) < n && data[right(t)] < data[left(t)]) {
                // 右孩子更小,置换右孩子
                temp = data[t];
                data[t] = data[right(t)];
                data[right(t)] = temp;
                t = right(t);
            } else {
                // 否则置换左孩子
                temp = data[t];
                data[t] = data[left(t)];
                data[left(t)] = temp;
                t = left(t);
            }
        }
    }

    // 寻找topN,该方法改变data,将topN排到最前面
    public void findTopN(int n, int[] data) {
        // 先构建n个数的小顶堆
        buildHeap(n, data);
        // n往后的数进行调整
        for(int i = n; i < data.length; i++) {
            adjust(i, n, data);
        }
    }

    // 打印数组
    public void print(int[] data) {
        for(int i = 0; i < data.length; i++) {
            System.out.print(data[i] + " ");
        }
        System.out.println();
    }

}

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值