如何在删除元素时防止重新散列 std::unordered_map?

Posted

技术标签:

【中文标题】如何在删除元素时防止重新散列 std::unordered_map?【英文标题】:How do I prevent rehashing of an std::unordered_map while removing elements? 【发布时间】:2012-11-23 16:56:45 【问题描述】:

我有一个 std::unordered_map,我将从迭代中删除元素。

auto itr = myMap.begin();
while (itr != myMap.end()) 
    if (/* removal condition */) 
        itr = myMap.erase(itr);
     else 
        ++itr;
    

我想阻止地图执行任何昂贵的操作,直到我完成删除所有需要删除的元素。我有正当的担忧吗?我是否误解了内部存储的工作原理?

【问题讨论】:

【参考方案1】:

erase 期间禁止无序容器重新散列:

[unord.req]/p14:

erase 成员只能使迭代器和对 被擦除的元素,并保持元素的相对顺序 没有被删除。

[unord.req]/p9:

重新散列使迭代器无效,更改元素之间的顺序,以及...

你的代码没有问题。

【讨论】:

我知道 4 年后我们正在研究这个问题,但我很高兴看到这个答案加入其中。再次查看文档,很明显最糟糕的复杂性不是来自潜在的重新散列,而是来自散列冲突。我认为这是正式的正确答案。 所以表只能增长。? erase 下,无序容器中的桶数永远不会减少。在rehash下允许数字缩小,所有实现都会这样做。【参考方案2】:

我不确定它是否有效,我在文档中没有找到确认 - 但如果 unordered_map 根据经典哈希表数据结构重新散列,您可以 set the max_load_factor到一个非常高的值并在完成后将其重置为正常值(这将触发重新哈希)(或者如果您可以预测将删除多少元素,则将其重置为预测值)。

就经典哈希表而言,它应该可以工作,因为当大小小于1/max_load_factor 时会发生减小表时的重新哈希。

(不确定在 C++ 中是否如此,但我认为值得一试,因为它真的很容易实现)。

【讨论】:

【参考方案3】:

据我所知,std::unordered_map 可以在erase(itr) 上重新散列:

C++11 表 103 -- 无序关联容器要求

a.erase(q)

删除指向的元素 通过q。返回值为 紧跟在q 之后的迭代器 在擦除之前。

平均情况 O(1), 最差 案子 O(a.size())

因此,您似乎确实有一个合理的担忧。至于解决它,我可以提出几个途径:

    确保这是一个实际问题而不是假设问题。分析应用程序,查看 C++ 库的源代码等。 如果是实际问题,请考虑使用不同的容器或不同的算法。 考虑通过与每个元素关联的布尔标志简单地标记要删除的元素,并不时清除已删除的元素,从而摊销成本。 考虑按照 cmets 中 @amit 的建议对负载系数进行试验。尽管仍允许容器花费 O(a.size()) 时间来擦除元素,但不同的负载因子可能会影响应用程序的实际性能。

【讨论】:

虽然信息丰富且相关 - 它没有回答问题:How do I prevent rehashing of an std::unordered_map while removing elements? @amit:如果您在字里行间阅读,确实如此(确切问题的答案是您不能:)) @amit:嗯,最坏的情况是O(a.size())。它不依赖于其他任何东西,包括负载因子。 @NPE:最坏的情况是基于非常糟糕的哈希值,而不是重新哈希。所有 unordered_* 操作都有可能此时容器中的所有对象碰巧具有相同的哈希值。我几乎可以肯定 .erase 目前被禁止重新散列。 这个答案不正确。我已经添加了一个正确的答案。

以上是关于如何在删除元素时防止重新散列 std::unordered_map?的主要内容,如果未能解决你的问题,请参考以下文章

散列更改时分离和重新附加元素

Java HashMap 内部数据结构在重新散列期间如何变化?

导航时如何防止div重新加载

在返回常量哈希码的情况下,Java8 Hashmap 重新散列

如何在 Touch Down 实现时防止 SwiftUI 上的重新触发

hashmap 或 hashtable 中的重新散列过程