为什么有人会使用set而不是unordered_set?

Ara*_*raK 134 c++ algorithm data-structures c++11

C++ 0x正在引入unordered_set,可以在boost许多其他地方使用.我理解的是unordered_set具有O(1)查找复杂性的哈希表.另一方面,set只是具有log(n)查找复杂性的树.为什么人们会使用set而不是unordered_set?即是否需要set了?

sth*_*sth 300

无序集必须以几种方式支付其O(1)平均访问时间:

  • set使用较少的内存而不是unordered_set存储相同数量的元素.
  • 对于少量元素,a中的查找set可能比查找中的查找更快unordered_set.
  • 尽管很多操作都在更快的平均情况unordered_set,他们经常保证有更好的最坏情况复杂set(例如insert).
  • set 排序的元素,如果你想顺序访问它们是有用的.
  • 您可以按字典顺序比较不同sets的<,<=,>>=.unordered_sets不需要支持这些操作.

  • +1,所有优点.人们倾向于忽略哈希表具有O(1)*平均情况*访问时间这一事实,这意味着它们偶尔会有很大的延迟.这种区别对于实时系统很重要. (8认同)
  • 定义"少量元素" (5认同)
  • @SunjayVarma通常100个元素是两者之间的良好截止.如果有疑问,在您的特定用例中没有任何东西可以取代两者的测试性能. (4认同)
  • @MichieluithetBroek仅声明相等比较,而不排序(`&lt;`)。 (2认同)

moo*_*dow 209

当对于想要迭代集合项目的人来说,顺序很重要.

  • 我想你的答案就是我所缺少的:) (3认同)
  • 它默认使用std :: less进行排序; 你可以覆盖它并提供自己的比较运算符.http://www.cplusplus.com/reference/set/set/ (2认同)

Meh*_*ari 26

每当您更喜欢树到哈希表时.

例如,在最坏的情况下,哈希表是"O(n)".O(1)是平均情况.树木最糟糕的是"O(log n)".

  • 在最坏的情况下,/ Balanced/trees是O(ln n).你最终可以得到O(n)树(基本上是链表). (17认同)
  • stager:是迂腐,是的.但是,我们讨论的是C++中的set,它通常被实现为**平衡二进制搜索树**.我们应该指定实际操作来谈论复杂性.在这种情况下,很明显我们正在谈论查找. (6认同)
  • 如果你可以编写一个合理智能的哈希函数,你几乎总能从哈希表中得到O(1)perf.如果你不能编写这样的哈希函数,如果你需要在你的集合上"按顺序"迭代,那么你应该使用一棵树.但你不应该使用树,因为你害怕"O(n)最糟糕的表现". (5认同)
  • stl树几乎是普遍实施的红黑树,一种先进的自平衡树.确实存在O(n)在更坏的情况下查找是不可接受的情况.提供存储用户值的界面的Web服务不应使用哈希映射,因为恶意用户可以通过存储特制值来有效地创建DoS.关键的,时间敏感的系统也可能不允许进行O(n)查找,空中交通管制等.虽然一般来说你是对的,默认情况下使用哈希映射,只有在你真正需要时才切换树版本. (2认同)

Jay*_*llo 9

使用时设置:

  1. 我们需要有序数据(不同的元素).
  2. 我们必须打印/访问数据(按排序顺序).
  3. 我们需要元素的前身/后继者.

在以下情况下使用unordered_set:

  1. 我们需要保留一组不同的元素,不需要排序.
  2. 我们需要单个元素访问,即没有遍历.

例子:

组:

输入:1,8,2,5,3,9

输出:1,2,3,5,8,9

Unordered_set:

输入:1,8,2,5,3,9

输出:9 3 1 8 2 5(也许这个顺序,受哈希函数的影响)

主要区别:

在此输入图像描述

注意:(在某些情况下set更方便)例如使用vectoras键

set<vector<int>> s;
s.insert({1, 2});
s.insert({1, 3});
s.insert({1, 2});

for(const auto& vec:s)
    cout<<vec<<endl;   // I have override << for vector
// 1 2
// 1 3 
Run Code Online (Sandbox Code Playgroud)

之所以vector<int>可以作为关键set因为vector覆盖operator<.

