找第k大的数(方法汇总)

    题目:n个数,确定其中第k个最大值。

    数据输入:一行,n、k,空格隔开。

    数据输出:一个数,第k大的数。

    所谓“第(前)k大数问题”指的是在长度为n(n>=k)的乱序数组中S找出从大到小顺序的第(前)k个数的问题。

    解法1: 我们可以对这个乱序数组按照从大到小先行排序,然后取出前k大,总的时间复杂度为O(n*logn + k)。
    解法2: 利用选择排序或交互排序,K次选择后即可得到第k大的数。总的时间复杂度为O(n*k)
    解法3: 利用快速排序的思想,从数组S中随机找出一个元素X,把数组分为两部分Sa和Sb。Sa中的元素大于等于X,Sb中元素小于X。这时有两种情况:
    1. Sa中元素的个数小于k,则Sb中的第k-|Sa|个元素即为第k大数;
    2. Sa中元素的个数大于等于k,则返回Sa中的第k大数。时间复杂度近似为O(n)
    解法4: 二分[Smin,Smax]查找结果X,统计X在数组中出现,且整个数组中比X大的数目为k-1的数即为第k大数。时间复杂度平均情况为O(n*logn)
    解法5:用O(4*n)的方法对原数组建最大堆,然后pop出k次即可。时间复杂度为O(4*n + k*logn)
    解法6:维护一个k大小的最小堆,对于数组中的每一个元素判断与堆顶的大小,若堆顶较大,则不管,否则,弹出堆顶,将当前值插入到堆中。时间复杂度O(n * logk)
    解法7:利用hash保存数组中元素Si出现的次数,利用计数排序的思想,线性从大到小扫描过程中,前面有k-1个数则为第k大数,平均情况下时间复杂度O(n)

    附注:
    1. STL中可以用nth_element求得类似的第n大的数(由谓词决定),使用的是解法3中的思想,还可以用partial_sort对区间进行部分排序,得到类似前k大的数(由谓词决定),它采用的是解法5的思想。
    2. 求中位数实际上是第k大数的特例。


《编程之美》2.5节课后习题:
    1. 如果需要找出N个数中最大的K个不同的浮点数呢?比如,含有10个浮点数的数组(1.5,1.5,2.5,3.5,3.5,5,0,- 1.5,3.5)中最大的3个不同的浮点数是(5,3.5,2.5)。
    解答:上面的解法均适用,需要注意的是浮点数比较时和整数不同,另外求hashkey的方法也会略有不同。
    2. 如果是找第k到第m(0<k<=m<=n)大的数呢?
    解答:如果把问题看做m-k+1个第k大问题,则前面解法均适用。但是对于类似前k大这样的问题,最好使用解法5或者解法7,总体复杂度较低。
    3. 在搜索引擎中,网络上的每个网页都有“权威性”权重,如page rank。如果我们需要寻找权重最大的K个网页,而网页的权重会不断地更新,那么算法要如何变动以达到快速更新(incremental update)并及时返回权重最大的K个网页?
提示:堆排序?当每一个网页权重更新的时候,更新堆。还有更好的方法吗?
    解答:要达到快速的更新,我们可以解法5,使用映射二分堆,可以使更新的操作达到O(logn)

    4. 在实际应用中,还有一个“精确度”的问题。我们可能并不需要返回严格意义上的最大的K个元素,在边界位置允许出现一些误差。当用户输入一个query的时候,对于每一个文档d来说,它跟这个query之间都有一个相关性衡量权重f (query, d)。搜索引擎需要返回给用户的就是相关性权重最大的K个网页。如果每页10个网页,用户不会关心第1000页开外搜索结果的“精确度”,稍有误差是可以接受的。比如我们可以返回相关性第10 001大的网页,而不是第9999大的。在这种情况下,算法该如何改进才能更快更有效率呢?网页的数目可能大到一台机器无法容纳得下,这时怎么办呢?

    提示:归并排序?如果每台机器都返回最相关的K个文档,那么所有机器上最相关K个文档的并集肯定包含全集中最相关的K个文档。由于边界情况并不需要非常精确,如果每台机器返回最好的K’个文档,那么K’应该如何取值,以达到我们返回最相关的90%*K个文档是完全精确的,或者最终返回的最相关的K个文档精确度超过90%(最相关的K个文档中90%以上在全集中相关性的确排在前K),或者最终返回的最相关的K个文档最差的相关性排序没有超出110%*K。
    解答:正如提示中所说,可以让每台机器返回最相关的K'个文档,然后利用归并排序的思想,得到所有文档中最相关的K个。 最好的情况是这K个文档在所有机器中平均分布,这时每台机器只要K' = K / n (n为所有机器总数);最坏情况,所有最相关的K个文档只出现在其中的某一台机器上,这时K'需近似等于K了。我觉得比较好的做法可以在每台机器上维护一个堆,然后对堆顶元素实行归并排序。

    5. 如第4点所说,对于每个文档d,相对于不同的关键字q1, q2, …, qm,分别有相关性权重f(d, q1),f(d, q2), …, f(d, qm)。如果用户输入关键字qi之后,我们已经获得了最相关的K个文档,而已知关键字qj跟关键字qi相似,文档跟这两个关键字的权重大小比较靠近,那么关键字qi的最相关的K个文档,对寻找qj最相关的K个文档有没有帮助呢?

    解答:肯定是有帮助的。在搜索关键字qj最相关的K个文档时,可以在qj的“近义词”相关文档中搜索部分,然后在全局的所有文档中在搜索部分。

