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


145

C ++ 0x即将推出unordered_set,该版本可在boost其他许多地方使用。我了解的是unordered_set具有O(1)查找复杂性的哈希表。另一方面,set只有一棵具有log(n)查询复杂性的树。为什么在地球上有人会set代替使用unordered_set?即是否有需要set


22
从根本上说,您的问题是询问是否还需要一棵树。
Vinko Vrsalovic

2
我想我在第一行中清楚地指出了这是一个愚蠢的问题。我错过了一些东西,现在我得到了答案:)
AraK

2
真正的原因是事情并没有像看起来的那样黑白。两者之间有很多灰色和其他颜色。您需要记住这些容器是工具。有时性能并不是很关键,而便利性就更有意义了。如果人们都看了,最有效的解决方案,我们“d从来没有使用C ++(更不用说Python的)摆在首位,不断用机器语言编写和优化代码。
AturSams

(为什么有人在实现/接口上使用通用名称,而其承诺却超出了该名称所隐含的承诺,而对于没有该名称的承诺却造成了尴尬的局面?)
greybeard

Answers:



319

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

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


9
+1,所有优点。人们倾向于忽略哈希表具有O(1)平均情况访问时间这一事实,这意味着它们有时可能会有很大的延迟。区别对于实时系统可能很重要。
j_random_hacker

不错,但是这里(en.cppreference.com/w/cpp/container/unordered_set/operator_cmp)指出我们可以比较unordered_sets。
Michiel uit het Broek

5
定义“少量元素”
Sunjay Varma 2015年

4
@SunjayVarma通常在两个元素之间是一个很好的界限。不确定时,没有什么可以取代您特定用例中两者的测试性能。
Nate

3
@MichieluithetBroek仅声明相等比较,而不是排序(<)。
lisyarus

26

每当您喜欢树而不是哈希表时。

例如,在最坏的情况下,哈希表为“ O(n)”。O(1)是平均情况。树木在最坏的情况下是“ O(log n)”。


18
/平衡/在最坏的情况下树是O(ln n)。您最终可以得到O(n)个树(基本上是链表)。
斯特拉格

5
如果您可以编写一个合理的智能哈希函数,则几乎总是可以从哈希表中获得O(1)perf。如果您无法编写这样的哈希函数,即您需要“按顺序”遍历集合,则应使用树。但是您不应该使用树,因为您担心“ O(n)最坏情况的性能”。
贾斯汀·

6
演出者:是的,要学究。但是,我们谈论的是C ++中的set,通常以平衡的二进制搜索树的形式实现。我们应该指定实际操作来谈论复杂性。在这种情况下,很明显,我们正在谈论查找。
Mehrdad Afshari

1
贾斯汀·L:这只是您可能喜欢一棵树的原因之一。我的回答的核心是第一行。每当您希望树形数据结构胜于哈希表时。在很多情况下,树优先于哈希表。哈希表尤其会吸引“范围交叉点”之类的东西。
Mehrdad Afshari

2
stl树几乎是普遍实施的红黑树,一种高级的自平衡树。实际上,在某些情况下O(n)在更坏的情况下无法查找。提供并连接用户值的Web服务不应使用哈希图,因为恶意用户可以通过存储特制值来有效创建DoS。关键的,对时间敏感的系统可能也不允许进行O(n)查找,空中交通管制等。虽然通常来说您是对的,但默认情况下使用哈希映射,并且仅在有实际需要时才切换树版本。
deft_code

14

在以下情况下使用设置:

  1. 我们需要有序数据(不同元素)。
  2. 我们将不得不按排序顺序打印/访问数据。
  3. 我们需要元素的前任/后继。

在以下情况下使用unordered_set:

  1. 我们需要保留一组不同的元素,并且不需要排序。
  2. 我们需要单元素访问,即无遍历。

例子:

组:

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

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

无序设置:

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

输出:9 3 1 8 2 5(可能是此顺序,受哈希函数影响)

主要区别:

在此处输入图片说明

注意:(在某些情况下set更方便)例如vector用作key

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 

之所以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
}

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

主要引用自:https : //www.geeksforgeeks.org/set-vs-unordered_set-c-stl/ https://stackoverflow.com/a/29855973/6329006


6

因为std :: set是标准C ++的一部分,而unordered_set不是。C ++ 0x不是标准,Boost也不是。对于我们许多人来说,可移植性是必不可少的,这意味着必须遵守标准。


2
如果我正确理解他,他不是在问为什么人们当前仍然使用set。他正在向自己介绍C ++ 0x。
Johannes Schaub-litb

2
也许。我以为每个人都知道哈希表和树解决了不同的问题。

21
好吧,这是现在的标准(仅用了几年)
Clayton Hughes

6

考虑扫掠线算法。这些算法在散列表中将完全失败,但是在平衡树中可以很好地工作。为了给您一个扫掠线算法的具体示例,请考虑财富算法。http://en.wikipedia.org/wiki/Fortune%27s_algorithm


1
考虑到这个问题,我认为这种参考太复杂了。(我必须

3

除了其他人已经提到的内容外,还有一件事。而用于插入元件到unordered_set预期摊销复杂度为O(1),每一个现在,然后它花费O(N),因为要重组的散列表需要(水桶需要改变的数量) -即使一个“好的”哈希函数。就像在向量中插入元素一样,由于不时需要重新分配底层数组,因此不时需要O(n)。

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


3

请原谅我,还有一点需要注意的是排序属性:

例如,如果要在容器中存储一定范围的数据则将时间存储在set中,并且希望时间从2013年1月1日到2014年1月1日。

对于unordered_set,这是不可能的。

当然,对于在mapunordered_map之间的使用情况,此示例将更具说服力。


3

g++ 6.4 stdlibc ++有序与无序设置基准

我对这个主要的Linux C ++实现进行了基准测试,以了解两者之间的区别:

在此处输入图片说明

有关完整的基准测试细节和分析的信息,请参见:用C ++设置的STL的底层数据结构是什么?我在这里不再重复。

“ BST”表示“经过测试,std::set而散列图”表示“经过测试” std::unordered_setstd::priority_queue我在其中分析了“堆” :堆与二进制搜索树(BST)

快速总结:

  • 该图清楚地表明,在这种情况下,当项目超过100k时,散列图的插入总是快得多,并且随着项目数的增加,差异会增大

    这种速度提升的代价是您无法高效地顺序移动。

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

mapvs的类似问题unordered_map在使用琐碎的键的情况下,使用map而不是unordered_map有什么优势吗?


1

临时而言,如果您希望将其转换为其他格式,则可以方便地将它们联系起来。

还有可能的是,虽然访问速度更快,但是建立索引或在创建和/或访问索引时使用的内存的时间会更长。


+ 1,Big Oh符号隐藏了恒定因素,对于典型的问题大小,通常最重要的是恒定因素。
j_random_hacker

1

如果要排序,则可以使用set而不是unordered_set。当排序的存储无关紧要时,将unordered_set用作集合。


1

虽然这个答案可能要晚10年,但值得指出的是 std::unordered_set也存在安全方面的缺点。

如果哈希函数是可预测的(除非应用了随机盐等对策,否则通常就是这种情况),攻击者可以手工处理产生哈希冲突并导致所有插入和查找花费O(n)时间的数据。 。

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

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.