但是如果你使用unordered_set<vector<int>>你必须创建一个哈希函数vector<int>,因为vector没有哈希函数,所以你必须定义一个像:

struct VectorHash {
    size_t operator()(const std::vector<int>& v) const {
        std::hash<int> hasher;
        size_t seed = 0;
        for (int i : v) {
            seed ^= hasher(i) + 0x9e3779b9 + (seed<<6) + (seed>>2);
        }
        return seed;
    }
};

vector<vector<int>> two(){
    //unordered_set<vector<int>> s; // error vector<int> doesn't  have hash function
    unordered_set<vector<int>, VectorHash> s;
    s.insert({1, 2});
    s.insert({1, 3});
    s.insert({1, 2});

    for(const auto& vec:s)
        cout<<vec<<endl;
    // 1 2
    // 1 3
}
Run Code Online (Sandbox Code Playgroud)

你可以看到在某些情况下unordered_set更复杂.

主要引用自:https : //www.geeksforgeeks.org/set-vs-unordered_set-c-stl/ /sf/answers/2089918141/


小智 7

抱歉,关于排序属性还有一件事值得注意:

如果您想要容器中的一系列数据,例如:您在set中存储了时间,并且您想要从 2013-01-01 到 2014-01-01 的时间。

对于unordered_set来说这是不可能的。

当然,对于mapunordered_map之间的使用案例,这个示例会更有说服力。


Cir*_*四事件 7

g++ 6.4 stdlibc++ 有序 vs 无序集合基准

我对这个占主导地位的 Linux C++ 实现进行了基准测试以查看差异:

在此处输入图片说明

完整的基准详细信息和分析已在以下位置给出:C++ 中 STL 集的底层数据结构是什么?我不会在这里重复它们。

“BST”的意思std::set是“用std::unordered_set. “堆”是std::priority_queue我分析的:堆与二叉搜索树(BST)

作为一个快速总结:

  • 该图清楚地表明,在这些条件下,当超过 10 万个项目时,hashmap 插入总是要快得多,并且差异随着项目数量的增加而增长

    这种速度提升的代价是您无法有效地按顺序遍历。

  • 曲线清楚地表明orderedstd::set是基于BST的并且std::unordered_set是基于hashmap的。在参考答案中,我通过GDB一步调试代码进一步确认。

mapvs 的类似问题unordered_map在关键的情况下,使用 map 比 unordered_map 有什么优势吗?


小智 6

因为std :: set是标准C++的一部分而unordered_set不是.C++ 0x不是标准,也不是Boost.对于我们许多人来说,便携性是必不可少的,这意味着坚持标准.

  • 嗯,这是一个标准的_now_(仅用了几年) (19认同)
  • 如果我理解他,他不会问为什么人们目前仍然使用套装.他正在告诉自己有关C++ 0x的信息. (2认同)
  • 也许.我以为每个人都知道哈希表和树木解决了不同的问题. (2认同)

ldo*_*dog 6

考虑扫描线算法.这些算法将完全失败并使用哈希表,但与平衡树一起工作得非常漂亮.为了给你一个扫描线算法的具体例子,考虑一下fortune的算法.http://en.wikipedia.org/wiki/Fortune%27s_algorithm


小智 5

除了其他人已经提到的之外,还有一件事。虽然将元素插入到 unordered_set 的预期摊销复杂度是 O(1),但它时不时地需要 O(n),因为哈希表需要重组(桶的数量需要改变) - 即使有一个“好”的哈希函数。就像在向量中插入一个元素时不时需要 O(n) 一样,因为底层数组需要重新分配。

插入一个集合总是最多需要 O(log n)。这在某些应用中可能更可取。


mic*_*c_e 5

虽然这个答案可能晚了 10 年,但值得指出的是,它std::unordered_set也有安全隐患。

如果散列函数是可预测的(这通常是这种情况,除非它应用随机盐等对策),攻击者可以手工制作产生散列冲突的数据,并导致所有插入和查找花费 O(n) 时间.

这可用于非常有效和优雅的拒绝服务攻击。

许多(大多数?)内部使用哈希映射的语言实现都遇到了这种情况:


归档时间:

查看次数:

59905 次

最近记录:

6 年 前