【问题标题】:Why would anyone use set instead of unordered_set?为什么有人会使用 set 而不是 unordered_set?
【发布时间】:2010-11-23 21:34:11
【问题描述】:

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

【问题讨论】:

  • 你的问题基本上是在问是否还需要一棵树。
  • 我想我在第一行已经清楚地说明了,这是一个愚蠢的问题。我错过了一些东西,现在我得到了答案:)
  • 真正的原因是事情并不像看起来那样黑白。中间有很多灰色和其他颜色。您需要记住这些容器是工具。有时性能并不重要,便利性更有意义。如果人们都在寻找最有效的解决方案,我们一开始就不会使用 C++(更不用说 Python),而是不断地用机器语言编写和优化代码。
  • (到底为什么会有人使用通用名称来表示实现/接口的承诺超出了该名称所暗示的承诺,从而为没有承诺的人创造了尴尬的局面?)

标签: c++ algorithm data-structures c++11


【解决方案1】:

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

  • set 使用unordered_set 更少的内存来存储相同数量的元素。
  • 对于少量元素set 中的查找可能比unordered_set 中的查找更快
  • 尽管对于unordered_set,许多操作在平均情况中更快,但对于set(例如@ 987654328@)。
  • 如果您想按顺序访问元素,set 对元素进行排序非常有用。
  • 您可以按字典顺序比较不同的sets 与<<=>>=unordered_sets 不需要支持这些操作。

【讨论】:

  • +1,所有优点。人们往往会忽略哈希表有 O(1) average-case 访问时间这一事实,这意味着它们偶尔会出现很大的延迟。这种区别对于实时系统可能很重要。
  • 好点,但是这里 (en.cppreference.com/w/cpp/container/unordered_set/operator_cmp) 声明我们可以比较 unordered_sets。
  • 定义“少量元素”
  • @SunjayVarma 通常 100 个元素是两者之间的一个很好的分界线。如果有疑问,在您的特定用例中,没有什么可以替代两者的测试性能。
  • @MichieluithetBroek 仅说明相等比较,而不是排序 (<)。
【解决方案2】:

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

【讨论】:

  • 是按照插入顺序排序,还是按照实际使用运算符< >比较排序?
  • 默认使用std::less排序;您可以覆盖它并提供您自己的比较运算符。 cplusplus.com/reference/set/set
  • 或者有时你只想迭代,即使顺序无关紧要。
【解决方案3】:

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

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

【讨论】:

  • /Balanced/ 树在最坏的情况下是 O(ln n)。您最终可以得到 O(n) 棵树(本质上是链表)。
  • 如果你能写出一个相当智能的散列函数,你几乎总能从散列表中得到 O(1) 的性能。如果您不能编写这样的哈希函数,如果您需要“按顺序”迭代您的集合,那么您应该使用树。但是您不应该使用树,因为您害怕“O(n) 最坏情况下的性能”。
  • stager: 学究气,是的。但是,我们讨论的是 C++ 中的集合,它通常实现为平衡二叉搜索树。我们应该指定实际操作来谈论复杂性。在这种情况下,很明显我们在谈论查找。
  • 贾斯汀 L:这只是您可能喜欢一棵树的一个原因。我的答案的核心是第一行。 每当您更喜欢树数据结构而不是哈希表。在很多情况下,树比哈希表更受欢迎。哈希表在“范围交叉点”之类的东西上尤其糟糕。
  • stl 树几乎是普遍实现的红黑树,一种高级自平衡树。在某些情况下,在更坏的情况下查找 O(n) 是不可接受的。提供和接口来存储用户值的 Web 服务不应使用哈希映射,因为恶意用户可以通过存储特制值来有效地创建 DoS。关键的、时间敏感的系统也可能不允许 O(n) 查找、空中交通管制等。虽然总的来说你是对的,但默认情况下使用哈希映射,并且只有在你真正需要时才切换树版本。
【解决方案4】:

