【问题标题】:O(n) Heavy-Hitters with O(1/epsilon) space?O(n) 具有 O(1/epsilon) 空间的重型击球手?
【发布时间】:2016-06-16 06:26:30
【问题描述】:

我知道以下针对重击手的算法:

Algorithm findHeavyHitters(epsilon, inputStream)
    integer k = ceiling(1 / epsilon) - 1
    initialize hashmap H of size k

    while an item i from the input stream arrives:
        if H[i] exists
            increment the value associated with H[i]
        elsif number of items in H < k
            put H[i] into map with value of 1
        elseif there exists an entry j with a value of 0
            remove j and put H[i] into map with value of 1
        else
            decrement all values in H by 1
    endwhile

    return H

如果我错了,请纠正我,但这个算法不会在 O(n) 中运行。是否可以修改此算法,使其在 O(n) 中运行,同时保持 O(1/epsilon) 的空间使用?

对于数据流,算法的重点是返回顶部的 epsilon*t 项。 Epsilon 以百分比形式给出(例如,对于至少出现 10% 时间的数据,输入 0.1)。

【问题讨论】:

  • 是什么让你认为它不能在 O(n) 中运行?
  • 在 else 块中,我们需要遍历所有现有的重量级人物,以将每个重量级人物递减 1。
  • 我认为这是您的反对意见,所以我在回答中涵盖了它。哈希表查找是最坏情况 O(n) 的可能反对意见不能轻易解决; afaik,你不能让最坏的情况小于 O(n log n)。
  • @fluffychaos 我打算使用 count-min 草图来实现重击手。您能否解释一下 else 块(将所有值减 1)处理哪些情况?另外,在答案中建议的解决方案中添加新的变量基础有何帮助?

标签: algorithm streaming-algorithm bigdata


【解决方案1】:

该算法的平均运行时间为 O(n),基于哈希查找的平均时间为 O(1)。

有两个实现细节。首先,最后一步似乎涉及触摸 H 中的每个值:

  • 将 H 中的所有值减 1

为了使这个 O(1),我们添加了一个额外的存储位置,称为base,它被初始化为 0。然后我们修改算法如下:

while an item i from the input stream arrives:
    if H[i] exists
        increment the value associated with H[i]
    elsif number of items in H < k
        put H[i] into map with value of base + 1
    elseif there exists an entry j with a value of base 
        remove j and put H[i] into map with value of base + 1
    else
        increment base
endwhile

第二个问题是在 O(1) 中查找值为 base(或 0)的条目。这可以通过将元素保持在“梳子”中来完成:双向链表的链表。每个内部链表都包含具有特定计数的条目。外部链表包含按计数顺序排列的计数列表,头部指向计数最小的列表。如果你把这个数据结构画出来,它看起来就像一个梳子:

[  base    ] -> entry a -> entry b -> entry c
    |
[ base + i ] -> entry d
    |
[ base + j ] -> entry e -> entry f
    |
   etc.

哈希表现在指向条目,而不是包含它们。为了增加单个条目的计数,将该条目从其列表中删除(如果列表包含多个元素)并插入到下一个列表中或放入一个单元素列表中,该列表插入到它所在的列表之后,取决于与下一个列表关联的计数。这个操作是 O(1)。

comb 数据结构仍然是 O(k),其中 k 是散列中元素的数量,因为不同的计数不能比元素多。

您可以使用一个简单的数组和一个包含每个计数的第一个条目的索引列表来代替双向链表。要将条目移动到下一个计数存储桶,首先将其与具有该计数的最后一个条目交换,然后根据下一个计数列表的计数是否推进下一个计数列表的开头或插入一个新的计数列表条目大于或大于一。为了完成交换,需要更新两个交换条目在哈希中的位置,但这仍然是 O(1)。

【讨论】:

  • 很酷的数据结构 - 谢谢!不过,我仍然无法理解如何在 O(1) 中找到条目。如果您使用 LinkedList 的 LinkedList,在最坏的情况下,您是否不必遍历外部 LinkedList 的所有值来查找条目与哪个计数相关联?这可能仍然是 O(k) 遍历。或者,在所有 k 个元素都映射到相同计数的情况下,您是否可能不需要遍历内部列表的所有 k 个项目来找到您想要的节点?
  • @fluffychaos:您使用哈希表,就像在您的原始算法中一样。哈希表包含指向链表节点的指针。拼接链表不会移动节点,因此哈希表指针仍然可以工作。如果使用分区数组数据结构(最后),则需要修改交换元素的哈希表链接,但总是最多两个交换元素(有时不需要交换)。 (我在答案中提到了所有这些。)
猜你喜欢
  • 1970-01-01
  • 2011-08-24
  • 2015-05-25
  • 2012-01-02
  • 2011-08-09
  • 1970-01-01
  • 1970-01-01
  • 2018-03-26
  • 2012-08-06
相关资源
最近更新 更多