c ++ unordered_map碰撞处理,调整大小和重新散列

时间:2015-06-28 09:31:26

标签: c++ c++11 hash hashmap unordered-map

我还没有读过C ++标准,但我觉得c ++的unordered_map假设可以工作。

  • 在堆中分配内存块。
  • 对于每个put请求,散列对象并将其映射到此内存中的空间
  • 在此过程中通过链接或开放寻址处理碰撞处理..

我很惊讶我找不到有关unordered_map如何处理内存的信息。是否存在unordered_map分配的特定初始内存大小。如果我们说我们分配了50个内存并且我们最终插入5000整数会发生什么?

这将是很多碰撞所以我认为应该有一种像重新散列和重新调整大小的算法,以在达到一定程度的碰撞阈值后减少碰撞次数。由于它们是作为成员函数显式提供给类的,因此我假设它们也在内部使用。有没有这样的机制?

3 个答案:

答案 0 :(得分:8)

  

对于每个put请求,散列对象并将其映射到此内存中的空间

不幸的是,这并不完全正确。您指的是开放式寻址封闭式散列数据结构,而不是指定unordered_map的方式。

每个unordered_map实现都将链表存储到存储桶数组中的外部节点。这意味着插入项目将始终至少分配一次(新节点),如果不是两次(调整存储桶数组,然后调整新节点)。

不,对于大多数常见用途而言,这并不是最有效的哈希映射方法。不幸的是,unordered_map规范中的一个小“疏忽”都需要这种行为。所需的行为是元素的迭代器在插入或删除其他元素时必须保持有效。因为插入可能导致桶阵列增长(重新分配),所以通常不可能让迭代器直接指向桶阵列并满足稳定性保证。

如果您要将昂贵的复制项目存储为您的密钥或值,那么

unordered_map是一种更好的数据结构。这是有道理的,因为它的一般设计是从Boost的移动前语义设计中解脱出来的。

Chandler Carruth(谷歌)在他的CppCon '14演讲"Efficiency with Algorithms, Performance with Data Structures"中提到了这个问题。

答案 1 :(得分:2)

std :: unordered_map包含一个负载因子,用于管理其内部存储桶的大小。 std :: unordered_map使用这个奇数因子将容器的大小保持在0.0和1.0因子之间。这降低了桶中发生碰撞的可能性。在那之后,我不确定它们是否会在发现碰撞的桶中回退到线性探测,但我会这样认为。

答案 2 :(得分:1)

<块引用>

在堆中分配一个内存块。

是的 - 有一个用于存储“桶”数组的内存块,在 GCC 的情况下,它实际上是能够记录前向链表中某个位置的迭代器。

<块引用>

对于每个 put 请求,散列对象并将其映射到该内存中的一个空间

不...当您将更多项目插入/放置到列表中时,将使用节点的 next 链接和被插入/放置的值的空间完成额外的动态(即堆)分配。链表相应地重新连接,因此新插入的元素链接到和/或来自散列到同一桶的其他元素,如果其他桶也有元素,则该组将链接到和/或来自节点那些元素。

在某些时候,哈希表的内容可能看起来像这样(GCC 就是这样做的,但也可以做一些更简单的事情):

           +------->  head
          /            |
bucket#  /            #503
[0]----\/              |
[1]    /\      /===> #1003
[2]===/==\====/        |
[3]--/    \     /==>  #22
[4]        \   /       |
[5]         \ /        #7
[6]          \         |
[7]=========/ \-----> #177
[8]                    |
[9]                   #100
                   
  • 左边的桶是原始分配的数组:图示数组中有 10 个元素,所以 "bucket_count()" == 10。

  • 具有散列值 X 的键 - 表示为 #x 例如#177 - 散列到存储桶 X % bucket_count();该存储桶将需要在第一个元素散列到该存储桶之前立即将迭代器存储到单向链表元素,因此它可以从存储桶中删除最后一个元素并重新连接头或另一个存储桶的next 指针,跳过被擦除的元素。

  • 虽然桶中的元素需要在前向链表中是连续的,但该列表中桶的顺序是容器中元素插入顺序的一个无关紧要的结果,并且在标准。

<块引用>

在此过程中通过链接或开放寻址处理冲突..

由哈希表支持的标准库容器总是使用单独的链接

<块引用>

我很惊讶我找不到太多关于 unordered_map 如何处理内存的信息。是否有 unordered_map 分配的特定初始内存大小。

不,C++ 标准没有规定初始内存分配应该是什么;由 C++ 实现来选择。您可以通过打印出 .bucket_count() 来查看新创建的表有多少个桶,并且很可能如果您将其乘以您的指针大小,您将获得无序容器所做的堆分配的大小:{ {1}}。也就是说,没有禁止您的标准库实现以任意和奇怪的方式(例如优化级别,取决于密钥类型)改变初始 myUnorderedContainer.bucket_count() * sizeof(int*),但我无法想象为什么会这样做。

<块引用>

如果假设我们分配了 50 个 int 内存并且我们结束了会发生什么 插入 5000 个整数?这将是很多碰撞,所以我相信应该有一种重新散列和重新调整大小的算法,以在达到一定程度的碰撞阈值后减少碰撞次数。

重新散列/调整大小不是由特定数量的碰撞触发,而是由加载因子衡量的特定倾向冲突,即{{3 }} bucket_count() .size()

当插入会将 .bucket_count() 推到 .load_factor() 之上时,您可以更改但 C++ 标准要求默认为 1.0,然后调整哈希表的大小。这实际上意味着它分配了更多的桶——通常接近但不一定是两倍——然后它将新桶指向链表节点,最后删除旧桶的堆分配。

<块引用>

因为它们是作为成员函数显式提供给类的,所以我假设它们也在内部使用。有这种机制吗?

没有关于如何实现调整大小的 C++ 标准要求。也就是说,如果我正在实现 /,我会考虑创建一个函数本地容器,同时指定新需要的 resize(),然后遍历 bucket_count 对象中的元素,调用 {{ 3}} 分离它们,然后 .max_load_factor() 将它们添加到函数本地容器对象,然后最终在 *this 和函数本地容器上调用交换。