在以下情况下使用集合:

  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 作为键

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&lt;int&gt; 之所以可以作为set 中的键,是因为vector 覆盖operator&lt;

但是如果你使用unordered_set&lt;vector&lt;int&gt;&gt;你必须为vector&lt;int&gt;创建一个哈希函数,因为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

【讨论】:

    【解决方案5】:

    g++ 6.4 stdlibc++ 有序与无序集基准测试

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

    完整的基准测试详细信息和分析已在:What is the underlying data structure of a STL set in C++? 上提供,这里不再赘述。

    “BST”的意思是“用std::set 测试过,而“hash map”的意思是“用std::unordered_set 测试过。 “堆”用于std::priority_queue,我在Heap vs Binary Search Tree (BST)进行了分析

    作为一个简短的总结:

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

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

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

    mapunordered_map 的类似问题:Is there any advantage of using map over unordered_map in case of trivial keys?

    【讨论】:

      【解决方案6】:

      因为 std::set 是标准 C++ 的一部分,而 unordered_set 不是。 C++0x 不是标准,Boost 也不是。对于我们中的许多人来说,可移植性至关重要,这意味着要坚持标准。

      【讨论】:

      • 如果我理解正确的话,他并不是在问为什么人们目前仍在使用 set。他正在告知自己有关 C++0x 的信息。
      • 也许吧。我以为每个人都知道哈希表和树解决了不同的问题。
      • 嗯,这是标准的现在(只花了几年时间)
      【解决方案7】:

      考虑扫描线算法。这些算法在哈希表上会完全失败,但在平衡树上工作得很好。为了给你一个扫描线算法的具体例子,请考虑财富算法。 http://en.wikipedia.org/wiki/Fortune%27s_algorithm

      【讨论】:

      • 考虑到问题,我认为这样的参考太复杂了。 (我不得不查一下)
      【解决方案8】:

      虽然这个答案可能晚了 10 年,但值得指出的是,std::unordered_set 也存在安全问题。

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

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

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

      【讨论】:

        【解决方案9】:

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

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

        【讨论】:

          【解决方案10】:

          对不起,关于 sorted 属性还有一点值得注意:

          如果你想要一个范围的数据在容器中,例如:你把时间存储在set中,你想要从2013-01-01到2014-01的时间-01.

          对于 unordered_set 这是不可能的。

          当然,对于 ma​​punordered_map 之间的用例,这个例子会更有说服力。

          【讨论】:

            【解决方案11】:

            顺便说一句,如果您想将关系转换为不同的格式,我会说将事物置于关系中会很方便。

            也有可能,虽然访问速度更快,但构建索引的时间或创建和/或访问它时使用的内存更长。

            【讨论】:

            • +1,Big Oh 表示法隐藏了常数因子,对于典型的问题规模,它通常是最重要的常数因子。
            【解决方案12】:

            如果你想对事物进行排序,那么你可以使用 set 而不是 unordered_set。 unordered_set 在排序存储无关紧要时使用 over set。

            【讨论】:

              【解决方案13】:

              这是一个我没有看到的实际原因...如果在错误代码中使用不正确,无序集可能会导致代码在不同机器上表现不同。这是因为值的存储顺序在机器之间不一致。

              如果(错误地)编写依赖于存储顺序的代码,结果将是程序在不同机器之间的行为不一致。实际上,如果无序集是返回值列表的函数/方法的实现的一部分,则可能会发生这种情况。该函数的客户端可能没有意识到正在使用无序集,并且可能没有意识到返回列表的顺序不能保证是一致的/可移植的。

              因此,对于程序员来说,无序集比有序集更难理解。他们引入了这种额外的机制来混淆代码行为,这可能会导致耗时/令人困惑的错误,因为它们可能无法在机器之间重现。

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2018-10-31
                • 1970-01-01
                • 1970-01-01
                • 2017-05-04
                • 2020-08-12
                • 1970-01-01
                相关资源
                最近更新 更多