快速选择算法高效解决topk问题

本文介绍了如何使用快速选择算法在O(n)的时间复杂度内解决大数据量下的TopK问题,对比了传统的排序方法和优先级队列方法,并通过代码示例展示了快速选择算法在1亿数据量上的高效性能,相比排序快了10多倍。
摘要由CSDN通过智能技术生成

快速选择算法高效解决topk问题

**你是否遇到过这样的业务问题,比如在数据库中根据某张表的某个字段来获取第K大(或小)的记录
  • 也许你会使用order by 加limit的组合来返回该数据,但这样做如果数据量很大的话可能会需要很长的耗时。
  • 或许你会选择返回所有数据,然后再排序处理返回排序后的len-K索引处即为结果,但这也需要O(nlogn)的时间复杂度
  • 或者你会选择优先级队列,即将数据都加入优先级队列(用堆实现)中,然后pop出第k个数即为结果,时间也是O(nlogn),建堆的时间代价是 O(n),删除的总代价是O(klogn),因为 k<n,故渐进时间复杂为O(n+klogn)=O(nlogn)。.

今天我推荐你使用快速选择来解决topK问题,可以在o(n)的时间内获取结果****

快速选择算法,可以在o(n)的时间内找到数组中第K大的元素

改进于快速排序算法

  • 在快速排序中,我们每次续选择的基准值pivot,在一次快速排序过程后,在pivot所在索引处
    pivot索引左边的元素<=nums[pivot],右边的元素>=nums[pivot]
  • 即pivot索引所在元素刚好是数组中的第nums.length-pivot大的元素,那么如果该索引刚好是我们需要的那个topK的索引,就无需再关心两边数据的顺序了
  • nums.length-pivot==k,直接返回pivot索引处的元素 nums.length-pivot>k,向左递归该过程
    nums.length-pivot<k,向右递归该过程

以下是代码

public class QuickSelect {
    static Random random = new Random();

    public static void main(String[] args) {
        QuickSelect quickSelect = new QuickSelect();
        //假定是数据库返回的数据,用1亿条数据演示
        int[] nums = new int[100000000];
        int k = 3243252;//第k大的数,不超过数组长度都可以
        //用随机数填充数组
        getRandomNums(nums);
        //备用一份
        int[] nums2 = Arrays.copyOf(nums, nums.length);
        long l = System.currentTimeMillis();
        Arrays.sort(nums);
        long l1 = System.currentTimeMillis();
        System.out.println("排序耗时:" + (l1 - l) + "被选中的数:" + nums[nums.length - k]);

        long t = System.currentTimeMillis();
        int kthLargest = quickSelect.findKthLargest(nums2, k);
        long t1 = System.currentTimeMillis();
        System.out.println("快速选择耗时:" + (t1 - t) + "被选中的数:" + kthLargest);
    }

    private static void getRandomNums(int[] nums) {
        for (int i = 0; i < nums.length; i++) {
            nums[i] = random.nextInt(10000000);
        }
    }

    public int findKthLargest(int[] nums, int k) {
        return quickSelect(nums, 0, nums.length - 1, nums.length - k);
    }

    private int quickSelect(int[] nums, int l, int r, int index) {
        int i = randomPartition(nums, l, r);
        if (i == index) {
            return nums[i];
        }
        if (i < index) {
            return quickSelect(nums, i + 1, r, index);
        } else {
            return quickSelect(nums, l, i - 1, index);
        }
    }

    private int randomPartition(int[] nums, int l, int r) {
        //随机选取基准值索引
        int i = random.nextInt(r - l + 1) + l;
        //将基准值索引放在当前数组r索引处,这样就可以在[l,r-1]中搜索了,[l  .... r-1   pivot]
        swap(nums, i, r);
        //一次快排过程,使l...pivot-1都<=nums[pivot],pivot+1...r都>=nums[pivot]
        return partition(nums, l, r);
    }

    private int partition(int[] a, int l, int r) {
        int temp = a[r];
        int index = l - 1;//指向<=a[pivot]处索引
        for (int i = l; i < r; i++) {
            if (a[i] <= temp) {
                //将小的数放在pivot的左侧
                swap(a, ++index, i);
            }
        }
        //将pivot放到它应该在的位置
        swap(a, ++index, r);
        return index;
    }

    private void swap(int[] nums, int l, int r) {
        int temp = nums[l];
        nums[l] = nums[r];
        nums[r] = temp;
    }
}

运行几次看看效果,
k = 3243252
在这里插入图片描述

k = 8765432;

在这里插入图片描述

k = 12345678;

在这里插入图片描述

可以看到,在1亿的数据量情况下,快速选择几乎比排序快了10多倍

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值