当前位置: 首页 > 科技观察

堆排序解决TopK问题详解

时间:2023-03-14 08:13:50 科技观察

上次介绍了堆排序,这次介绍堆排序常见应用场景的TopK问题。利用堆求TopK问题TopK问题是堆排序的典型应用场景。题目是这样的:假设我们要在大量数据中,比如100亿整数数据中,找出K个值最大的元素,K小于10000,你会怎么做呢?目标是力扣第215题:“数组中第K大的元素”。具体链接:https://leetcode-cn.com/problems/kth-largest-element-in-an-array/在未排序的数组中寻找第k大的元素。请注意,您正在寻找排序数组的第k个最大元素,而不是第k个不同的元素。示例1:输入:[3,2,1,5,6,4]和k=2输出:5示例2:输入:[3,2,3,1,2,4,5,5,6]和k=4Output:4经典的TopK问题还包括:最大(小)K个数,前K个高频元素,第K大(小)元素。这个TopK问题本质上是一个排序问题,排序算法一共有十种,还有很多排序算法没有介绍。至于为什么TopK问题的最佳答案是堆排序?事实上,考虑到空间和时间的复杂性,虽然快速排序是最好的排序算法,但是它把100亿个元素从大到小排序,然后输出前K个元素值。但是不管我们是快排序算法还是堆排序算法,在排序的时候,都需要把所有的元素读入内存。也就是说,100亿个整??数元素需要占用大约40GB的内存空间,这听起来不像是普通民用电脑能做到的,(一般民用电脑内存都比这个小,比如我写文章用的电脑内存是32GB)。众所周知,快速排序和堆排序的时间复杂度是可以达到的,但是对于快速排序来说,数据是顺序访问的。对于堆排序,通过跳过访问数据。例如在堆排序中,最重要的操作就是数据的堆放。因此,快速排序的时间复杂度优于堆排序。但是快速排序是一个新的数组,空间复杂度比堆排序要低很多。对于海量数据,应该首选堆排序。如果使用heapq内置模块,查找数组中第K个最大的元素只是一行代码。封装了heapq中的nlargest接口,返回的数组是需要切片取值的数组。importheapqclassSolution:deffindKthLargest(self,nums:List[int],k:int)->int:returnheapq.nlargest(k,nums)[-1]当然一般是手写堆排序,找第K大的array为元素创建一个最小堆,找到数组中第K小的元素构建一个最大堆。思路是:“取nums的前K个元素构建一个大小为K的最小堆,然后维护一个容量为k的小顶堆。堆中的堆中的k个节点代表当前最大的k个元素,而堆顶显然是k个元素中的最小值。”因此,只要遍历整个数组,当二叉堆的大小等于K时,当遇到大于堆顶值的元素时,弹出堆顶,将元素弹出被推入,最大的K个元素被持续维护。遍历后,堆顶元素为第K大元素。时间复杂度。classSolution:deffindKthLargest(self,nums:List[int],k:int)->int:heapsize=len(nums)defmaxheap(a,i,length):l=2*i+1r=2*i+2large=iifla[large]:large=lifra[large]:large=riflarge!=i:a[large],a[i]=a[i],a[large]maxheap(a,large,length)defbuildheap(a,length):foriinrange(heapsize//2,-1,-1):maxheap(a,i,length)buildheap(nums,heapsize)foriinrange(heapsize-1,heapsize-k,-1):nums[0],nums[i]=nums[i],nums[0]heapsize-=1maxheap(nums,0,heapsize)returnnums[0]相反,如果是要找到前k个最小的,那么就用最大的堆,所以面对TopK问题,最完美的解决方案就是堆排序。所以,只有看到数组的第Kth...,马上就会想到堆排序。如果数据量不大,对时间复杂度和空间复杂度的要求不高,真的没必要用“高大上”的算法,写个快速排序就完美了。TopK问题就像一个搜索引擎,每天都会收到大量的用户搜索请求。它会记录这些用户输入的搜索关键词,然后离线进行统计分析,得到最热门的Top10搜索关键词。.本文已收录到GitHubhttps://github.com/MaoliRUNsen/runsenlearnpy100