更有效的填充unordered_set方式?

More efficient way to populate unordered_set?

本文关键字:set 方式 unordered 有效 填充      更新时间:2023-10-16

我有一个整数数组连续存储在内存中,我想将它们全部添加到unordered_set集合中。

现在,我一次添加一个。

for (int i = 0; i < count; i++)
collection.insert(pi[i]);

有没有办法更有效地做到这一点?

我意识到这些项目不是连续存储在集合中,因此它不会像将数组移交给集合那么简单。但这能以某种方式优化吗?

unordered_set有一个构造函数,该构造函数采用一系列元素来最初添加它们:

template< class InputIt >
unordered_set( InputIt first, InputIt last,
size_type bucket_count = /*implementation-defined*/,
const Hash& hash = Hash(),
const key_equal& equal = key_equal(),
const Allocator& alloc = Allocator() );

因此,您只需执行collection = std::unordered_set{ p, p + count };,然后由实施决定。

正如其他用户在评论中指出的那样,insert还有一个重载,它需要一个范围:

template< class InputIt >
void insert( InputIt first, InputIt last );

所以,就像调用构造函数一样,你可以做,collection.insert(p, p + count);

不能保证这种重载会更有效,因为平均重载的复杂性都是线性的,以及只是逐个插入元素。

事实上,如果我们研究一下如何在 MSVC 中实现insert,它非常简单。

template<class _Iter>
void insert(_Iter _First, _Iter _Last)
{   // insert [_First, _Last) at front, then put in place
_DEBUG_RANGE(_First, _Last);
for (; _First != _Last; ++_First)
emplace(*_First);
}

所以这种情况没有优化。

我认为,最好的方法是调用reserve,如果您知道要添加的许多元素,并且如果有很多冲突(整数不会有),也许可以修改bucket_count

使用基于范围的构造函数或插入方法将简洁而优雅,但可能与您的方法一样高效。 原因是传递给这些函数的迭代器是输入迭代器,而不是随机迭代器。 因此,无法计算范围的长度,并且当集合的负载系数达到高时,必须通过定期重新哈希逐个插入元素。

请考虑调用 std::unordered_set 的保留方法。

collection.reserve(pi.size());
collection.insert(pi.begin(), pi.end());

编辑: 如评论中所述,人们还可以担心逐个散列插入元素的效率。 然后,能够执行某种批量插入将是有效的。 但是,在OP的情况下,元素是整数,碰巧在std::hash的大多数(如果不是全部)实现中使用标识函数进行哈希处理,这不会花费那么多;)。事实上,它是随机整数可以获得的最佳哈希函数。其他哈希函数可能更适合"有组织"的集合。

编辑2: 注释部分现在推测插入方法的更好实现。 我坚持认为基于范围的插入重载要求输入迭代器,所以是的,您实际上可以传递任何非输出迭代器。 还可以查看范围插入的最坏情况复杂性:您将看到它被指定为允许逐个插入元素。 最后,看一下 insert 方法的一些实现,您会发现随机访问迭代器没有特定的重载。 这是有道理的,因为没有理由在插入方法中强制进行额外的检查,而保留方法在这里,用于我们希望将容器设置为至少给定容量的情况。 基于此,上面的答案很可能是基于 stdlib 实际实现的最佳技术。