成对数组而不是 STL 映射 - 可能吗?优缺点都有什么?
An array of pairs instead of an STL map - possible? What are the pros and cons?
为什么 c++ 标准映射非常有名,而 数组对 也很相似?
什么时候使用 C++ 标准映射更好,什么时候使用对数组?
或者两者的应用程序是否相似?
Why are c++ standard maps very famous for, whereas an array of pairs
is also similar to those?
下面的table应该给你一个小想法,在哪里使用c++标准映射(只选择一些)而不是对数组.
When would it be better to use c++ standard maps, and when to use an
array of pairs?
您始终可以对数据结构的效率进行基准测试,以了解哪种适合哪种情况。
让我们来看一个例子。
例如下面是在https://quick-bench.com/中做的benchmark,插入10000个元素每个
的开头
- 对数组(即
std::vector<std::pair<int, std::string>>
)与
- 标准地图(即
std::map<int, std::string>
)vs
- 标准哈希映射(即
std::unordered_map<int, std::string>
)。
事实证明,对于这种操作,使用 C++ 标准映射 faster/efficient 比 对数组 。
然而,对于条目较少的情况(让我们考虑10个元素),同样的测试表明array of pairs(即std::vector<std::pair<int, std::string>>
)将比标准散列映射(即std::unordered_map<int, std::string>
)faster/efficient,并且几乎等于标准地图(即std::map<int, std::string>
)。
简而言之,经验法则是,在选择正确的数据结构以获得最佳结果之前,始终对您将要进行的操作进行基准测试和测试。如需进一步参考,请查看 std::vector
, std::map
, std::unordered_map
等及其各自的操作。
当您尝试这样做时,您会使用地图,将一个项目映射到另一个项目。对数组只是存储一堆对,它不提供映射功能,因为您仍然受到数组索引的约束。
地图对于计算频率也很方便。由于地图(至少是标准地图,有允许重复的地图变体)不会复制键值。您可以使用它来查找元素的频率等。
使用地图的主要原因是当您想要根据您定义的键查找值时。与其说只是将物品存放在一起,不如说是成对存放物品。
如果您遇到适合关联映射的问题,您可以使用 std::unordered_map
或 std::map
(或 Abseil's Swiss Tables or Boost's flat_map 或类似工具)等工具来简化您的工作.是的,您始终可以自己管理数组或 std::vector
中的对,有时这是正确的做法。
使用标准容器的原因包括代码的可读性和可维护性(大多数 C++ 程序员在某种程度上熟悉标准容器和算法)、正确性(标准容器历史悠久,不太可能比家庭编写的数据结构代码更容易出错),开发速度(重用可靠的设计并专注于使用可用的工具而不是为了边际收益而重新实现该工具),以及可能的速度(更多内容见下文)。
您可能想要推出自己的容器或选择非标准容器的原因包括特定容器上的特殊 API,使其更适合您的用例,与您无法控制的代码交互(例如,第三方库期望在连续内存中成对)和性能要求。
即:映射或数组的不同变体将具有不同的性能特征。每个操作(查找、插入、删除等)都有 Big-O 评级,这是一个很好的起点。但这并不能说明全部。一方面,您可能不会以相同的频率或紧迫性使用所有这些操作(例如,您需要快速查找并且不太关心删除)。另一方面,缓存局部性最终会使操作的理论 Big-O 值相形见绌,但这完全取决于您的数据特征、目标机器的缓存大小等。
数组将位于连续的内存中,并且倾向于(取决于大小和使用模式)比 std::map
具有更好的缓存位置和整体性能,后者倾向于通过内存传播。这和平面地图的思路是一样的,它有一个地图的界面,但是下面是一个数组的存储。
基于散列的映射将具有常量 (O(1)
) 查找,但它也可能会因计算散列而受到惩罚(取决于键类型,甚至可能无法散列;或散列对于某些键类型可能实际上是免费的),不会以严格有序的方式存储项目以进行迭代(在许多用例中不是问题),如果哈希器有很多冲突,可能会转移到 O(n)
查找时间,并且取决于实现,可能在也可能不在提供缓存局部性的连续内存中。
当然,您应该以实际绩效衡量而不是直觉或猜测来指导您的决策,直觉或猜测是出了名的不准确。
如果您需要一个合理的默认值,我将从 std::unordered_map
开始,假设您的密钥便宜且易于散列,并且您不关心迭代顺序。当该地图未提供您需要的所有功能或当您的性能分析表明您需要这样做时,请选择不同的数据结构。
几点到note/consider:
虽然 Jejo 的答案在 渐近 复杂性方面是正确的,但在许多实际场景中 - 一对向量(或一对向量)实际上可能是比使用地图更快 - 即使是插入或擦除。请参阅 Herb Sutter 的 this presentation-fraction,在实践中赞扬了在各种场景中使用数组(std::vector
's)优于其他结构的优点。如果您之前没有深入了解过这些内容,那将是一次有趣的聆听...
有两种以上供您选择-根据您的实际需要:
- 您是否需要地图元素可以按键的递增顺序进行迭代? IE。
您需要对数据进行排序吗?如果不是,那么即使在地图方面,您也需要
std::unordered_map
,而不是 std::map
。
- 如果您使用数组,您可以使用一个键值对数组,也可以像您指定的那样使用两个数组。这也转化为实践中的不同表现(即使渐近复杂性相同)。
- 您可以使用带有“tombstones”的数组 - 标记“空”或“缺失”值 - 指示已删除的元素,或执行更巧妙的插入等。
看看这个问题:
vector or map, which one to use?
使用地图与成对向量进行比较。
标准库映射作为实现非常慢!看到这个问题:
Is gcc std::unordered_map implementation slow? If so - why?
为什么 c++ 标准映射非常有名,而 数组对 也很相似?
什么时候使用 C++ 标准映射更好,什么时候使用对数组? 或者两者的应用程序是否相似?
Why are c++ standard maps very famous for, whereas an array of pairs is also similar to those?
下面的table应该给你一个小想法,在哪里使用c++标准映射(只选择一些)而不是对数组.
When would it be better to use c++ standard maps, and when to use an array of pairs?
您始终可以对数据结构的效率进行基准测试,以了解哪种适合哪种情况。
让我们来看一个例子。
例如下面是在https://quick-bench.com/中做的benchmark,插入10000个元素每个
的开头- 对数组(即
std::vector<std::pair<int, std::string>>
)与 - 标准地图(即
std::map<int, std::string>
)vs - 标准哈希映射(即
std::unordered_map<int, std::string>
)。
事实证明,对于这种操作,使用 C++ 标准映射 faster/efficient 比 对数组 。
然而,对于条目较少的情况(让我们考虑10个元素),同样的测试表明array of pairs(即std::vector<std::pair<int, std::string>>
)将比标准散列映射(即std::unordered_map<int, std::string>
)faster/efficient,并且几乎等于标准地图(即std::map<int, std::string>
)。
简而言之,经验法则是,在选择正确的数据结构以获得最佳结果之前,始终对您将要进行的操作进行基准测试和测试。如需进一步参考,请查看 std::vector
, std::map
, std::unordered_map
等及其各自的操作。
当您尝试这样做时,您会使用地图,将一个项目映射到另一个项目。对数组只是存储一堆对,它不提供映射功能,因为您仍然受到数组索引的约束。
地图对于计算频率也很方便。由于地图(至少是标准地图,有允许重复的地图变体)不会复制键值。您可以使用它来查找元素的频率等。
使用地图的主要原因是当您想要根据您定义的键查找值时。与其说只是将物品存放在一起,不如说是成对存放物品。
如果您遇到适合关联映射的问题,您可以使用 std::unordered_map
或 std::map
(或 Abseil's Swiss Tables or Boost's flat_map 或类似工具)等工具来简化您的工作.是的,您始终可以自己管理数组或 std::vector
中的对,有时这是正确的做法。
使用标准容器的原因包括代码的可读性和可维护性(大多数 C++ 程序员在某种程度上熟悉标准容器和算法)、正确性(标准容器历史悠久,不太可能比家庭编写的数据结构代码更容易出错),开发速度(重用可靠的设计并专注于使用可用的工具而不是为了边际收益而重新实现该工具),以及可能的速度(更多内容见下文)。
您可能想要推出自己的容器或选择非标准容器的原因包括特定容器上的特殊 API,使其更适合您的用例,与您无法控制的代码交互(例如,第三方库期望在连续内存中成对)和性能要求。
即:映射或数组的不同变体将具有不同的性能特征。每个操作(查找、插入、删除等)都有 Big-O 评级,这是一个很好的起点。但这并不能说明全部。一方面,您可能不会以相同的频率或紧迫性使用所有这些操作(例如,您需要快速查找并且不太关心删除)。另一方面,缓存局部性最终会使操作的理论 Big-O 值相形见绌,但这完全取决于您的数据特征、目标机器的缓存大小等。
数组将位于连续的内存中,并且倾向于(取决于大小和使用模式)比 std::map
具有更好的缓存位置和整体性能,后者倾向于通过内存传播。这和平面地图的思路是一样的,它有一个地图的界面,但是下面是一个数组的存储。
基于散列的映射将具有常量 (O(1)
) 查找,但它也可能会因计算散列而受到惩罚(取决于键类型,甚至可能无法散列;或散列对于某些键类型可能实际上是免费的),不会以严格有序的方式存储项目以进行迭代(在许多用例中不是问题),如果哈希器有很多冲突,可能会转移到 O(n)
查找时间,并且取决于实现,可能在也可能不在提供缓存局部性的连续内存中。
当然,您应该以实际绩效衡量而不是直觉或猜测来指导您的决策,直觉或猜测是出了名的不准确。
如果您需要一个合理的默认值,我将从 std::unordered_map
开始,假设您的密钥便宜且易于散列,并且您不关心迭代顺序。当该地图未提供您需要的所有功能或当您的性能分析表明您需要这样做时,请选择不同的数据结构。
几点到note/consider:
虽然 Jejo 的答案在 渐近 复杂性方面是正确的,但在许多实际场景中 - 一对向量(或一对向量)实际上可能是比使用地图更快 - 即使是插入或擦除。请参阅 Herb Sutter 的 this presentation-fraction,在实践中赞扬了在各种场景中使用数组(
std::vector
's)优于其他结构的优点。如果您之前没有深入了解过这些内容,那将是一次有趣的聆听...有两种以上供您选择-根据您的实际需要:
- 您是否需要地图元素可以按键的递增顺序进行迭代? IE。
您需要对数据进行排序吗?如果不是,那么即使在地图方面,您也需要
std::unordered_map
,而不是std::map
。 - 如果您使用数组,您可以使用一个键值对数组,也可以像您指定的那样使用两个数组。这也转化为实践中的不同表现(即使渐近复杂性相同)。
- 您可以使用带有“tombstones”的数组 - 标记“空”或“缺失”值 - 指示已删除的元素,或执行更巧妙的插入等。
- 您是否需要地图元素可以按键的递增顺序进行迭代? IE。
您需要对数据进行排序吗?如果不是,那么即使在地图方面,您也需要
看看这个问题:
vector or map, which one to use?
使用地图与成对向量进行比较。
标准库映射作为实现非常慢!看到这个问题:
Is gcc std::unordered_map implementation slow? If so - why?