转载于:https://www.cnblogs.com/haipzm/archive/2012/04/20/2460616.html

<think>好的,我现在要解决的问题是如何高效地从1亿个整中筛选出最的10000个。这个问题看起来有点挑战性,特别是当据量非常的时候,直接的方法可能效率很低,甚至无法处理。我需要仔细分析可能的算法和它们的优缺点,然后选择最适合的方法。 首先,我想到最简单的方法可能是先对整个组进行排序,然后取前10000个最。比如使用快速排序或者归并排序这样的O(n log n)算法。但问题是,当据量达到一亿的时候,这样的排序可能会消耗量的时间和内存。特别是如果据无法全部加载到内存中,这种方法可能不可行。比如引用[1]中提到,输入一百万据时算法就接近崩溃,这说明处理据时需要考虑内存和效率问题。 那有没有更高效的方法呢?我记得有一个叫做堆排序的方法,特别是使用最小堆来维护最的k个元素。具体来说,可以维护一个小为10000的最小堆,遍历整个据,每次将元素堆顶比较,如果更就替换堆顶并调整堆。这样时间复杂度是O(n log k),而k是10000,相比O(n log n)可能更高效。因为log10000约是14,所以总的时间复杂度是O(n*14),而排序的话是O(n log n),当n是一亿时,log n约是26,所以堆的方法应该更快。 另外,还有一种方法叫做快速选择(Quickselect),这是快速排序的变种,用于在未排序的组中到第k的元素。不过,如果我要的10000个,可能需要到第n-10000+1小的元素,然后提取所有于等于这个元素的。不过这种方法在最坏情况下时间复杂度是O(),但平均情况下是O(n)。不过,如果原据是重复的或者有特殊分布的话,可能效果不稳定。另外,这种方法需要将所有据加载到内存中,可能对内存要求较高,尤其是在处理一亿据的时候。 另外,考虑到据量过可能无法一次性加载到内存,可能需要分块处理。比如将据分成多个块,每次处理一个块,提取该块中的最10000个,最后合并这些结果再筛选。不过这样的话,合并阶段可能需要多次处理,复杂度会增加。但分块处理可以降低内存的压力。 还有一种方法是使用外部排序,当据无法全部放入内存时,将据分块排序后合并。这种方法可能比较适合非常据集,但同样需要较多的磁盘I/O操作,可能会影响速度。 再考虑引用[4]中提到的问题,寻或最小的n个,里面可能比较过不同算法。比如可能提到堆的方法,或者部分排序的方法。同时,引用[2]中提到的布隆过滤器虽然可能不直接相关,但其中提到的权衡空间和时间的思想可能适用,比如在内存使用和计算时间之间到平衡。 那回到问题,用户需要的是从一亿个整中取最的10000个。假设据可以全部加载到内存,那么堆的方法可能更高效,因为只需要维护一个10000小的堆,遍历一遍据即可。而如果内存不够,可能需要分块处理,比如每次读取一部分据,用堆的方法筛选出每个块中的最值,然后合并这些结果再次筛选。 具体来说,堆的步骤如下: 1. 初始化一个最小堆,容量为10000。 2. 遍历整个组,对于每个元素: a. 如果堆的小不足10000,就将元素加入堆。 b. 否则,如果当前元素比堆顶,就替换堆顶,并调整堆。 3. 遍历完成后,堆中的元素就是最的10000个。 这种方法的时间复杂度是O(n log k),空间复杂度是O(k),也就是10000,这对于内存来说应该是可以接受的,因为即使每个整是4字节,10000个也就是40KB左右,非常小。 另一种方法是快速选择,但可能需要更多的内存,因为需要所有据在内存中进行分区操作。而堆方法只需要维护一个较小的堆,并且在据流式处理时非常有效,比如据可以逐个读取,不需要全部存入内存,这对于处理非常据集可能更有利。 此外,如果据是分布在不同的机器或者需要并行处理,可能可以采用分治的方法,每个处理单元处理一部分据,提取各自的最10000个,然后汇总后再提取最终的10000个最值。这种方法在分布式系统中可能更高效。 总结下来,最优的方法可能是使用最小堆来维护最的10000个,遍历一遍据即可完成,时间和空间复杂度都比较低。如果据无法一次性加载到内存,可以分块处理,每个块使用堆的方法提取最的10000个,然后将所有块的堆合并,再次筛选。但需要注意,分块处理时每个块提取的10000个合并后可能会很,比如如果有100个块,合并后的据是100*10000=1,000,000,这时候再从中取10000个最值,可能还需要进行一次堆处理,但总体复杂度仍然可控。 例如,假设内存只能一次处理一百万的据,那么将一亿据分成100块,每块一百万。对每块处理,提取最的10000个,然后将这100*10000=1,000,000个合并,再从中提取最的10000个。这种方法需要两次堆处理,时间复杂度是O(n + m log k),其中m是合并后的据量,这里m是1,000,000,k是10000。两次处理的总时间应该还是可行的。 此外,还可以考虑使用优先队列(即堆)的数据结构,这在很多编程语言中都有现成的实现,比如Python中的heapq模块。例如,可以用heapq来实现最小堆,当堆小超过10000时弹出最小的元素,这样最后剩下的就是最的10000个。 不过,在Python中,heapq模块默认实现的是最小堆,所以为了维护最的元素,可能需要将元素取负后存储,或者使用最堆。但Python的heapq并不直接支持最堆,所以需要一些技巧。例如,存储值的负,这样堆顶就是最小负的绝对值最。 例如,代码可能如下: import heapq def find_largest_numbers(nums, k): heap = [] for num in nums: if len(heap) < k: heapq.heappush(heap, num) else: if num > heap[0]: heapq.heappop(heap) heapq.heappush(heap, num) return heap 不过,这里有问题,因为heapq是min-heap,所以当堆的小为k时,堆顶是当前k个中的最小值。因此,当遍历所有时,如果当前比堆顶,则替换堆顶,这样最后堆中保存的就是最的k个。 这种方法在Python中是可行的,但需要注意处理据时的内存问题。如果nums是一个生成器或者可以分批次读取,那么可以避免一次性加载所有据到内存中。 比如,可以分批次读取据,每次读取一定量的据,然后处理。这样可以减少内存的占用。 总结来说,高效的方法应该是使用最小堆,时间复杂度O(n log k),空间复杂度O(k),适用于据量的情况,尤其是当k远小于n时,比如这里的k=10000,n=1亿。这种方法不需要全部据一次性加载到内存,可以流式处理,适合处理无法完全载入内存的据。 此外,还可以考虑其他优化,例如: 1. 如果据有部分有序,可以利用这一点减少比较次。 2. 使用多线程或并行处理,分块处理据,然后合并结果,但需要注意线程安全和合并的开销。 3. 如果据范围有限,可以使用计排序或桶排序的思路,但可能不适用于最值的提取,因为需要统计每个元素的出现次,当据范围很时可能不适用。 综上,使用最小堆的方法是较为推荐的高效方法,适用于处理从一亿个整中筛选最的10000个的问题。</think>要从1亿个整中高效筛选出最的10000个,可以采用**最小堆(优先队列)**的方法,时间复杂度为$O(n \log k)$,空间复杂度为$O(k)$($k=10000$)。以下是具体实现步骤: ### 方法实现 1. **初始化最小堆**:创建一个容量为10000的最小堆。 2. **遍历据**: - 若堆未满,直接插入元素。 - 若堆已满,比较当前元素堆顶元素。若当前元素更,则替换堆顶并调整堆结构。 3. **输出结果**:遍历完成后,堆中保存的即为最的10000个。 ### 代码示例(Python) ```python import heapq def find_largest_numbers(nums, k): heap = [] for num in nums: if len(heap) < k: heapq.heappush(heap, num) else: if num > heap[0]: heapq.heapreplace(heap, num) return heap ``` ### 优势分析 - **低内存占用**:仅需维护小为10000的堆,而非存储全部据[^1]。 - **流式处理**:支持逐个读取据,适合无法一次性加载到内存的场景。 - **高效性**:时间复杂度$O(n \log k)$,远低于全排序的$O(n \log n)$。 ### 优化扩展 - **分块处理**:若内存有限,可将据分块处理。例如,每次处理100万据,提取每块的Top 10000,再合并后二次筛选。 - **并行计算**:利用多线程/分布式系统分块处理,最后汇总结果[^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值