top-k选择/合并

时间:2010-05-20 22:33:28

标签: algorithm database-design

我有 n 排序列表(5< n< 300)。这些列表很长(300000+元组)。选择单个列表的前k个当然是微不足道的 - 它们就在列表的头部。

k = 2的例子:

top2 (L1: [ 'a': 10, 'b': 4, 'c':3 ]) = ['a':10 'b':4]
top2 (L2: [ 'c': 5, 'b': 2, 'a':0 ]) = ['c':5 'b':2]

当我想要在所有排序列表中组合top k 时,它变得更有趣。

top2(L1+L2) = ['a':10, 'c':8]

仅仅组合单个列表的前k个不一定会得到正确的结果:

top2(top2(L1)+top2(L2)) = ['a':10, 'b':6]

目标是减少所需的空间,并使排序的列表保持较小。

top2(topX(L1)+topX(L2)) = ['a':10, 'c':8]

问题在于是否存在一种算法来计算具有正确顺序的组合顶部k,同时切断某个位置处的列表的长尾。如果有:如何找到限制X哪里可以安全切割?

注意:正确的计数并不重要。只有订单。

top2(magic([L1,L2])) = ['a', 'c']

7 个答案:

答案 0 :(得分:1)

如果我正确理解你的问题,正确的输出是前10项,无论每个项目的列表如何。如果这是正确的,那么从每个列表中的前10个项开始将允许您生成正确的输出(如果您只想要输出中的唯一项,但输入可能包含重复项,那么每个列表中需要10个唯一项)

在最极端的情况下,所有顶级项目都来自一个列表,其他列表中的所有项目都将被忽略。在这种情况下,在一个列表中包含10个项目就足以产生正确的结果。

答案 1 :(得分:1)

此算法使用O(U)内存,其中U是唯一键的数量。我怀疑是否可以实现较低的内存界限,因为无法确定哪些键可以被丢弃直到所有钥匙已被总结。

  1. 制作(key:total_count)元组的主列表。一次只能在每个列表中运行一个项目,并记录每个键被看到的次数。
  2. 使用主列表中不使用额外内存的任何top-k selection algorithm一个简单的解决方案是对列表进行排序。

答案 2 :(得分:0)

  1. 将索引与n个列表中的每个列表相关联。将其设置为指向每种情况下的第一个元素。
  2. 创建列表列表,并按索引元素对其进行排序。
  3. 列表列表顶部列表中的索引项是您的第一个元素。
  4. 增加最顶层列表的索引,并从列表列表中删除该列表,然后根据其索引元素的新值重新插入该列表。
  5. 列表列表顶部列表中的索引项是您的下一个元素
  6. 转到4并重复完成。
  7. 您没有指定您拥有的列表数量。如果n很小,那么步骤4可以非常简单地完成(只需重新排序列表)。随着n的增长,你可能想要考虑更有效的方法来求助和几乎排序的列表列表。

答案 3 :(得分:0)

我不明白两个列表中是否出现'a',他们的计数必须合并。这是一种新的内存有效算法:

(新)算法:

  1. (重新)按ID(不计算)对每个列表进行排序。要释放内存,可以将列表写回磁盘。只需要足够的内存用于最长的列表。
  2. 获取下一个最低的未处理ID,并查找所有列表的总计数。
  3. 将ID插入k个节点的priority queue。使用总计数作为节点的优先级(而不是ID)。如果插入的节点数超过k个,则此优先级队列将丢弃最低节点。
  4. 转到第2步,直到所有ID都用完为止。
  5. 分析:此算法只能使用O(k)附加内存来实现,以存储最小堆。它需要做出一些权衡来实现这一目标:

    1. 列表按ID排序;计数的原始排序丢失了。否则,需要额外的O(U)内存来制作ID为:total_count元组的主列表,其中U是唯一ID的数量。
    2. 通过检查每个列表的第一个元组,在O(n)时间内找到下一个最低ID。重复U次,其中U是唯一ID的数量。通过使用最小堆来跟踪下一个最低ID,可以改进此。这将需要O(n)额外的内存(并且在所有情况下都可能不会更快)。
    3. 注意:此算法假设可以快速比较ID。字符串比较并非无足轻重。我建议将字符串ID散列为整数。它们不必是唯一的哈希值,但必须检查冲突,以便正确排序/比较所有ID。当然,这会增加内存/时间的复杂性。

答案 4 :(得分:0)

完美的解决方案要求至少检查一次所有元组。

但是,可以在不检查每个元组的情况下将关闭转换为完美的解决方案。丢弃“长尾”会引入误差。您可以使用某种类型的启发式来计算误差范围何时可以接受。

例如,如果有n = 100个已排序的列表,并且您已经检查了每个列表,直到计数为2,那么密钥的总计数最多可以增加200个。

我建议采用迭代方法:

  1. 计算每个列表,直到达到某个较低的计数阈值L.
  2. 降低L以包含更多元组。
  3. 将新元组添加到目前已计算的计数中。
  4. 转到第2步,直到降低L不会将前k个计数改变超过一定百分比。
  5. 该算法假设前k个键的计数将接近某个值,进一步的长尾被遍历。您可以使用其他启发式而不是特定百分比,例如前k中新键的数量,前k键被洗牌多少等等...

答案 5 :(得分:0)

通过mapreduce实现这一目标有一个明智的方法:

http://www.yourdailygeekery.com/2011/05/16/top-k-with-mapreduce.html

答案 6 :(得分:-1)

总的来说,我认为你遇到了麻烦。想象一下以下列表:

['a':100, 'b':99, ...]
['c':90, 'd':89, ..., 'b':2]

你有k = 1(即你只想要前一个)。 'b'是正确的答案,但你需要一直看到第二个列表的末尾才能意识到'b'击败了'a'。

编辑:

如果你有正确的分布(长,低计数尾),你可能会做得更好。现在让我们保持k = 1,让我们的生活更轻松。

基本算法是保留您到目前为止看到的键及其相关总计的哈希映射。向下走列表处理元素并更新地图。

关键的观察结果是,一个密钥最多可以计入每个列表当前处理点的计数总和(称为和S)。因此,在每个步骤中,您可以从哈希映射中删除总数超过当前最大计数元素的S的任何键。 (我不确定你需要修剪什么数据结构,因为你需要在给定一系列计数的情况下查找密钥 - 可能是优先级队列?)

当您的哈希映射中只有一个元素,并且其计数至少为S时,您可以停止处理列表并将该元素作为答案返回。如果你的计数分布很好,这个提前退出可能实际上触发,所以你不必处理所有列表。