`
fionajw
  • 浏览: 22944 次
  • 性别: Icon_minigender_2
  • 来自: 北京
社区版块
存档分类
最新评论

关于最小的k个数的讨论(top-k问题)

    博客分类:
  • Java
阅读更多

给定一个长度为 n 的序列,不妨设为 L1,L2,L3,….,Ln 。这个序列可以是任意一种排列,可能的排列有 n !种,我们要找到最小的 k 个数,即找到这样的 k 个数 { Li(1) , Li(2) , Li(3)… , Li(k)} ,并满足 Li(1)<=Li(2)<=Li(3)…<=Li(k) ;且对任意的 j : k+1<=j<=n ,有 Li(k)<=Li(j) ,。

    例如:有这样一个长度为 8 的序列 {1 , 4 , 1 , 5 , 6 , 7 , 4* , 6} ,找出最小的 3 个数,则结果为 {1 , 1 , 4} 或者 {1 , 1 , 4*} ,而不是最小的 k 个排序码 {1 , 4 , 5} 。

        我们只讨论 n 较大的情况,不妨设 n 至少百万数量级( M ),下面对 k 进行逐一的分析:

    首先,回顾一下几大类排序

    冒泡排序

    选择排序: { 简单选择排序,锦标赛排序,堆排序 }

    快速排序,快速排序也可以看作一种选择排序。

    插入排序: { 直接插入排序,希尔排序 }

    归并排序:

    基数排序:

    计数排序:

    其中,适合 topk 问题的排序,只有冒泡排序,选择排序和快速排序,其余的排序都必须在排序最后一刻才能知道谁是最小的 k 个元素。

    如果 k = 1 ,此时很显然只能在简单选择排序和冒泡排序中考虑,因为锦标赛排序和堆排序初始化的成本太大。而冒泡排序在最坏情况下需要有 3 ( N-1 )次移动,即如果初始排列正好是降序的情况。而简单选择排序,只需要扫描一遍,无需移动数据。但冒泡排序有一个其他排序都不具备的性质就是,如果初始排列恰好是有序的(升序),则一趟冒泡就可以知道这个信息。因此在 k > 1 时,可以考虑第一趟用冒泡的方法,既能判断出初始序列是否有序,也能够在 O ( n )的时间内找到最小值,一举两得。

如果 1 < k < c1( 某个小常数 c1) ,则继续使用简单选择排序也是理想的,这个 c1 的临界点恰好是锦标赛排序和堆排序这种复杂排序初始化的时间开销引起的,当越过了 c1 的临界点后,锦标赛排序和堆排序的优势就发挥了出来。在这种情况下,简单选择排序,需要比较的次数为 n-1+n-2+…+n-c1 次。从内存的层次结构的角度看,复杂选择排序 ( 非线性 ) 和简单选择排序(线性)相比,缓存的命中率更低,换入换出的代价较大,且堆排序的初始化过程虽然复杂度也为 O ( N ),但在 n 很大的情况下,最坏情况下,系数接近 4 。

 

    如果 c1 <= k < c2 ,此时锦标赛排序会是更理想的选择,和堆排序相比,锦标赛排序树是一个完全二叉树(有些教材认为是满二叉树,这是不够好的),需要 n-1 个辅助空间,但锦标赛排序的初始化比较次数很少,只有 n-1 次(没有最好最坏之分)和堆排序的 4n(最坏情况下) 相比,在选出最小的元素后,选择后续的元素,堆排序和锦标赛排序都需要调整,锦标赛从底向上调整,堆排序从上向下调整,但锦标赛排序每上升一格只需要比较 1 次,堆排序需要比较 2 次(据称采用加速堆的方法,可以把这个系数降到 1 ,但需要付出 lglgn 的代价,同时付出代码的复杂性,本文不深入讨论这一点)。锦标赛排序总需要从叶子到根,而堆排序可能不需要,比如 n 个元素都相同的情况下,后者其他恰好符合堆性质而不需调整,或不需调整到叶子,总体情况看,锦标赛排序占据初始化的优势,在排序的早期应该能够胜出堆排序。当然锦标赛排序和堆排序都有优化提高的空间,就优化后的比较本文不作探讨。

 

    如果 c2 <= k < n/2 ,堆排序将会是更理想的选择,堆排序是一种原地排序,辅助空间为 O ( 1 ),因此空间局部性更好,特别是如果把堆看做一个数组,那么随着排序的进行,主要的计算都集中在数组的一段,而且局部性越来越好,因为数组的尾部已经是排好序的,没有访问的必要了。为了找出最小的 k 个数,堆排序将会使用小根堆,输出时从数组的尾部反序输出。

 

    如果 n/2 < k ,此时可以考虑用快速排序和堆排序结合的方法。前几趟用快速排序,快速压缩问题空间。使得问题转化为在 l 长度序列的排序 + m 个序列中找 top-k’ 个元素的问题或者在 L’ 长度序列中找 top-k 的问题。

    举个例子,例如 {4 , 2 , 1 , 5 , 6 , 7 , 4* , 6 , 8 , 3} 中找前 5 个元素,则通过 4 的划分后得到 {3 , 2 , 1} 4 {5 , 6 , 7 , 4* , 6 , 8} ,由于已知 4 是第 4 大的元素,则只需要将前一段全部排序输出,再输出 4 ,在输出后一段的第 5 – 4 = 1 个元素即可。如果是找前 2 个元素,问题就归结为在 {3,2,1} 中找最小的 2 个元素,则问题将大大化简。

 

    当 k 接近 n 的时候,毫无疑问使用快排序应该是最理想的了。

 

    最后,我们再讨论一下,当 n 足够大,以至于不能使用内排的情况。 由于这时问题的复杂性主要取决于读盘,因此我们希望的是找出最小的 k 个数的代价是只读一遍磁盘,同时考虑排序码还有其他卫星数据的情况。

   在这种情况下,直接选择排序,只有在 k = 1 时,才是最理想的。

   当 k > 1 时,选择堆排序时很理想的,因为锦标赛排序的辅组空间不能接受。可以设置一个大小为 k 的最大堆,该元素是整个堆最大的,如果在扫描磁盘的过程中,有排序码比这个更大则 pass ,如果更小,则把这个值淘汰掉,插入这个更小的值后,恢复成一个最大堆。扫描完毕后留在堆中的 k 个值,即为所求。 如果有其他卫星数据的情况下,堆的结点只需要增加相应的数据域或指针域即可。

 

    但问题是,假定实际的问题是需要在 100 亿网页 URL 中,找到 PV 最大的 top100 时(我们讨论的是最小,最大也可以用一样的方法)。我们使用了堆的方法,找到了结果,但是,领导突然需要看 top 1000 的 URL 时,还得做个大小为 1000 的堆再跑一遍,如果 topk 中的某些条件发生变化,比如 URL 长度在 512 以上的排除。。。可见,用堆的方法没有保存有价值的中间结果,这是很不理想的。

 

        什么才是理想的中间结果呢?我们考虑使用计数排序,例如这样一个整数序列 {4 , 2 , 1 , 5 , 6 , 7 , 4* , 6 , 8 , 3} ,我们可以申请从 1 到 8 的 8 个计数器,组成一个数组,pennyliang_ counter[]={1,1,1,2,1,2,1,1} ,其中 pennyliang_counter [0] = 1 ,表示 1 出现了 1 次,penny_ counter[3] = 2 ,表示 4 出现了 2 次。输出时,按照计数器数组,输出即为有序序列,计数排序是线性的,而且计数器数组是理想的排序中间结果。

 

    对于 top-k PV 的问题,申请一个阈值以上的计数器,例如 1024 以上的 PV 数值才能有可能申请计数器,对 10k 以上的 PV 数值,才申请存储 URL 的空间,(后续的处理也有很多优化,本文不再展开)。扫描一趟磁盘,就可以生成这样的计数器数组,对于任意的处理要求,可以通过这个计数器数组直接得到,最坏情况也可以通过这个计数器数组加上再一次的磁盘扫描线性地得到。

        昨夜,我一夜难眠,就在想怎么把这个问题讲清楚,临到写的时候,还是感到很多内容无法展开,同时感到自己对这个问题的理解还不能定量的分析,因此甚是遗憾

 

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/fatshaw/archive/2011/04/11/6315504.aspx

分享到:
评论

相关推荐

    一个简单的top-k实现

    在这个场景中,我们讨论的是一个用Java实现的简单Top-K算法。这个算法的目标是高效地找到一个数据集合中的前K个最大(或最小)的元素,而不需要对整个集合进行完整的排序。 在传统的排序方法中,如快速排序或归并...

    Finding Top-k Min-Cost Connected Trees in Databases2007

    标题 "Finding Top-k Min-Cost Connected Trees in Databases 2007" 指出了这篇论文的核心研究问题,即在数据库中寻找最小成本的前k个连通树。从标题可以推导出以下几点: 1. **数据库**:数据库技术是现代信息系统...

    TOPK算法的Hash实现

    标题中的“TOPK算法的Hash实现”指的是使用哈希数据结构来解决找出数据集中最大或最小的K个元素的问题。这种算法通常用于大数据处理和实时分析中,因为哈希表可以提供快速的查找和更新操作。 TOPK算法的核心是通过...

    TOP,K算法.pdf

    文档通过之前的寻找最小k个数的问题作为铺垫,探讨了Top K算法的实现,特别是寻找最大k个数的情况。文章强调,虽然寻找最大k个数和最小k个数在原理上相似,但Top K算法在搜索引擎和大数据处理等领域有更广泛的应用。...

    图中的整体Top-k简单最短路径联接

    不同于传统的发现每对节点之间最短路径的方法,本文提出的方法是通过寻找两个节点之间的Top-k简单最短路径来回答Top-k路径联接问题。为了实现这一点,提出了在候选路径搜索中使用编码后的预计算最短路径。文章阐述了...

    基于二分查找的有序表在做topK算法的给力实现

    TopK算法的主要目标是从大量数据中找出前K个最大的(或最小的)元素。这里的“给力实现”可能意味着这种方法在性能和效率上有显著优势。 **描述分析:** 描述简洁地重申了标题中的主题,表明我们将专注于如何使用...

    不确定图上的Top-k稠密子图挖掘算法

    - Top-k问题是指在一组数据中找出前k个最大(或最小)元素的问题。 - 在不确定图中寻找稠密子图的Top-k问题,即是在所有可能的稠密子图中找出k个最稠密的子图。 4. **Top-k稠密子图挖掘算法的目标** - 这类算法...

    一亿取100数字Top100

    综上所述,"一亿取100数字Top100"的问题涉及到大数据处理、高效算法和编程技巧等多个方面,理解和掌握这些知识点对于解决类似问题至关重要。通过合理地运用上述方法,可以在有限的时间和资源内完成任务。

    程序员编程艺术第一 ~二十七章

    - **第六章:亲和数问题--求解500万以内的亲和数** - 讨论了亲和数的定义及其求解方法。 - **第七章:求连续子数组的最大和** - 使用动态规划方法解决了最大子数组和的问题。 - **第八章:从头至尾漫谈虚函数** ...

    海量数据查找数据问题

    本篇文章将详细探讨如何解决"海量数据查找数据问题",并着重讨论如何在海量数据中寻找中位数以及查找特定的数。 首先,我们来关注如何在海量数据中找到中位数。中位数是一组数据的代表值,它能够反映出数据的整体...

    程序员编程艺术第一~三十七章集锦by_July

    - **TopK算法问题实现**:通过优先队列等数据结构优化查找最小的k个数。 - **快速选择SELECT算法**:深入分析并实现该算法,用于在未排序数组中找到第k小的元素。 - **数组中给定下标区间内的第K小(大)元素**:在...

    c++最小生成树算法

    在有向图中,通常讨论的是最小权重生成森林,因为有向图可能存在多棵树才能连接所有顶点。 Prim算法是一种用于求解无向图最小生成树的贪心算法,由Vojtech Jarník在1930年提出,后被Edsger Dijkstra改进并推广。该...

    TDEP:有效处理海量数据的前k个主导查询

    在top-k查询中,为每个元组提供一个排名函数F来决定其分数,并返回分数最小的k条记录。文中假定在这个场景中较小的值更受欢迎。 文章进一步讨论了TDEP算法的理论基础和它如何减少候选元组数量以提高效率。同时,还...

    程序员编程艺术 第一~二十七章集锦与总结

    此外,还介绍了TopK问题的实现细节。 4. **现场编写类似strstr/strcpy/strpbrk的函数** - **知识点**:C语言标准库函数实现、内存管理 - **内容概述**:模拟C语言标准库中的几个典型函数,如strstr(字符串查找)...

    IE598NH-lecture-17-Stochastic Approximation for MSP.pdf

    在本讲义中,我们讨论了随机逼近在多阶段随机规划中的应用,特别是针对两阶段非线性随机规划和具有圆锥约束的多阶段优化问题。以下是对这部分内容的详细解析。 ### 随机逼近与多阶段随机规划 在“IE598NH-...

    大厂面试系列二.pdf

    在内存限制为2G的情况下找出10G个整数中的中位数问题,可以使用外部排序算法将数据分块,然后使用最小堆或最大堆来维护较小的一半数据和较大的一半数据。 时分秒针每天重合的次数,可以通过计算它们的相对速度得出...

Global site tag (gtag.js) - Google Analytics