【问题标题】:nth_element implementations complexitiesnth_element 实现复杂性
【发布时间】:2012-06-19 13:43:16
【问题描述】:

有人知道std::nth_element 的不同实现的预期运行时间和最坏情况下的运行时间吗?我几乎每天都使用这个算法。

我对最近的 Microsoft 编译器附带的 STL 版本特别感兴趣,但有关此主题的任何信息都会有所帮助。

Please note that this is not a duplicate of this question.我了解存在哪些算法,但我对哪些实现使用哪些算法感兴趣。

作为背景,有众所周知的算法可以做到这一点。一种是 O(n) 平均情况和 O(n log n) 最坏情况,一种是 O(n) 最坏情况但在实践中很慢(中位数的中位数)。 另请注意there is talk of interesting implementation strategies to get worst-case O(n) running times that are fast in practice。标准规定这必须是更糟糕的 O(n) 平均时间。

【问题讨论】:

  • 标准规定复杂性:平均线性。您是否查看了实现的标题?这可以是一个开始。
  • 好点,我在此基础上澄清问题。
  • 一个相关的bug,你可能会了解VS中的优化。
  • 哦,是的,我见过一些低于一定长度的实现,只需对数组进行排序,因为它足够快。在我看来,这是一个边界情况,并不违反 O(n) 平均情况,因为 O(n) 复杂度的定义必然只对大 n 有意义。
  • @dirkgently 换句话说,这根本不是错误。

标签: c++ algorithm stl nth-element


【解决方案1】:

预计运行时间为 O(N) 大多数实现的最坏情况运行时间是 O(N * N),因为大多数实现都使用 QuickSelect,并且可能是 QuickSelect 运行到错误的分区中。 Microsoft VS2008、VS2010 和 VS2012 也是如此。

现在有了新的 ISO C++ 2011 标准,std::sort 的复杂性得到了加强 - 它保证为 O(N * log N) 并且没有更糟糕的情况,因为使用了 David Musser 的 IntroSort: - 使用快速排序,如果部分数组遇到错误分区,则交换到堆排序。

理想情况下,std::nth_element 应该完全相同,但 ISO C++ 2011 标准并未收紧复杂性要求。所以 std::nth_element 在最坏的情况下可能是 O(N * N) 。这可能是因为在 David Musser 的原始论文(参见 here)中,他没有提到如果 QuickSelect 出现问题应该换成什么算法。

在最坏的情况下,可以使用使用 5 组的中位数中位数(我看过一篇论文推荐的 7 组但找不到)。因此,如果分区出错,std::nth_element 的高质量实现可以使用 QuickSelect 并交换到中位数。这将保证 O(N) 行为。 QuickSelect 可以通过使用抽样来改进,使最坏的情况不太可能发生但并非不可能。

【讨论】:

  • 很好的答案,我刚刚看到了。当您说“并且没有更糟糕的情况,因为使用了 David Musser 的 IntroSort:- 使用 QuickSort,如果部分数组遇到错误的分区,请交换到堆排序。”你的意思是最坏的情况是 O(N*log N) 对吧?还是我误会了?
  • IntraSelect :如果 QS 变坏,则使用 QuickSelect 并以 5 个元素为一组切换到 Median-of-Medians。平均和更坏的情况是 O(N)。 MIcrosoft 不检查错误并交换到 M-of-M,因此上次我查看 VS2012 时,在更糟糕的情况下,它们的 nth_element 可能是 O(N * N)。我还没有看到VS2013的代码。
  • IntraSort :如果 QS 变坏,使用 QuickSort 并切换到 HeapSort。平均和更坏的情况是 O(N * log N)。
  • "大多数实现都使用 QuickSelect" -- GCC has used introselect (quickselect+heap select) since 4.2。 MSVS 2015 和 2017 仍然使用纯快速选择(我知道 MS 是 OP 最感兴趣的)。我还没有完全理解LLVM's,但它看起来可能是一个纯粹的快速选择。 -- C++17 使用ExecutionPolicy 收紧了表单的时间复杂度。
  • ZachB:不幸的是,堆选择不是 O(N)。所以这个选择不是很好。我向 GCC 投诉并收到了一份 bugzilla 错误报告。 5 个组中的中位数为 O(N)。所以理想情况下,Intraselect 应该做 QuickSelect,如果它变坏,换成 M-of-M's。 ISO C++ 17 不要求它,因此没有编译器供应商提供的不仅仅是提供 QuickSelect(如果出现问题,没有其他算法)。
【解决方案2】:

GCC 4.7 中的实现使用 David Musser 的 introspective selection(这里有他的 paper 提供了有关 introsort 和 introselect 的详细信息)。根据那些文件,最坏情况下的执行时间是 O(n)。

【讨论】:

  • This gcc bugzilla 可能是相关的,因为它声称 libstdc++ 中的当前实现不符合标准的复杂性要求。
  • 这是完全错误的。最坏的情况是 O(n log n)。它写在您链接的同一个维基百科条目上。
  • GCC 使用 introselect,但它是 O(n log n) 的版本。某些版本的 introselect 有 O(n) 最坏情况。
  • David Musser 的原始论文从未 说过如果快速选择出错应该使用什么算法。但是如果在组中使用中位数算法 >= 5,那么您可以保证 Intraselect 不会比 O(n) 差。我抱怨过 gcc bugzilla,因为 heapselect not O(n)。因此,如果快速选择变坏,质量 nth_element 应该使用快速选择和中位数进行选择。这将保证 O(n)。
【解决方案3】:

cppreference 说,首先它排序然后找到第 n 个元素,但是通过这种方式平均值应该是 O(n log n)(通过基于比较的排序算法),但是他们写的平均值是 O(n),除了使用之外似乎不正确像基数排序一样排序,...但是因为它具有基于通用比较的输入,似乎不可能使用基数排序或任何其他不基于比较的排序。无论如何,在实践中使用快速排序算法比使用普通选择算法更好(内存和平均时间)。

【讨论】:

  • 不,它说std::nth_element 部分[first,last) 范围进行排序,所以nth 元素在正确的位置好像整个范围已完全排序。它所做的比完全排序更接近递归分区。
  • @SaeedAmiri 这当然不是一个完整的排序。我wrote an Stack Overflow tag wiki 代表nth_element,我认为它简洁地描述了输出条件。
  • @Blastfurnace,它部分排序,但这种排序仍然需要 O(n logn)平均,如果很难看到这一点,请告诉我,我会添加证明。
  • @ChrisA。是的,它部分排序,但这不会影响平均情况,当我引用特定链接时,我确定我知道他们写了什么。
  • C++ 标准保证兼容的std::nth_element 实现平均具有线性复杂度,而不是 O(n log n)。如果您不同意,请使用 ISO/IEC JTC1/SC22/WG21。
猜你喜欢
  • 2013-12-07
  • 1970-01-01
  • 2015-05-22
  • 2019-07-31
  • 1970-01-01
  • 2013-11-26
  • 2012-01-29
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多