【问题标题】:how to create a collection with O(1) complexity如何创建具有 O(1) 复杂度的集合
【发布时间】:2010-07-28 13:00:32
【问题描述】:

我想创建一个数据结构或集合,它在添加、删除和计算 no 时具有 O(1) 的复杂性。的元素。我应该如何开始?

我想到了一个解决方案:我会使用一个 Hashtable 并且对于插入的每个键/值对,我将只有一个哈希码,即:我的哈希码算法每次都会生成一个唯一的哈希值,所以存储值的索引将是唯一的(即没有冲突)。

这会给我 O(1) 复杂度吗?

【问题讨论】:

  • “我的哈希码算法每次都会生成唯一的哈希值”——我想很多人都会有兴趣看到这种哈希码算法的源代码 ;-) 请注意,在现实生活中,你正在将可能无限数量的不同对象映射到有限数量的哈希码,因此碰撞的机会实际上永远不会为 0(某些特殊情况除外)。
  • 我有一个独特的哈希算法来存储 32 位整数。哈希(i)==i :)
  • 我也知道这样的算法。问题是它需要O(N) 空间:-)
  • 你提前知道所有可能的键吗?如果你这样做,你可以建立一个完美的哈希。
  • @Constantin,正如斯蒂芬所说。我知道 long 的所有可能值。这是否意味着我可以为 Long 构建一个不冲突的 32 位哈希?

标签: algorithm data-structures collections hash theory


【解决方案1】:

是的,这会起作用,但正如您提到的,您的散列函数需要 100% 唯一。任何重复都会导致您不得不使用某种冲突解决方案。我会推荐线性链接。

编辑: Hashmap.size() 允许 O(1) 访问

编辑 2: 回应拉里造成的混乱 =P

是的,散列是 O(k),其中 k 是密钥长度。每个人都可以同意这一点。但是,如果您没有完美的哈希,您根本无法获得 O(1) 时间。您的主张是您不需要唯一性来实现 O(1) 删除特定元素。我向你保证这是错误的。

考虑一个最坏的情况:每个元素都散列到相同的东西。你最终得到一个链表,众所周知,它没有 O(1) 删除。正如你所提到的,我希望没有人愚蠢到可以制作这样的哈希。

重点是,哈希的唯一性是 O(1) 运行时的先决条件。

即便如此,从技术上讲,它也不是 O(1) 大 O 效率。只有使用摊销分析,您才能在最坏的情况下实现恒定的时间效率。正如维基百科关于摊销分析的文章所述

基本思想是,最坏情况下的操作可以改变状态,使最坏情况在很长一段时间内不会再次发生,从而“摊销”其成本。

这指的是在某些负载因子下调整哈希表的大小(改变数据结构的状态)可以确保更小的冲突机会等。

我希望这能解决所有问题。

【讨论】:

  • 还好没有size() 方法这样的东西,没有先生!自从在 Java 1.2 中创建 HashMap 类型以来,它肯定不会存在。毕竟,那会证明你错了……
  • 为什么不编辑你的答案,这样每个人都可以直接看到...... ;-)
  • 散列不需要 100% 唯一——我认为提问者不理解 O(1) 或散列,因为即使没有唯一性也是 O(1)
  • @Larry:你已经在几个不同的地方发布了这个废话,但我不认为 理解哈希表在你接近 100% 的概率时将如何工作碰撞次数。
【解决方案2】:

添加、删除和大小(前提是单独跟踪,使用简单的计数器)可以由链表提供。除非您需要删除特定项目。您应该更具体地说明您的要求。

