k均值算法,程序员面试题狂想曲:第三章续、Top K算法问题的实现

                     程序员面试题狂想曲:第三章续、Top K算法问题的实现
作者:July
致谢:
时间:2011年05月08日
微博:http://weibo.com/julyweibo 。
出处:http://blog.csdn.net/v_JULY_v
-----------------------------------------------
前奏
    在上一章,第三章寻找最小的k个数中,由于后来为了论证类似快速排序中partition的方法在最坏情况下,能在O(N)的时间复杂度内找到最小的k个数,而前前后后updated了10余次。所谓功夫不负苦心人,终于得到了一个想要的结果。
    简单总结如下(详情,请参考原文第三章):
    1、RANDOMIZED-SELECT,以序列中随机选取一个元素作为主元,可达到线性期望时间O(N)的复杂度。
    2、SELECT,快速选择算法,以序列中“五分化中项的中项”,或“中位数的中位数”作为主元(枢纽元),则不容置疑的可保证在最坏情况下亦为O(N)的复杂度。
    本章,咱们来阐述寻找最小的k个数的反面,即寻找最大的k个数,但此刻可能就有读者质疑了,寻找最大的k个数和寻找最小的k个数,原理不是一样的么?
    是的,的确是一样,但这个寻找最大的k个数的问题的实用范围更广,因为它牵扯到了一个Top K算法问题,以及有关搜索引擎,海量数据处理等广泛的问题,所以本章有必要对这个Top K算法问题,进行阐述。
第一节、寻找最大的k个数
把之前第三章的问题,改一个字,即成为寻找最大的k个数的问题了,如下所述:
查找最大的k个元素
题目描述:输入n个整数,输出其中最大的k个。
例如输入1,2,3,4,5,6,7和8这8个数字,则最大的4个数字为1,2,3和4。
    分析:由于寻找最大的k个数的问题与之前的寻找最小的k个数的问题,本质是一样的,所以,这里就简单阐述下思路,ok,考验你举一反三的能力到了:
    1、排序,快速排序。我们知道,快速排序平均所费时间为n*logn,从小到大排序这n个数,然后再遍历序列中后k个元素输出,即可,总的时间复杂度为O(n*logn+k)=O(n*logn)。
    2、排序,选择排序。用选择或交换排序,即遍历n个数,先把最先遍历到得k个数存入大小为k的数组之中,对这k个数,利用选择或交换排序,找到k个数中的最小数kmin(kmin设为k个元素的数组中最小元素),用时O(k)(你应该知道,插入或选择排序查找操作需要O(k)的时间),后再继续遍历后n-k个数,x与kmin比较:如果x>kmin,则x代替kmax,并再次重新找出k个元素的数组中最大元素kmin‘(多谢kk791159796 提醒修正);如果x<kmin,则不更新数组。这样,每次更新或不更新数组的所用的时间为O(k)或O(0),整趟下来,总的时间复杂度平均下来为:n*O(k)=O(n*k)。
    3、维护k个元素的最小堆,原理与上述第2个方案一致,即用容量为k的最小堆存储最先遍历到的k个数,并假设它们即是最大的k个数,建堆费时O(k),并调整堆(费时O(logk))后,有k1>k2>...kmin(kmin设为大顶堆中最大元素)。继续遍历数列,每次遍历一个元素x,与堆顶元素比较,x>kmin,更新堆(用时logk),否则不更新堆。这样下来,总费时O(k*logk+(n-k)*logk)=O(n*logk)。此方法得益于在堆中,查找等各项操作时间复杂度均为logk(不然,就如上述思路2所述:直接用数组也可以找出最大的k个元素,用时O(n*k))。
    4、按编程之美第141页上解法二的所述,类似快速排序的划分方法,N个数存储在数组S中,再从数组中随机选取一个数X,把数组划分为Sa和Sb俩部分,Sa>=X>=Sb,如果要查找的k个元素小于Sa的元素个数,则返回Sa中较大的k个元素,否则返回Sa中所有的元素+Sb中最大的k-|Sa|个元素。不断递归下去,把问题分解成更小的问题,平均时间复杂度为O(N)(严格证明,请参考第三章)。
   .........
   其它的方法,在此不再重复了,同时,借助堆寻找最小的k个数,在第三章已有实现,更多,可参考第三章,只要把最大堆改成最小堆,即可。
第二节、Top K 算法问题
2.1、搜索引擎热门搜索
题目描述:
    搜索引擎会通过日志文件把用户每次检索使用的所有检索串都记录下来,每个查询串的长度为1-255字节。
    假设目前有一千万个记录(这些查询串的重复度比较高,虽然总数是1千万,但如果除去重复后,不超过3百万个。一个查询串的重复度越高,说明查询它的用户越多,也就是越热门。),请你统计最热门的10个查询串,要求使用的内存不能超过1G。
    分析:这个问题在之前的这篇文章里,已经有所解答,十一、从头到尾彻底解析Hash表算法。方法是,第一步、先对这批海量数据预处理,在O(N)的时间内用Hash表完成统计(之前写成了排序,特此订正。July、2011.04.27);
    第二步、借助堆这个数据结构,找出Top K,时间复杂度为N‘logK。
        即,借助堆结构,我们可以在log量级的时间内查找和调整/移动。因此,维护一个K(该题目中是10)大小的小根堆,然后遍历300万的Query,分别和根元素进行对比所以,我们最终的时间复杂度是:O(N) + N'*O(logK),(N为1000万,N’为300万)。ok,更多,详情,请参考原文。
    或者:采用trie树,关键字域存该查询串出现的次数,没有出现为0。最后用10个元素的最小推来对出现频率进行排序。
    ok,本章里,咱们来实现这个问题,为了降低实现上的难度,假设这些记录全部是一些英文单词,即用户在搜索框里敲入一个英文单词,然后查询搜索结果,最后,要你统计输入单词中频率最大的前K个单词。ok,复杂问题简单化了之后,编写代码实现也相对轻松多了,如下:

2.2、统计出现次数最多的数据
题目描述:
给你上千万或上亿数据(有重复),统计其中出现次数最多的前N个数据。
    分析:上千万或上亿的数据,现在的机器的内存应该能存下。所以考虑采用hash_map/搜索二叉树/红黑树等来进行统计次数。然后就是取出前N个出现次数最多的数据了。当然,也可以堆实现。
    ok,此题相当于上面的统计最热门的搜索,不过,咱们用红黑树取代之前的用hash表,来完成最初的统计,然后用堆更新,找出出现次数最多的前N个数据。
    完整代码如下:

Tags:  k聚类算法 k近邻算法 kmeans算法 k均值聚类算法 k均值算法

延伸阅读

最新评论

发表评论