【问题标题】:Why/Are unordered_map and unordered_set slower?为什么/unordered_map 和 unordered_set 更慢?
【发布时间】:2020-05-23 12:46:26
【问题描述】:

我正在解决一个在数组中查找唯一元素的简单问题。我使用std::unordered_map 来计算唯一元素,但它在一个测试用例中给出了 Time Limit Exceeded。然后我使用了std::unordered_set,结果相同。

PS:我使用了std::unordered_mapstd::unordered_set,因为我读到这两个分别比std::mapstd::set 快得多。

#include<bits/stdc++.h>
using namespace std;

int main() {
    int n, a;
    cin >> n;
    unordered_set<int> s;

    for(int i = 0; i < n; i++) {
        cin >> a;
        s.insert(a);
    }
    cout << s.size();
}

测试 7 超过了时间限制。

我的问题是:

如果 std::unordered_mapstd::unordered_set 更快,为什么他们要提供 TLE?

【问题讨论】:

  • n 和 a 的界限是什么?
  • std::unordered_maputd::unordered_set 并不是天生就更快。编程中没有什么只是“神奇的速度”。您必须知道如何以及何时使用什么。
  • 您可以先尝试分配内存,这可能会提高您的代码速度
  • 在您的示例中,任何容器都将比您等待的用户输入快得多。

标签: c++ unordered-map stdmap unordered-set stdset


【解决方案1】:

std::unordered_set&lt;int&gt; 是一个基于节点的容器,其中每个元素存储在单独分配的列表节点中。列表节点包含一个int元素和两个列表节点指针,在64位平台上,由于对齐,每个列表节点占用24字节。每个分配的内存块也有分配器开销(GNU libc 为 8 个字节),因此每个 4 字节 int 元素至少有 28 个字节的开销。

s.insert(a); 为每个新元素分配内存,这就是代码变慢的原因。


为了有效地解决这个问题,您可以使用由整数索引的位集,例如std::vector&lt;bool&gt;。对于每个读取的整数,将该位设置为 1,然后计算设置位的数量。如果元素是有符号的,则将它们转换为无符号以使位索引为非负数。

一个工作示例:

#include <vector>
#include <iostream>
#include <numeric>
#include <limits>

int main() {
    int n;
    std::cin >> n;
    std::vector<bool> bitset(1000000001); // a range is 1≤a≤10^9.

    for(int i = 0, a; i < n; ++i) {
        std::cin >> a;
        bitset[static_cast<unsigned>(a)] = true;
    }
    std::cout << std::accumulate(bitset.begin(), bitset.end(), 0u) << '\n';
}

通过该评分者的版本:

#include <bitset>
#include <iostream>
#include <numeric>
#include <limits>

int main() {
    int n;
    std::cin >> n;
    std::bitset<1000000001> bitset; // a range is 1≤a≤10^9.
    unsigned unique = 0;
    for(int i = 0, a; i < n; ++i) {
        std::cin >> a;
        if(!bitset.test(a)) {
            ++unique;
            bitset.set(a);
        }
    }
    std::cout << unique << '\n';
}

【讨论】:

  • 如果aINT_MAX 怎么办?这仅适用于我们知道a 的界限。
  • 您可以创建一个包含 2^32 个元素的位集,它占用 2^29 个字节,即 512MB。
  • 如果a 是-1 怎么办? :D
  • @Ayxan std::bitset 内联存储元素(不在动态分配的内存中)。 std::bitset 具有 2^32 位将占用堆栈上的 512MB,您的平台可能不支持。例如,在 Linux 上,默认的最大堆栈大小约为 8MB。
  • 非常感谢@MaximEgorushkin。所有测试用例都通过了(AC)。你真是个天才。我学到了一项新技术!
【解决方案2】:

无序容器针对检索值进行了优化,而不是针对插入它们。

你可以看看这个比较https://medium.com/@nonuruzun/stl-container-performance-3ec5a8fbc3be。在那里,您可以看到无序容器的插入最坏情况为 O(N),而有序容器的最坏情况为 O(log(N))

但是你可以通过先分配内存来解决这个问题,这样你就会有更少的冲突。

【讨论】:

  • 如何先分配内存?
【解决方案3】:

std::unordered_map 大多数时候会给出 o(1) 的结果,但并非总是如此。 谈到 TLE 问题,约束可能超过 10^18,在这种情况下,o(n) 复杂性将不起作用。尝试 o(log(n)) 方法。

【讨论】:

  • 你的意思是 O(1) 而不是 O(n)? O(n) 插入是最坏的情况。 cppreference
猜你喜欢
  • 2020-08-12
  • 2014-08-04
  • 2015-08-05
  • 1970-01-01
  • 2017-03-12
  • 2012-08-25
  • 2015-09-10
  • 2011-08-06
  • 2016-03-14
相关资源
最近更新 更多