【讨论】:

    【解决方案3】:

    即使您确切地知道要散列的事物的空间,执行一个完全不冲突的散列函数也是相当棘手的,而且通常是不可能的。它还很大程度上取决于您要散列到的数组的大小。也就是说,您需要确切地知道自己在做什么才能使这项工作发挥作用。

    但是,如果您稍微放松一下,使相同的哈希码不暗示相等1,那么您可以将现有的 Java HashMap 框架用于所有其他部分。您需要做的就是在您的密钥类中插入您自己的hashCode() 实现,这是Java 一直支持的。并确保您也正确定义了平等。在这一点上,你得到的各种操作并不比 O(1) 贵多少,特别是如果你对容量和负载因子有一个很好的初始估计。

    1当然,相等必须意味着相等的哈希码。

    【讨论】:

    • 它并不是“比 O(1) 贵不了多少”,它正好是 O(1)。但是 +1 因为其他一切都是正确的。
    • @Larry:这取决于你的 hashCode 实现,真的。您不会从定义为return 42; 的 hashCode 中获得 O(1) 效率。但在 OP 的情况下,显然它分布得很好,所以这里不是问题。
    • @Larry:这不完全是 O(1),但区别确实很复杂,而且最终效果非常相似。有一个好的散列函数和一个不太重负载的散列,就足够了。
    • @Donal:我认为散列正好是 O(1)
    • @Larry:实际上是 O(k),其中 k 是密钥长度(对于短密钥,没什么大不了的,对于长密钥,很重要)加上一个复杂的术语,它取决于哈希表,其中考虑了冲突。基本上,一旦冲突变得足够频繁,将哈希表重建得更大是一个胜利,但这是一个相当昂贵的操作。
    【解决方案4】:

    即使您的哈希码是唯一的,这也不能保证无冲突的集合。这是因为您的哈希映射不是无限大小的。哈希码必须减少到哈希图中的桶数,在减少之后,您仍然可能会遇到冲突。

    例如假设我有三个对象 A (hash: 2), B (hash: 18), C (hash: 66) 都是唯一的。 假设您将它们放在容量为 16(默认值)的 HashMap 中。如果在减少哈希码后将它们映射到具有 % 16 的存储桶(实际上比这更复杂),我们现在有 A(哈希:2 % 16 = 2)、B(哈希:18 % 16 = 2)、C(哈希:66 % 16 = 2)

    HashMap 可能比 Hashtable 更快,除非您需要线程安全。 (在这种情况下,我建议你使用 CopncurrentHashMap) 恕我直言,Hashtable 已成为 12 年的遗留集合,我建议您仅在必要时使用它。

    【讨论】:

      【解决方案5】:

      您需要哪些链表无法提供的功能?

      【讨论】:

      • 一个存储对头部的引用的链表可以作为一个堆栈,对于 push/pop/size 的 O(1)。添加对尾部的引用,它可以是一个 O(1) 的队列,用于入队/出队/大小。 OP 没有指定随机访问,只是 O(1) 用于添加/删除/大小。
      • 好吧,我知道你来自哪里,虽然我不认为删除操作是 OP 的目的。不过,在这种情况下,我会特别说队列或堆栈。很遗憾无法删除 d/v。
      【解决方案6】:

      令人惊讶的是,如果您提前知道所有要放入集合中的密钥,您的想法就会奏效。这个想法是生成一个special hash function,它将每个键映射到(1, n)范围内的唯一值。那么我们的“哈希表”只是一个简单的数组(+一个整数来缓存元素个数)

      实现这一点并非易事,但也不是火箭科学。我会把它留给Steve Hanov来解释细节,因为他给出的解释比我能做的要好得多。

      【讨论】:

        【解决方案7】:

        这很简单。只需使用哈希映射。你不需要做任何特别的事情。 Hashmap 本身的插入、删除、计算元素个数是 O(1)。

        即使键不是唯一的,只要 Hashmap 在集合变得太大时自动扩展大小,算法仍然是 O(1)(大多数实现会自动为您执行此操作)。

        所以,只需根据给定的文档使用哈希映射,一切都会好起来的。不要想更复杂的事情,那只会浪费时间。

        使用哈希确实不可能避免冲突。如果可能的话,那么它基本上只是一个数组或到数组的映射,而不是哈希。但是没有必要避免碰撞,碰撞仍然是 O(1)。

        【讨论】:

        • 不需要避免碰撞?一切都发生了冲突,你退化为O(N) 搜索。使哈希表具有抗冲突性绝对重要。
        • 关于自动增加大小,我可能是错的,但不是只有 O(1) 使用摊销分析吗?此外,如果您使用线性链接作为您的碰撞系统并且您没有唯一的哈希函数,您怎么能声称删除特定元素仍然是 O(1)?您的存储桶基本上会变成链表。除非我遗漏了一些关于 Java Hashmap 实现的内容,否则如果没有 100% 唯一的散列函数,删除不会是 O(1)。
        • 除非你做一些非常愚蠢的事情,比如使用 mod 函数,否则想出一个好的散列并不难。库中使用的默认值应该足够好@JGord - 尝试查找 Aho 的算法书 - 实际上是任何算法书 - 他们会清楚地告诉你散列是 O(1)。
        • @Larry:制作一个好的哈希并不难,但制作一个非常糟糕的哈希也很简单。我曾经看到一个点类 (int x, int y) 的实现,它在内部使用 x 的高位存储一个 long,y 的低位存储。他们想,“哦,为什么不使用 Long 的 hashCode 实现呢?”麻烦的是,当高位和低位之间存在对应关系时,这是一个可怕的哈希。对 x 和 y 的 0 到 1000 之间的每个点进行散列,从 1000000 个组合中产生 1024 个唯一散列......碰撞率为 99.9%
        • 简而言之,您有时需要深入了解您的域才能进行有效的散列。
        猜你喜欢
        • 1970-01-01
        • 2018-03-26
        • 2020-08-18
        • 1970-01-01
        • 1970-01-01
        • 2021-07-06
        • 2016-04-21
        • 2019-08-16
        • 2013-03-21
        相关资源
        最近更新 更多