问题原型:http://blog.csdn.net/v_july_v/article/details/6279498
2、搜索引擎会通过日志文件把用户每次检索使用的所有检索串都记录下来,每个查询串的长度为1-255字节。
假设目前有一千万个记录(这些查询串的重复度比较高,虽然总数是1千万,但如果除去重复后,不超过3百万个。一个查询串的重复度越高,说明查询它的用户越多,也就是越热门。),请你统计最热门的10个查询串,要求使用的内存不能超过1G。
典型的Top K算法,还是在这篇文章里头有所阐述,详情请参见:十一、从头到尾彻底解析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个元素的最小推来对出现频率进行排序。
我采用 trie 树 + 堆排序的实现方案,代码如下:
#coding:utf-8
#py2.7
#trie树
class trie:
def __init__(self):
self.t={}
def add(self, w):
p = self.t
for i in range(0, len(w)):
p.setdefault(w[i], {})
p = p[w[i]]
p.setdefault('', 0)
p[''] += 1
def __iter__(self):
return self.huisu(self.t, '')
def huisu(self, dt, w):
for k in dt.keys():
if k == '':
yield w, dt[k]
dt.pop(k)
elif dt == {}:
break
else:
nextstr = w + k
for w, co in self.huisu(dt[k], nextstr): #调用递归中的发生器
yield w, co
t = trie()
t.add(u'我')
t.add(u'我们')
t.add(u'我家')
t.add(u'我们家')
t.add(u'我是')
t.add(u'我们')
t.add(u'我说的对')
t.add(u'我想说')
t.add(u'我想说')
t.add(u'我想说')
t.add(u'我方')
print t.t
print [dc for dc in t]
代码中实现了两个重要的接口,一个是add 负责向trie树 添加 实例,另一个是对trie树的遍历的方法: __iter__。遍历方法采用回溯算法,其本质就是一个DFS算法。python 实现回溯算法用到了yield,注意递归 和yield 结合起来的方式。
下面是使用python 的堆排序返回了 TOP-K 的 搜索项:
def topk(t, k):
rs = {}
ll = []
for w, c in t: #遍历trie树
if len(ll) == k:
ll.append(c)
rs.setdefault(c, [])
rs[c].append(w)
if len(ll) == k:
heapq.heapify(ll) #收集前k项,并且进行堆排序
else:
if c in ll:
rs[c].append(w)
continue
pc = heapq.heappushpop(ll, c) #弹出频度最小的一项
if pc in rs.keys():
rs.pop(pc) #从结果集中剔除被弹出的搜索项
rs.setdefault(c, [])
rs[c].append(w)
return rs
小窍门:heapq是一个最小堆, 如果要用最大堆的话,可以把所有 数值 都取相反数,就得到了最大堆。
trie 遍历的时间复杂度是O(n*le),查找一个词的时间复杂度是O(le),le是单词的平均长度,n是单词数量
一次K个元素 的堆排序的时间复杂度是 log(K),最多n次,所以堆排序总的时间复杂度为 O(n*log(K))