为什么即使加载因子限制没有被破坏,std :: unordered_set也会重新出现?

时间:2018-03-17 07:02:14

标签: c++ hash stl unordered-set

根据cppreference

  

仅当新元素数大于max_load_factor()*bucket_count()时才会发生重新散列。

此外,[unord.req]/15也有类似的规则:

  

insertemplace成员如果(N+n) <= z * B不影响迭代器的有效性,其中N是插入操作之前容器中元素的数量, n是插入的元素数,B是容器的存储区计数,z是容器的最大负载系数。

但是,请考虑以下示例:

#include <unordered_set>
#include <iostream>

int main()
{
    std::unordered_set<int> s;
    s.emplace(1);
    s.emplace(42);
    std::cout << s.bucket_count() << ' ';
    std::cout << (3 > s.max_load_factor() * s.bucket_count()) << ' ';
    s.emplace(2);
    std::cout << s.bucket_count() << ' ';
}

使用GCC 8.0.1,输出

3 0 7

这意味着在放置2之后,虽然新的元素数(3)大于max_load_factor()*bucket_count(),但仍会发生重新散列(注意第二个输出为0)。为什么会这样?

3 个答案:

答案 0 :(得分:2)

来自 26.2.7无序关联容器

  

当元素添加到无序关联容器时,桶的数量会自动增加,以便每个桶的平均元素数保持在绑定之下。

b.load_factor()           Returns the average number of elements per bucket.

b.max_load_factor()       Returns a positive number that the container attempts 
                          to keep the load factor less than or equal to. The container
                          automatically increases the number of buckets as necessary
                          to keep the load factor below this number.

我同意,max_load_factor的描述的第一部分表明负载因子可以达到该值,但在第二部分和前面的引用中,它清楚地表明负载因子将是保持低于这个数字。所以,你在cppreference中发现了一个错误。

在您的代码中,如果没有重新散列,则在第三次插入后,您的s.load_factor将等于s.max_load_factor()

编辑:为了回答我在unordered_set实施的问题中所做的更改,它实现为

// hash table -- list with vector of iterators for quick access

然后你要求一个迭代器,使用例如lower_bound,你得到list元素的迭代器,它不会通过重新散列而失效。所以,它同意[unord.req] / 15。

答案 1 :(得分:2)

你感到困惑的是,bucket_count()已经因迭代器的失效而发生了变化。迭代器只有在重新散列时才会失效,如果新元素数小于或等于max_load_factor()*bucket_count()(如果size()>max_load_factor()*bucket_count()重新发送

由于在您的示例中不是这种情况,因此不会发生重新散列并且迭代器仍然有效。但是,必须增加桶数以适应新元素。

我使用Mac OSX的clang进行了一些实验(扩展了你的代码),这使得迭代器在rehash(size())之后保持有效(它确实改变了元素的桶关联,通过遍历桶并打印其内容直接测试)。

答案 2 :(得分:0)

Issue 2156起,重新哈希条件已更改。更改之前,当新元素的数量不少于 max_load_factor()*bucket_count()时,将发生重新哈希,并且在更改之后,哈希变得“大于”。

GCC 8.0.1未实现此更改。已经有bug report,并且已在GCC 9中修复。