c++ unordered_map collision handling , resize and rehash
这是我之前提出的一个问题,我看到我对unordered_map的实现方式感到很困惑。我相信很多其他人都会和我分享这种困惑。基于我所知道的信息而不阅读标准:
每个unordered_map实现都将链表存储到外部 存储桶数组中的节点...不,这根本不是最重要的 为大多数常见用途实现哈希映射的有效方法。 不幸的是,规范中的一个小“疏忽” unordered_map all但需要此行为。所需的行为是 插入或删除元素的迭代器必须保持有效 其他元素
我希望有人可以解释实现以及它如何与c ++标准定义(在性能要求方面)相对应,以及它是否真的不是实现哈希映射数据结构的最有效方法如何改进它?
答案 0 :(得分:56)
标准有效地要求使用开放哈希的std::unordered_set
和std::unordered_map
实现,这意味着一个桶阵列,每个桶都包含一个逻辑(通常是实际)列表的头部。这个要求是微妙的:它是默认最大载荷因子为1.0的结果,并且保证除非增长超出该载荷因子,否则不会对表进行重新定义:如果没有链接,这将是不切实际的,因为与闭合散列的碰撞变为负载因子接近1时势不可挡:
23.2.5 / 15:如果
insert
,emplace
和(N+n) < z * B
成员不会影响迭代器的有效性,其中N
是容器中元素的数量在插入操作之前,n
是插入的元素数,B
是容器的存储桶计数,z
是容器的最大负载系数。在23.5.4.2/1的构造函数的效果中:
max_load_factor()
返回1.0
。
(为了允许最佳迭代而不通过任何空桶,GCC的实现将带有迭代器的桶填充到一个包含所有值的单个链接列表中:迭代器指向紧接在该桶之前的元素&#39 ; s元素,因此如果删除存储桶的最后一个值,则可以重新连接下一个指针。)
关于您引用的文字:
不,对于大多数常见用途而言,这并不是最有效的哈希映射方法。不幸的是,一个小小的&#34;疏忽&#34;在unordered_map的规范中,只需要这种行为。所需的行为是元素的迭代器在插入或删除其他元素时必须保持有效
没有&#34;监督&#34; ......所做的事情是非常慎重的并且完全意识到了。确实可以实现其他妥协,但开放式散列/链接方法对于一般用途来说是一种合理的折衷方案,它可以合理地优雅地处理来自平庸散列函数的冲突,对于小的或者不太浪费大型键/值类型,并处理任意多个insert
/ erase
对,而不会像许多闭合哈希实现那样逐渐降低性能。
作为意识的证据,来自Matthew Austern's proposal here:
我不知道在通用框架中有任何令人满意的开放式寻址实现。开放式寻址存在许多问题:
•区分空缺职位和被占职职位是必要的。
•有必要将哈希表限制为具有默认构造函数的类型,并提前构造每个数组元素,或者维护一个数组,其中一些元素是对象,其他元素是原始记忆。
•开放式寻址使冲突管理变得困难:如果您要插入其哈希码映射到已占用位置的元素,您需要一个策略来告诉您下一步尝试的位置。这是一个已解决的问题,但最着名的解决方案很复杂。
•允许删除元素时,冲突管理尤其复杂。 (请参阅Knuth进行讨论。)标准库的容器类应该允许擦除。
•开放寻址的冲突管理方案倾向于采用固定大小的阵列,最多可容纳N个元素。插入新元素时,标准库的容器类应该能够根据需要增长,直到可用内存的限制。
解决这些问题可能是一个有趣的研究项目,但是,由于缺乏C ++环境中的实现经验,标准化开放寻址容器类是不合适的。
特别是对于只有插入的表,其数据足够小以便直接存储在存储桶中,未使用的存储桶的方便的标记值,以及良好的散列函数,封闭的散列方法可能大约快一个数量级,并且使用率大大降低记忆,但这不是一般目的。
散列表设计选项的完整比较和详细说明及其含义是S.O.的主题。因为它太宽泛而无法在这里妥善解决。