“数组中求前K大/小数”深入探讨及举例

原文地址:http://hi.baidu.com/liangrt_fd/blog/item/dbf5b5d8726023accc11665e.html


“数组中求前K大/小数”深入探讨及举例
2010-07-17 22:07

在一个数组中取得前k大元素,在搜索引擎返回搜索结果时比较有用,当客户获得搜索引擎返回的结果时,往往只会关注前几页的链接,所以我们只要返回前k大的元素即可,没必要对整个返回结果进行排序,返回结果的数量级可能在10^6甚至更多。


算法1:如果将该数组拆分,然后单独排序,最后进行归并,那么总的时间复杂度将是O(nlgn),在n很大的情况下,lgn也是一个客观的数字,比如n=2^32,那么lgn=32

算法2:如果在内存里维持一个长度为k的线性列表或者数组array[k],用来储存前k大的元素,线性扫描数组,每次从数组中读取一个元素,那么就需要依此比较array中的元素,替换掉其中最小的元素,这样整个算法的时间复杂度就变成了O(k*n),如果k<lgn,那么该算法比算法1要优秀。但是在实际应用中,尤其是搜索引擎在返回结果时,我们往往需要前50个或者前100个元素,那么该算法也不优良。

在算法2中,我们是使用一个长度为k的线性链表来保存前k的元素,由于对于非排序线性数据结构搜索效率较低,我们不妨将线性数据结构改为树形搜索结构,只要将链表或者数组改为一个小根堆,红黑树或者AVL树即可,在此我们选择小根堆,因为小根堆的实现最简单。这样每次替换小根堆中最小元素只需要lgk的时间复杂度就可以了,时间复杂度就降为了O(nlgk),而且只需要扫描一遍数组(文件)即可。

对取得数组前k大元素,我们进行了以上探讨,下面就举一个例子:

有一个长度为n的数组,要求取得距离该数组中位数最近的k个数(中位数:在数组排好序以后居于中央的元素,可以认为下标为(n/2))。

普通的思路:(为了便于分析,假设数组元素为整数)

对数组array[n]进行排序,取得中位数x,在内存里维护一个长度为k的大根堆,然后扫描一遍数组,将数组中每一个元素与array[i]中位数x进行比较,维护大根堆,最终结果便是距离数组最近的k个数。

上述方法的时间复杂度:O(nlgn)排序+ O(nlgk)维护堆+O(n)取得符合条件的k个元素=O(nlgn)

 

当数组可以在内存中存放时,我们在O(n)的时间复杂度里,就可以取得数组的中位数x,所以我们没有必要去进行排序,在内存中维护一个长度为k的大根堆(因为是取绝对值前k小元素),然后依次扫描数组中的每个元素array[i],获得array[i]-x的绝对值,更新大根堆,大根堆中元素应为:

Struct

{

int abs;//array[idx]-x的绝对值大小:|array[idx]-x|

int idx;//在原数组中的下标

}

最后扫描一遍大根堆,取得相应元素的下表,输出该元素即可。该算法的时间复杂度为:O(n)取得中位数+O(nlgk)同上+O(n)同上=O(nlgk)

但是该问题的时间复杂度能不能降为O(n),假设内存足够大?答案是可以。

假设原数组为array[n],我们可以创建一个新的数组new_array[n]new_array中的元素为:

Struct

{

int abs;//array[idx]-x的绝对值大小:|array[idx]-x|

int idx;//在原数组中的下标

}

这样我们就可以在new_array[n]中以O(n)的时间复杂度取得前k小的元素集合U,然后扫描U中元素,取得对应下标属性值idx的值,在原数组中取得该下标idx对应的元素array[idx]并输出。该算法的时间复杂度为O(n),但是空间复杂度也相对较大,空间又一次被用来换取时间。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值