【问题标题】:STL::Map - Walk through list or use find?STL::Map - 浏览列表还是使用查找?
【发布时间】:2010-09-22 06:32:40
【问题描述】:

假设我有一个方法需要从包含 100 个元素的地图中提取 8 个值。你认为哪个更可取:

在for循环中从头到尾走一遍,通过按键拉出元素?

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

【问题讨论】:

    标签: c++ performance stl iterator find


    【解决方案1】:

    遍历列表将花费你 O(n) 时间来找到一个随机元素。

    Map 是一个平衡二叉树,所以查找是 O(log n)。

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

    在非随机情况下,如果有理由让您想要的项目在树中彼此靠近,或者靠近“开始”(左侧),那么步行/迭代会更快。但这似乎不太可能。

    【讨论】:

    • 请注意,STL 映射通常不是高度平衡的树。它通常实现为红黑,IIRC 意味着在最坏情况下节点的深度是 2*log2(n),尽管典型情况更好。并不是说这真的会影响这种情况下的分析。
    • 红黑树是“自平衡”树。
    • 正确。这意味着它们的不平衡程度是有限度的,但这并不意味着它们的高度永远不会超过 log(n)。
    【解决方案2】:

    虽然我会选择 find 选项,但人们对渐近性能过于强调。

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

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

    【讨论】:

      【解决方案3】:

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

      您应该尽量不要根据小数字做出性能判断,因为 (a) 在这种大小下,无论哪种方式都不太可能成为性能瓶颈,并且 (b) 数字将来可能会发生变化 - 选择算法最好的渐近性能。

      注意:您提到键上的“切换”......这可能适用于您的情况,但通常您无法在地图中打开键值。允许这样做会使通过迭代在地图中搜索 M 项的代码更加复杂。

      【讨论】:

        【解决方案4】:

        8 次查找最好,因为代码更简单清晰。

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

        正如我在写这个答案时 Artelius 所说,忽略复杂性。这无关紧要,因为您知道 n=100。例如,插入排序的算法复杂度比快速排序更差(至少在平均情况下),但在几乎所有实现中,对于小的 n,插入排序都比快速排序快,因此快速排序在接近尾声时突破为插入排序。您的 n 也很小,因此 n -> 无穷大的限制并不重要。

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

        但是如果做不到这一点,通常比较搜索算法的方法是计算比较,假设它们比遍历结构要慢得多。如果不出意外,每次比较都会访问内存并且是一个不可预测的分支,这是 CPU 通常最不擅长的两件事。

        如果你在开始之前对你的 8 个元素进行排序(大约需要 24 次比较)而不是你建议的 switch,那么因为地图也是排序的,你只需要在你遍历的每个节点上做一次比较,加上一个比较您正在搜索的每个项目(比较每个“边”中的一个节点。如果它们匹配,则增加两边。如果它们不匹配,则增加具有较小元素的边)。所以在最坏的情况下是 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 个发现)。我有没有提到,如果你真的想知道你无法预测的速度,你只需要分析它吗?

        【讨论】:

          【解决方案5】:

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

          矢量::迭代器 a = my_map.lower_bound(my_map.begin(), my_map.end(), "a"); 矢量::迭代器 b = my_map.lower_bound(a + 1, my_map.end(), "b"); 向量::迭代器 c = my_map.lower_bound( b + 1, my_map.end(), "c" ); // ...

          这两种方法都有对数查找,但这有助于在一定程度上减少常数。

          【讨论】:

            【解决方案6】:

            你应该使用 find 8 次。

            将切换方法想象为对每个节点进行 8 次比较。那是 800 次比较,而您完全失去了键控地图的所有好处,它还可能是一个列表。

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

            【讨论】:

            • std::map 是二叉树,但不一定是平衡的。最坏情况深度与 log(n) 成正比,但不一定等于 log(n)。
            【解决方案7】:

            这里是对它们的时间复杂度的分析(n是地图中的项目数),保证查找find具有对数或更好的时间复杂度:

            8 * log2 n 8 次查找
            n 表示遍历所有

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

            【讨论】:

            • std::map 不一定是平衡的。最坏情况深度与 log(n) 成正比,但不一定等于 log(n)。而且 log(0) 不是 1,它是未定义的 ;-)
            • 确实如此。对不起,我想写“对数”。哑巴。晚上太晚了
            • 我希望你不要认为我一直在写废话。就在我累的时候。大声笑
            【解决方案8】:

            如果这很关键,我会同时实施这两种方法并对性能进行基准测试。

            理论上是这样

            8 * lg(100) >?

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

            【讨论】:

              【解决方案9】:

              让我们假设“find”在找到密钥时退出。

              让我们进一步假设您对“开关”进行了明智的编码,并在找到匹配项后退出检查。我们还将假设您在找到所有 8 个代码后,无需费心编写代码以在整个过程中保释(编写代码可能会很痛苦)。

              “8 查找”方法可以预期(iow:平均)执行 50 * 8 = 400 次比较。

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

              因此,在比较方面,8 次查找方法更好。但是,比较的数量足够少,您最好选择更容易理解的选项。不过,这也可能是 8 find 方法。

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 2016-12-08
                • 2015-10-30
                • 1970-01-01
                • 1970-01-01
                • 2016-08-06
                • 2021-10-06
                • 2021-11-23
                • 2012-12-02
                相关资源
                最近更新 更多