在购物网站中订购top-k商品的最佳方式(算法/数据结构)是什么,相关信息在每个服务器的日志中?
我正在考虑一种方法,该方法涉及维护固定大小k的双向链表,每个节点具有计数变量(可以是范围)一组共享相同计数的产品ID。随着每个事件(productId)的到达,遍历列表并计数更新,并且如果可能的话,提升到下一个更高的计数范围。
上述方法是否正确?还有哪些其他更好的解决方案?
答案 0 :(得分:1)
你的方法不正确,你说列表是固定大小的,但这表明你已经知道哪些是前k个元素 - 显然不是这种情况。假设您已经填充了一个大小为k
的列表,并且您遍历了一半的项目 - 现在,下一个项目重复整个集合(n / 2次重复) - 它显然应该位于前k,但是你永远不会把它放在你的清单中 - 所以结果是错误的。
您可以通过某些方式解决问题,具体取决于限制(主要是日志文件的大小)。
方法1:构建直方图并找到前k个元素
首先,迭代列表,并构建一个histogram(基于哈希/树的地图map<item,int>
) - 然后,在找到每个元素重新出现的数字后,它只是找到前k个元素, this thread详细介绍了该问题。
通过维护一个最小堆,迭代你的集合,为每个项找到顶部k - 检查它是否高于堆中的最小项,如果是,则从堆中弹出元素并插入此项代替。
建立直方图只需:
histogram = new map<item,int>
for each element x in the list:
val = (x is a key in map? map.get(x) : 0) + 1
map.put(x,val)
如果使用基于树的地图,此方法的复杂性为O(nlogn)
,如果使用基于散列的地图,则为O(nlogk)
。这非常有效,但如果您的日志文件包含数万亿条目,则可能无法在一台计算机上合理的时间内完成,并且您需要在多台计算机上分配您的工作。这引导我们采用下一个方法。
方法2: map-reduce
此方法适用于非常大的日志文件,可通过在大型群集上分发问题来完成。这是一种更复杂的方法 - 但对于非常大的文件,使用一台机器可能无法找到前k个元素。
map(file):
for each item in file:
emit(item,1)
reduce(item,list)
sum = 0
for each x in list:
sum = sum + x
emit(item,sum)
在这个阶段你处理了列表并构建了一个直方图,现在我们需要找到前面的k,想法是分割数据,这样每台机器都会获得一部分,并产生它的本地顶部K元素,然后将所有#machines * K元素发送到将选择全局前k
的单个“主”机器答案 1 :(得分:0)
除了Amit的回答,还有概率数据结构来处理这种查询。它们可能牺牲精度以减少资源使用。
以下是关于此的论文的链接:
Efficient Computation of Frequent and Top-k Elements in Data Streams
指向实现的链接(使用Java)。
答案 2 :(得分:0)
根据您的提交,我想添加一个建议。如果P
是产品数量,我认为
n
和k
比P
和如果是这种情况,那么您的解决方案可能是
x
成为第k个最常见产品的本地计数。 lc >= x/n
的所有元素发送到中央服务器。k
元素。y
计算临时第k个全球最常见的产品f >= y/n
的所有产品,请求所有服务器将其本地频率发送到中央服务器。您按y/n
和x/n
进行过滤的原因是任何产品必须至少有一个服务器lc >= gc/n
。 x
和y
是我们想要找到的频率最低的产品的全局数量的下限。
这种方法的网络流量将比map-reduce模型少得多 - 但是编程也需要更长。
如果您计划进行更多日志分析,我肯定会建议您查看Hadoop(和Hive / Spark/SparkSQL)或Google BigQuery。设置需要一段时间,但投资将很快在节省的编程时间内自行支付。