【问题标题】:C++ std::unordered_map complexityC++ std::unordered_map 复杂度
【发布时间】:2013-11-05 18:55:39
【问题描述】:

我在 stackoverflow 上阅读了很多关于 unordered_map(c++11) 时间复杂性的内容,但我还没有找到我的答案问题。

假设按整数索引(仅举例):

Insert/at 函数持续工作(平均时间),所以这个例子需要 O(1)

std::unordered_map<int, int> mymap = {
            { 1, 1},
            { 100, 2},
            { 100000, 3 }
};

我很好奇的是遍历存储在 map 中的所有(未排序的)值需要多长时间 - 例如

for ( auto it = mymap.begin(); it != mymap.end(); ++it ) { ... }

我可以假设每个存储的值只被访问一次(或两次或恒定时间)吗?这意味着遍历所有值都在 N 值映射 O(N) 中。另一种可能性是我的带有键 {1,10,100000} 的示例可能需要多达 1000000 次迭代(如果由数组表示)

是否还有其他容器可以线性迭代并通过给定键不断访问值?

我真正需要的是(伪代码)

myStructure.add(key, value) // O(1)
value = myStructure.at(key) // O(1)
for (auto key : mySructure) {...} // O(1) for each key/value pair = O(N) for N values

std::unordered_map 是我需要的结构吗?

整数索引就足够了,平均复杂度也是如此。

【问题讨论】:

  • 如果您担心枚举需要遍历您插入容器的配对,请放心,它不会。使用常规mapunordered_map 的决定应基于您是否需要对密钥保留 进行相对严格的弱排序。如果你这样做,你需要一个普通的map。如果您不这样做,unordered_map 是最合乎逻辑的选择(前提是可以将密钥散列到合理的分布)。
  • @WhozCraig:在选择mapunordered_map 时要考虑的另一个功能因素是后者在insert/emplace/[] 触发期间是否使现有迭代器/引用/指针失效重新散列是可以接受的,然后会有性能差异倾向于有利于unordered_map,但应该由那些分析器/仪器说必须真正关心的人来衡量......

标签: c++ stl iteration time-complexity unordered-map


【解决方案1】:

哈希表有几种不同的实现方式,如果您有兴趣,我建议您阅读更多内容,但主要的两种方式是通过链接和开放寻址。

在第一种情况下,您有一个链表数组。数组中的每个条目都可以是空的,哈希表中的每个项目都将位于某个桶中。所以迭代是遍历数组,遍历其中的每个非空列表。显然是 O(N),但根据链表本身的分配方式,可能内存效率非常低。

在第二种情况下,您只有一个非常大的数组,其中包含许多空槽。在这里,迭代再次明显是线性的,但如果表大部分为空(这应该用于查找目的),则可能效率低下,因为实际存在的元素将位于不同的缓存行中。

无论哪种方式,您都将进行线性迭代,并且您将只触及每个元素一次。请注意,std::map 也是如此,那里的迭代也将是线性的。但是在地图的情况下,迭代肯定会比迭代向量的效率低得多,所以请记住这一点。如果您的用例涉及需要快速查找和快速迭代,如果您预先插入所有元素并且从不擦除,那么实际上同时拥有地图和矢量可能会好得多。为额外的性能腾出额外的空间。

【讨论】:

    【解决方案2】:

    所有标准容器的复杂性保证在C++ Standard中指定。

    std::unordered_map 元素访问和元素插入要求平均复杂度 O(1)O(N) 最坏情况(参见第 23.5.4.3 和 23.5.4.4 节;第 797-798 页)。

    特定的实现(即标准库的特定供应商的实现)可以选择他们想要的任何数据结构。但是,要符合标准,它们的复杂性必须至少符合规定。

    【讨论】:

    • 在使用迭代器的最坏情况下,这种复杂性是否也是O(N)? (更具体地说,将迭代器增加 1?)
    • 正如上面已经回答的那样,遍历 all 容器必须是O(N)std::unordered_map 迭代器满足forward iterator 概念)。访问特定元素或插入元素平均是O(1),最坏情况是O(N)
    【解决方案3】:

    无论它们是如何实现的,标准容器都提供满足迭代器要求的迭代器。增加一个迭代器需要是常数时间,所以遍历 any 标准容器的所有元素是 O(N)。

    【讨论】:

    • 我似乎找不到增量的复杂性要求(也没有取消引用)。你有引用吗?
    • 根据 SGI 的forward iterator 概念前向迭代器上的操作的复杂性保证是摊销常数时间。 C++ 标准的第 24.4.4 节仅声明 对于输入、前向和双向迭代器 [...] 他们使用 ++ 来提供线性时间实现
    • @Arrieta:对于unordered_map 的情况,“线性”的含义并不明确。它可以表示“桶数”或“容器中的元素”。有没有更具体的东西表明后者?
    • @jxh:我发现了以下内容:迭代器的所有类别只需要在恒定时间内(摊销)对于给定类别可实现的那些函数。因此,迭代器的需求表没有复杂度列。(参见 24.2.1.8)。所以增量只需要为容器中的元素摊销恒定时间(据我所知)。
    • @TonyD:我曾经不知道load_factor 的含义,但后来我明白了。不过感谢您的信息!
    猜你喜欢
    • 2013-03-06
    • 1970-01-01
    • 2014-10-01
    • 2013-05-07
    • 1970-01-01
    • 2022-01-13
    • 2019-02-27
    • 2016-04-06
    • 1970-01-01
    相关资源
    最近更新 更多