【问题标题】:set with average O(1) for add/remove and worst max/min O(log n)为添加/删除设置平均 O(1) 和最差最大/最小 O(log n)
【发布时间】:2014-01-21 17:24:11
【问题描述】:

我可以有一个集合,其中平均添加/删除操作为 O(1)(这对于基于哈希表的集合是典型的)并且最差的 max/min 小于 O(n),可能是 O(log n)(典型对于基于树的集合)?

upd 嗯,在最简单的情况下,我似乎可以在每次 max/min 消失时重新扫描所有 N 个元素,通常它给我 O(1)。但是我将我的算法应用于股票交易,其中接近最小值/最大值的变化更有可能,所以我只是不想在每次最大值或最小值消失时重新扫描所有内容,我需要比完全重新扫描更智能的东西,它给出了 O(n)。

upd2 在我的例子中,集合包含 100-300 个元素。 max/min 元素的变化很可能,因此 max/min 经常变化。我需要跟踪最大/最小。我仍然希望 O(1) 用于添加/删除。

【问题讨论】:

  • 我假设您也想要高效删除?
  • @JanDvorak 是的,我想要像哈希表这样的东西。所有基本操作都使用 O(1),但 + 快速 max/min,并且更有可能更改接近 max/min 的元素。
  • 排序跳过列表怎么样?不给 O(1) 添加/删除,但对于所有操作仍然相当快
  • O(1) 添加/删除是强制性的,因为它是主要操作。
  • 通过哈希表和专用堆的组合,您可以获得 O(1) 插入、O(1) 查找最小值和查找最大值以及 O(log(n)) 删除。这不是你所要求的,但它非常接近。不过,堆可能有一个不好的常数因子。

标签: algorithm data-structures


【解决方案1】:

这是一个不可能的结果,在一个确定性模型中,对于最坏情况、非摊销边界,可以比较和散列键,但没有其他任何东西。 (是的,有很多规定。我赞成 van Emde Boas 树的推荐。)

与比较下限的情况一样,这是一个对抗性论点。对手的游戏计划是插入许多密钥,同时选择性地删除算法拥有最多信息的密钥。最终,算法将无法处理对 max() 的调用。

对手按如下方式决定关键比较。与每个键相关联的是一个二进制字符串。每个键最初都有一个空字符串。当比较两个键时,它们的字符串被最小地扩展,使得它们都不是另一个的前缀,并且根据字典顺序决定比较。例如,使用键 x、y、z,我们可以:

x < y:  string(x) is now 0, string(y) is now 1
x < z:  string(z) is now 1
y < z:  string(y) is now 10, string(z) is now 11.

设 k 是一个操作进行的键比较次数的最坏情况上限。每个键比较最多将总字符串长度增加 2,因此对于最多 3 * n 次插入/删除操作的每个序列,总字符串长度最多为 6 * k * n。如果每当有一个字符串长度至少为 6 * k 的键时,我们插入 2 * n 个不同的键并穿插删除,那么我们在到达具有至少 n 个键的集合的途中最多删除 n 个键,其中每个键都有一个字符串小于 6 * k 位。

将每个密钥的字符串任意扩展为 6 * k 位。密钥字符串的 (6 * k) 位前缀是密钥所属的 bucket。目前,该算法没有关于桶内键的相对顺序的信息。有 2 ** (6 * k) 个桶,我们想象它们按照 (6 * k) 位前缀指定的递增顺序从左到右排列。对于足够大的 n,存在一个具有恒定(取决于 k)部分键的存储桶,并且至少是其右侧组合存储桶的 2 * k 倍。删除后面的键,max() 需要进行线性数量的额外比较,才能整理出现在拥有最大值的大桶,因为删除完成的所需工作最多只有一半多一点。

【讨论】:

    【解决方案2】:

    嗯,你知道max/min &lt; CONST,元素都是数字。基于此,您可以获得O(1) 插入和O(k+n/k) 查找最小/最大1。

    有一个大小为k 的数组,数组中的每个元素都是一个哈希集。在插入时,向array[floor((x-MIN)/MAX-MIN)*k)] 插入一个元素(x=MAX 的特殊情况)。假设元素分布均匀,这意味着每个哈希集都有预期数量的n/k 元素。
    删除时 - 类似地从相关集中删除。

    findMax() 现在完成如下:找到集合不为空的最大索引 - 它需要 O(k) 最坏情况,O(n/k) 在第一个非空集合中找到最大元素。

    寻找最优k:

    我们需要最小化 k+n/k。

    d(n+n/k)/dk = 1-n/k^2 = 0
    n = k^2
    k = sqrt(n)
    

    这给了我们O(sqrt(n) + n/sqrt(n)) = O(sqrt(n)) 平均找到最小值/最大值,插入/删除 O(1)。

    由于最大值和最小值的极端变化,有时您可能需要“重置”表格,但考虑到“安全边界” - 我相信在大多数情况下这不会成为问题。
    每次“重置” DS 时,请确保您的 MAX 类似于 2*max,MIN 类似于 1/2*min。


    (1) 假设所有元素都来自一个已知分布。在我的回答中,我假设一个均匀分布 - 所以 P(x)=P(y) 对于每个 x,y,但很容易将其修改为任何已知分布。

    【讨论】:

    • 非常好的方法。如果这是递归完成的,例如,两层深,每层有cbrt(n) 桶,那么复杂性是多少?我猜它主要会影响最小值/最大值。
    • @JanDvorak 启动它但有点卡住,基本上它会影响插入/删除为O(d) - 其中d 是深度。最小值/最大值现在将是 O(kd + n/k^d)。推导找到最小值将得到我们k=n^(-d-1),这给了我们O(n^(-d-1)d + n/(n^(-d-1))^d)。您认为我们可以更好地简化它吗?
    • 它只能在不存在的具有巨大 CPU 高速缓存内存的计算机上工作。 k 有多大?如果 k 很大(即数组很大,每个数字最多一个数组项),那么数组将占用大量内存。如果数组占用大量内存 - 您通常会访问主计算机内存而不是 CPU 缓存。每次访问主存需要 60-100 纳秒,而访问 CPU 缓存需要数次。如果你让 k 小到足以适应 CPU 缓存的整个结构,你就会扼杀你的算法的想法——每个篮子都会包含太多的项目。
    • 如果我有 10 个数字 1 , 2 , 3 , ... , 10 ,MAX-MIN 是 9 所以所有数字都将在同一位置 0 进行哈希处理。你将如何克服这一点?
    • 你的意思可能是floor( ( [ (x-MIN)*k ] /(MAX-MIN) )
    猜你喜欢
    • 2011-08-26
    • 1970-01-01
    • 1970-01-01
    • 2018-03-08
    • 2018-01-08
    • 1970-01-01
    • 2013-01-24
    • 2012-06-24
    • 1970-01-01
    相关资源
    最近更新 更多