您的位置:首页 > 其它

寻找最小的k个数

2015-06-01 10:41 281 查看
今天上午看了篇博文,这篇博文是对另一篇博文的总结,两篇博文都是在讨论一个算法——寻找最小的K的数,感觉两篇文章写得都很不错,值得借鉴和学习,所以做了一个自我的一个小总结,两篇博文的连接如下:
http://blog.csdn.net/huagong_adu/article/details/6901924 http://blog.csdn.net/v_JULY_v/article/details/6370650
我主要就看了第一篇博文,第一篇博文也是在第二篇博文的基础上总结的。

算法的题目:如何在一堆数据中找出最小的K个数

一共有以下几种方法,效率由低到高:

1. 先对数据进行排序(插入排序,快速排序,whatever),然后取出前K个数

2. 声明一个K大小的数组,先从这堆数据中装入前K个数,找出这K个数中的最大的数Max(K),然后从第K+1个数开始向后找,如果有小于这个Max(K)的,则替换掉这个数,然后从这K个数中重新找出最大的Max(K),这样一直向后扫描,得到结果。这个算法的时间复杂度最坏为O(kn)。

3. 扫描数据堆K遍,每遍找出最小的那个数,复杂度为O(kn)。

4. 使用最大堆:先用数据中的前K个数建立一个最大堆(根元素大于左子树,同时大于右子树,以此类推),建立堆的复杂度为O(K),然后从第K+1个数开始向后扫描,遇到小于堆顶元素时替换掉堆顶元素,更新堆,这个操作的时间复杂度为O(logK)。所以总的时间是O(K + (n - k) * logK) = O (n * logK),比O(nK)稍微好一点。

5. 使用最小堆:这次是将这个数据建一个最小堆(时间复杂度O(n)),然后取出堆顶元素,每取完一次更新一次堆(O(logn)),取k次,所以总的时间复杂度是O(n + k * logn)

注意:可以证明O(n + k * logn)< O (n * logK),即使用最小堆的方法优于使用最大堆的方法,但是在实际应用中两者的时间上的差距很小。但是请注意,在空间复杂度上最大堆需要O(k),而最小堆需要O(n),所以综合来讲,最大堆方法比最小堆方法有优势。

6. 对最小堆方法的改进:每次取走堆顶元素更新堆时,正常是把堆中最后一个元素放到堆顶(暂且称为 !Top),然后调整堆把 !Top下调到他应该在的位置。改进后, !Top不用下调到他原所应该在的位置,而是下调顶多K次就可以了。具体如下:

建立n的最小堆之后,取走堆顶元素(第一个数),然后将最后的数 !Top调到堆顶,把 !Top下调至多K-1层形成新的堆;接着取走堆顶元素(第二个数),同样,更新堆的时候 !Top下调至多K-2层...直到取走第K个数时,不再更新堆(此时的堆已经不是最小堆),算法结束,已经取得最小的K个数,最后的“堆”是不是堆已经跟我没关系了。

改进后的复杂度:建堆O(n),更新堆O(K),K次更新为O(K*K)=O(K^2),所以总的复杂度是O(n+K^2),比改进前的O(n+K*logn)要好。

7. 用快速排序的思想,先选取一个数作为基准比较数(作者称为“枢纽元”,即pivot),用快速排序的方法把数据分为两部分Sa和Sb,Sa中的数据都小于pivot,Sb中的数据都大于pivot。

如果K< |Sa|( |Sa|表示Sa的大小),则对Sa部分用同样的方法继续操作;

如果K= |Sa|,则Sa是所求的数;

如果K= |Sa| + 1,则Sa和这个pivot一起构成所求解;

如果K> |Sa| + 1,则对Sb部分用同样的方法查找最小的(K- |Sa|-1)个数(其中Sa和pivot已经是解的一部分了)
8. 第二篇博客中所提到的BFPRT算法,BFPRT算法本质上是对上述快排思想算法的一种改进,重要改进就是在pivot的选取上,一般的快速排序算法是将数据堆中的第一个或者是最后一个数作为pivot,而BFPRT算法采用“五分化中位数的中位数”方法取得这个pivot,从而使算法复杂度降低到O(n),具体方法如下:

以5个数为一组对数据进行划分,最后一组数据的个数为n%5,然后对每组数据用插入排序方法选出中位数,对选出的中位数用同样的方法继续选,最后选出这些数的中位数作为pivot,即可达到O(N)的效率。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: