STL :: Map - 浏览列表或使用find?

时间:2008-11-13 23:38:02

标签: c++ performance stl iterator find

假设我有一个方法需要从地图中提取8个值,其中包含100个元素。您认为哪个更合适:

从头到尾走一圈for,通过开启钥匙拉出元素?

或者使用find 8次来获取这些值?

9 个答案:

答案 0 :(得分:9)

走在列表中需要花费O(n)时间来查找随机元素。

Map是一个平衡的二叉树,因此执行find是O(log n)。

因此,8在8 * log2(n)中找到结果,并且在列表中行走是(n)。列表越大,增益越大,但在所有随机情况下,执行查找将比执行迭代更快。

在非随机的情况下,如果有理由认为你想要的物品在树中彼此靠近,或者在“开始”(左侧)附近,那么步行/迭代会更快。但这似乎很不合适。

答案 1 :(得分:5)

虽然我选择find选项,但人们对渐近性能的压力过大。

事实上,渐近性能是可以接收相当大的输入的算法的便利指南,但即便如此,它也不是万无一失的。对于任何合理的输入,具有比另一个更差的渐近性能的算法很可能更快。

然后有时候你的输入大小相当小(甚至是固定的)。在这种情况下,渐近性能几乎毫无意义。

答案 2 :(得分:3)

我会使用find 8次。这将是更少(和更明显)的代码。

你应该尽量不做基于小数字的性能判断,因为(a)它不可能是这种尺寸的性能瓶颈和(b)数字可能在未来发生变化 - 选择算法最好的渐近性能。

注意:你提到'切换'键可能适用于你的情况,但一般情况下你不能在地图中打开键值。允许这样做会使代码通过迭代在地图中搜索M项更加复杂。

答案 3 :(得分:1)

8发现是最好的,因为代码更简单,更清晰。

考虑性能更有趣,所以我也会这样做。

正如Artelius在写这个答案时说的那样,忽略复杂性。这是不相关的,因为你知道n = 100。例如,插入排序比快速排序具有更差的算法复杂度(至少在平均情况下),但在几乎所有实现中,插入排序比小n的快速排序快,因此快速排序突破到插入排序到最后。你的n也很小,所以限制为n - >无限并非重要。

由于两个选项的代码都很容易编写,我建议对其进行分析。这将(a)告诉你哪个更快,并且(b)证明两者都是如此之快以至于你做了什么并不重要(除非它是你的程序唯一做的事情,它做了很多)。特别是因为你谈到了打开密钥 - 如果密钥是一个整数类型,那么限制因素更可能是内存缓存问题而不是任何实际处理。

然而,如果失败,通常比较搜索算法的方法是计算比较,假设它们比遍历结构慢得多。如果不出意外,每个比较都会访问内存并且是一个不可预测的分支,这是CPU经常最糟糕的两件事。

如果您在开始之前对8个元素进行排序(这需要进行24次比较),而不是您建议的开关,那么因为地图也是排序的,您只需要在每个遍历的节点进行一次比较,再加上一个你要搜索的每个项目的比较(比较每个“side”的一个节点。如果它们匹配增加两侧。如果它们不匹配,则用较小的元素增加一侧)。所以在最坏的情况下是8 + 100,或者如果你在结束之前找到所有8个,则更少。但是,如果他们随机地位于地图中,那么8的最后一个的平均位置仍然是8/9。所以称之为8 + 88 + 24 = 120比较,其中132为最差情况。最好的情况是8(如果您正在搜索的内容都在开始时)+24(初始排序)= 32次比较,或者如果您在排序方面也很幸运,或者您的8个搜索项目是由于某种原因准备好了。

红黑树(通常是地图)中节点的平均深度略高于log2(n)。在这种情况下将其称为7,因为2 ^ 7是128.所以找到8个元素应该采取类似于56的比较。 IIRC红黑树的平衡特征是最深节点不超过最浅节点深度的两倍。所以最坏的情况深度是floor(2 * log2(n)),称之为15,总共120,最好是ceil(1/2 log2(n)),这是4.这是32次比较。

因此,假设比较是影响速度的唯一因素,那么8次发现将比线性遍历快4倍,慢4倍,平均值提高2倍。

线性遍历可能会触及更多内存,因此该帐户可能会更慢。但最终对于n = 100,你说的是毫秒级,所以只做最简单的代码(可能是8个发现)。我是否提到如果你真的想知道你无法预测的速度,你只需要描述一下吗?

答案 4 :(得分:1)

正如其他人所说,我可能只是在地图上使用find()八次并完成它。但根据您的需求,可以考虑另一种选择。如果构建映射后映射中的项目不会发生太大变化,或者您不需要将插入与查找交错,则可以尝试将键/值对保持在已排序的向量中。如果执行此操作,则可以使用lower_bound()函数以对数时间执行二进制搜索。这样做的好处是,如果您要查找的键可以被排序(并且您知道它们将始终存在),那么您可以使用从先前查找返回的迭代器作为下一个的下限。如,

vector::iterator a = my_map.lower_bound( my_map.begin(), my_map.end(), "a" );
vector::iterator b = my_map.lower_bound( a + 1, my_map.end(), "b" );
vector::iterator c = my_map.lower_bound( b + 1, my_map.end(), "c" );
// ...

这两种方法都有对数查找,但这有助于减少常数。

答案 5 :(得分:0)

以下是对它们的时间复杂度的分析(n是映射中的项目计数),保证以对数或更好的时间复杂度查找查找:

8 * log2 n  for 8 times find
n for the iterate through all

对于较小的数字,第一个较大(例如,n = 2时为8),但是在43左右,第一个将比第二个好,并保持不变。因此,您将需要使用第一种方法,因为它也更方便编码。

答案 6 :(得分:0)

你应该使用find 8次。

将切换方法视为占用每个节点并将其比较8次。这是800次比较,你会失去所有关键地图的好处,它也可能是一个列表。

使用查找方法,您可以使用地图的优势遍历列表。我相信std :: maps是作为二叉树实现的,这意味着搜索一个键只需要将你的密钥与树的深度进行比较,对于一个100元素的二叉树,它将是8~。现在,您可以找到所有8个元素,只有8 * 8个比较,或64个比较。

答案 7 :(得分:0)

如果这很重要,我会实施两者并对性能进行基准测试。

理论上是否

8 * lg(100)>?< 100

其他考虑因素是,如果这些数字中的任何一个都会改变 - 它是否会超过100个元素;你会做超过8次搜索吗?

答案 8 :(得分:-1)

让我们假设找到钥匙时“找到”保释金。

让我们进一步假设您明智地编码“开关”,并在找到匹配后退出检查。一旦找到所有8个,我们也会假设你不要费心去编码整个过程(这可能会让代码变得很麻烦)。

“8 find”方法可以预期(低于平均值)进行50 * 8 = 400次比较。

“切换”方法可以预期(低于平均值)执行(8 * 100) - 28 = 772比较。

因此,在比较方面,8发现方法更好。但是,比较的数量足够小,以便您更好地选择更容易理解的选项。尽管如此,这可能也是8找到的方法。