set vs unordered_set用于最快的迭代

时间:2014-07-01 09:27:05

标签: c++ c++11 stl set unordered-set

在我的申请中,我有以下要求 -

  1. 数据结构只会填充一些值(不是键/值对)。 这些值可能会重复,但我希望数据结构只存储一次。

  2. 我将通过上面创建的数据结构的所有元素迭代100次。元素在迭代中出现的顺序并不重要。

  3. 约束1表明我必须使用set或unordered_set,因为数据不是键值对的形式。

    现在设置插入比unordered_set插入更昂贵,但数据结构只在我的程序开头填充一次。

    我认为决定因素是我可以多快地遍历数据结构的所有元素。我不确定set或unordered_set对于此目的是否会更快。我相信标准没有提到这个事实,因为这个操作对于任一数据结构都是O(n)。但我想知道iterator.next()的数据结构会更快。

5 个答案:

答案 0 :(得分:12)

有几种方法。

  1. 对您的问题的评论建议保留std::unordered_set具有最快O(1)查找/插入和O(N)次迭代的O(N)(与每个容器一样)。如果您的数据变化很大,或需要大量随机查找,这可能是最快的。但测试
  2. 如果您需要在没有中间插入的情况下迭代100次,则可以对std::vector执行单个std::unordered_set复制,并从连续的内存布局中获取100次。 测试这是否比常规boost::flat_set更快。
  3. 如果迭代之间有少量中间插入,则可以使用专用向量。如果您可以使用Boost.Container,请尝试std::set,它提供std::vector接口和flat_set存储后端(即连续的内存布局,非常缓存和预取友好)。同样,测试这是否能加速其他两个解决方案。
  4. 对于最后一个解决方案,请参阅Boost文档以了解一些权衡(了解所有其他问题,如迭代器失效,移动语义和异常安全性):

      

    Boost.Container flat_ [multi] map / set容器是ordered-vector   基于Austern's和Alexandrescu的基于关联的容器   准则。这些有序的载体容器也受益   最近,为C ++添加了移动语义,加速了   插入和擦除时间相当大。扁平关联容器   具有以下属性:

         
        
    • 比标准关联容器更快的查找
    •   
    • 比标准关联容器
    • 更快的迭代次数   
    • 减少小对象(以及使用shrink_to_fit时的大对象)的内存消耗
    •   
    • 改进了缓存性能(数据存储在连续内存中)
    •   
    • 非稳定迭代器(插入和擦除元素时迭代器无效)
    •   
    • 无法存储不可复制和不可移动的值类型
    •   
    • 比标准关联容器更弱的异常安全性(复制/移动构造函数在删除擦除值时可以抛出   和插入)
    •   
    • 比标准关联容器更慢插入和删除(特别适用于不可移动类型)
    •   

    注意:查询速度越快,意味着O(log N)对连续内存执行O(log N)而不是std::set指针追逐常规std::unordered_set }}。当然,O(1)N查找,对于大型{{1}}来说会更快。

答案 1 :(得分:5)

我建议您使用set或unordered_set进行“过滤”,完成后,将数据移动到固定大小的矢量

答案 2 :(得分:4)

如果构建数据结构不考虑性能问题(或者至少只是略微考虑),请考虑将数据保存到std::vector:没有任何事情可以打败它。

为了加快数据结构的初始构建,您可能首先插入std::unordered_set或至少使用一个用于在插入之前检查存在。

在第二种情况下,它不需要包含元素,但可以包含例如指数。

std::vector<T> v;
auto h = [&v](size_t i){return std::hash<T>()(v[i]);};
auto c = [&v](size_t a, size_t b){return v[a] == v[b];};
std::unordered_set<size_t, decltype(h), decltype(c)> tester(0, h, c);

答案 3 :(得分:3)

我强烈建议您在这种情况下使用。 set是二叉树,unordered_set是哈希表 - 因此它们使用大量内存,并且迭代速度慢且引用的局部性差。如果您必须经常插入/删除/查找数据,setunordered_set这是一个不错的选择,但现在您需要只读取,存储,排序数据一次,仅使用数据很多次。

在这种情况下,排序的矢量可以是一个很好的选择。 vector是动态数组,因此开销很低。

直接看看代码。

std::vector<int> data;

int input;
for (int i = 0; i < 10; i++)
{
    std::cin >> input;
    data.push_back(input); // store data
}

std::sort(data.begin(), data.end()); // sort data

这就是全部。您的所有数据都准备好了。

如果您需要删除重复项set,请在排序后使用unique - erase

data.erase(
    std::unique(data.begin(), data.end()),
    data.end()
    );

请注意,您应该使用lower_boundupper_boundequal_range而不是findfind_if来使用排序数据。

答案 4 :(得分:2)

无序集使用散列表提供近O(1)时间搜索。这是通过使用键的散列来计算从数据集的开头搜索的元素(键)的偏移量来完成的。除非您的数据集很小(如char s),否则不同的键可能具有相同的哈希值(冲突)。

为了最大限度地减少冲突,无序集必须保持数据存储相当稀疏。这意味着找到一个键最多是O(1)时间(除非发生碰撞)。

然而,当迭代遍历散列表时,我们的迭代器将在我们的数据存储区中遇到大量未使用的空间,这将减慢迭代器对下一个元素的查找速度。我们可以使用额外的指针链接散列表中的相邻元素,但我不认为无序集合会这样做。

鉴于上述情况,我建议您使用排序向量作为“集合”。使用二分法,您可以在O(log n)时间搜索商店,并且遍历列表是微不足道的。向量具有额外的优势,即内存是连续的,因此您不太可能遇到缓存未命中。