【问题标题】:Why does dict have worst case O(n) for so many operations?为什么 dict 对于这么多操作有最坏情况 O(n) ?
【发布时间】:2011-02-01 01:09:08
【问题描述】:

dict 究竟是如何实现的,它具有对冲突的线性时间查找?我会假设它是作为由列表支持的哈希表实现的。我认为对于各种操作,更好的实现是 O(log(n)),而是使用树来支持表。幕后是否发生了一些神奇的事情来尽可能长时间地保持持续的时间查找?

顺便说一句,我的来源是:

http://www.google.com/search?sourceid=chrome&ie=UTF-8&q=python+complexity

【问题讨论】:

  • 最坏情况的复杂性并不是唯一值得优化的因素。
  • “我认为对于各种操作来说更好的实现是 O(log(n))”,为什么?你见过这方面的任何基准吗?我的理解是“随机”探测实际上平均来说是最快的,并且导致 O(n) 作为最坏的情况。你在假设什么,你看到了什么测量结果?
  • 我认为 Python dicts 使用 32 位密钥,这意味着您需要 2**31 或接近 620000000000000 个密钥才能预期 单个冲突(不包括其实现的对象__hash__ 真的很糟糕,但我宁愿将其视为一个错误)。所以碰撞真的没有实际意义,花在优化上的时间是浪费时间。
  • @Jochen,我认为您对哈希函数有不切实际的期望。在用尽桶之前给你一个碰撞是不是真的很糟糕,它实际上很常见。看看你在生日冲突之前可以通过多少人,它肯定不会是 365。你可以拥有完美的哈希函数,但前提是你了解数据提前。给定一个通用的散列函数,如果你知道算法,你可以只用两个条目创建一个冲突。
  • @Jochen:当然有碰撞;哈希表通常没有 2^32 个桶。 (另外,2^31 只是 2147483648,而不是 620000000000000——而且你完全忘记了生日问题。)

标签: python


【解决方案1】:

Dict 对于大多数操作来说是 O(1),除了涉及所有元素的操作,例如迭代和复制(在这种情况下,它显然是 O(n))。

见:http://wiki.python.org/moin/TimeComplexity

它有 O(n) 最坏的情况,因为你总是可以设计一个所有键都具有相同哈希值的病态示例。

【讨论】:

  • 好答案。重要的是要记住Big-O 是一个上限——即使amortized performance 明显更好。不幸的是,摊销的性能通常被视为复杂性。
【解决方案2】:

选择一种实现而不是另一种实现的重点不一定是upper-bound,而是预期的amortized performance。虽然不同的算法可能有退化的情况,但它通常比使用具有可证明的下限的方法“在实践中更好”。然而,在某些情况下,必须设计结构来防止病态的错误输入。

此外,某些语言/库(不确定 Python)实际上会更改底层实现,例如当项目数超过低 n 时。这会影响摊销性能(在某些情况下),但不一定会影响big O

最后:“这取决于”。

编码愉快。

【讨论】:

    【解决方案3】:

    考虑一下即使是银河系中最好的散列函数。仍然有可能有一天你会带着一列值,这些值的最佳散列函数值恰好都是相同的。如果将它们放在字典中,系统别无选择,只能执行线性搜索。

    使用平衡树可以将最坏情况的时间缩短为 O(log n),但维护成本相当高。通常,哈希表的性能相当不错。

    【讨论】:

      【解决方案4】:

      我认为对于各种操作来说更好的实现是 O(log(n)),而是使用树来支持表。

      树和哈希表有非常不同的要求和性能特征。

      • 树需要有序类型。
      • 树需要执行顺序比较才能找到对象。对于某些对象,例如字符串,这会阻止一些重要的优化:您总是需要执行字符串比较,这非常昂贵。这使得 O(log n) 的常数因子相当高。
      • 哈希表需要可哈希类型,并且您可以测试是否相等,但它们不需要有序类型。
      • 可以显着优化相等性测试。如果两个字符串被实习,您可以通过比较它们的指针来测试它们在 O(1) 中是否相等,而不是通过比较整个字符串来测试它们是否相等。这是一个大规模优化:在每个被转换为foo.__dict__["bar"]foo.bar 查找中,"bar" 是一个内部字符串。
      • 哈希表在最坏的情况下是 O(n),但请检查导致最坏情况的原因:非常糟糕的哈希表实现(例如,您只有一个桶),或者总是返回相同结果的损坏的哈希函数价值。当您拥有适当的哈希函数和适当的分桶算法时,查找非常便宜 - 通常接近恒定时间。

      树木确实有显着的优势:

      • 它们的内存需求往往较低,因为它们不必预先分配存储桶。最小的树可能是 12 字节(节点指针和两个子指针),其中哈希表往往是 128 字节或更多——我的系统上的 sys.getsizeof({}) 是 136。
      • 它们允许有序遍历;能够在有序集中迭代 [a,b) 非常有用,而哈希表不允许这样做。

      我确实认为 Python 没有标准的二叉树容器是一个缺陷,但对于 Python 核心所需的性能特征,例如 __dict__ 查找,哈希表确实更有意义。

      【讨论】:

        【解决方案5】:

        关于散列函数和冲突解决策略的可靠信息来源实际使用包括源文件dictobject.c中的cmets和整个文件dictnotes.txt

        【讨论】:

          猜你喜欢
          • 2021-06-12
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-12-09
          • 2020-12-05
          • 2018-02-13
          • 2015-05-17
          • 1970-01-01
          相关资源
          最近更新 更多