Set 在检查值时实际上如何在内部工作?

How does Set actually work internal when checking for values?

这是一个基于计算机科学的非常普遍的问题,但根据有关它们如何工作的文献,这个问题似乎并不直观。这是一个与语言无关的问题,但与 Set 数据类型在内部的工作方式有关。

我用过很多次了,推荐使用来存储唯一值,快速访问。据推测,在 Big-O 表示法中,每次访问 Set 时其时间和复杂度为 O(1)。如果 Set 可能包含数千个项目,那怎么可能呢?即使项目是唯一的。

为了在 Set 中找到一个项目,它仍然必须扫描每个唯一的项目,这在 Big-O 中在时间和复杂度上都是 O(n)。我在这里遗漏了什么吗?

在此先感谢您的帮助!最彻底的答案获得赞成票!

A Set 是一种更普遍的对象,统称为 HashedCollections。它们使用某种 HashTable 来实际存储和检索它们的元素。

给定任何 element,这些 table 计算一个整数值,命名为 hash。有几种众所周知的技术可以定义元素与其 hash 值之间的映射。有些是 固有的 ,因为 hash 不依赖于 element 的属性,后者可能会改变,因此 hashelement 的生命周期中保持不变。其他的是 外部的 ,因为它们可能取决于属性。但是,在后一种情况下,假定特定元素在从 HashedCollection 引用时不会被修改(否则 HashedCollection 必须是 rehashed)。

存储element的过程如下:

  1. hash 是为 element 计算的。
  2. table 的 index 计算为 hash 对 table 的 length 取模的余数。
  3. 如果计算出的 index 处的插槽已被占用,则应用某些策略来解决 碰撞

第 1 步应该非常快(例如,hash 没有 cryptographic 强度)。

步骤 2 假设(在大多数情况下)table 的长度是一个 prime 数(也使用 2 的幂)

步骤 3 基本上可以通过两种不同的方式解决:

  • table 被顺序扫描 j 次,直到 index + j 的插槽空闲,或者
  • 该元素被添加到在给定 index (bucket)
  • 碰撞的元素集​​合中

此外,如果没有足够的空槽(这增加了碰撞的可能性),table被放大并且rehashed(因为modulo改变了)。

如果有足够的空闲插槽和相当随机的索引机制分布,在 O(1) 中找到所需插槽的概率非常高。当然,如果有太多元素发生碰撞,平均复杂度就不再是 O(1),但这应该可以通过不断增长的策略 (+ rehash).

来缓解

检索类似。要检查 element 是否属于集合,计算其 hashmodulo 并将 element 与目标槽的内容进行比较。如果比较失败,则搜索在桶中线性进行。

当没有 bucket 而增加了 indexes 时,删除元素会有些困难,但你明白了。

如果您真的想在工作中看到所有这些,请继续在任何 Smalltalk 方言中调试 HashedCollections 的基本操作。保证有很多乐趣。