【问题标题】:What is nth_element and what does it do exactly? and how to implement it什么是 nth_element 以及它究竟做了什么?以及如何实施
【发布时间】:2021-11-12 18:05:51
【问题描述】:

在我接触到算法std::nth_element 之前,我几乎了解了许多STL 算法。我被困住了;我不知道它是如何工作的,但确实如此。

为了教育和理解,有人可以向我解释算法std::nth_element 的工作原理吗?

std::vector<int> v{ 9, 3, 6, 2, 1, 7, 8, 5, 4, 0 };
std::nth_element(v.begin(), v.begin() + 2, v.end());

for (auto i : v)
    std::cout << i << " ";
std::cout << '\n';

输出:

1 0 2 3 6 7 8 5 4 9 
  • 那么nth 元素在哪里?
  • 算法的作用和作用是什么?
  • 它会进行某种部分排序吗?

以下是来自 cppreference.com 的一些解释:

nth_element 是一种部分排序算法,它重新排列 [first, last) 中的元素,使得:

  • 如果 [first, last) 已排序,则 nth 指向的元素将更改为该位置将出现的任何元素。
  • 此新的第 n 个元素之前的所有元素都小于或等于新的第 n 个元素之后的元素。 更正式地说,nth_element 以升序对范围 [first, last) 进行部分排序,以便对于范围 [first, nth ) 以及 [nth, last) 范围内的任何 j。如果范围已完全排序,则放置在第 n 个位置的元素正是出现在该位置的元素。

nth 可能是结束迭代器,在这种情况下该函数无效。

  • 我仍然对此感到困惑。什么是第 n 个元素以及如何实现这样的可能算法?为了教育起见,我模仿了许多 STL 算法。非常感谢!

【问题讨论】:

  • So where is nth element here?“在哪里”是什么意思? How and what the algorithm does? 正是您引用的文档中所述的内容。 Does it do some sort of partial sorting?嗯...nth_element is a partial sorting algorithm
  • 如果您无法理解文档,请告诉我们您不理解哪些行/语句。
  • 也许要回答的一个更有启发性的问题是“在什么情况下有人会发现调用nth_element() 很有用”? (大概这个函数不是为了让 STL 变大而编写的;一定是有人想要解决一些常见问题,促使他们编写它并将其包含在 STL 中)
  • @ItachiUchiwa:第 n 个元素指的是位置,而不是值。 v.begin() + 2 是第三个元素(索引 2,从 0 开始)。如果整个数组已排序,2 将出现在该位置,nth_element 会发生这种情况。所有其他元素的位置都是半随机的,除了保证小于以索引2 结尾的元素的所有元素都在它之前,所有大于它的元素都在它之后。 Introselect 是推荐算法。
  • @JeremyFriesner:我想它的用例类似于partial_sort。您需要在某个排名阈值处分离最大和最小元素,但不需要对元素进行排序以获得有用的结果。例如,要获得数据集中间 90% 的平均值,您需要将两边的 5% 分离为异常值,但中间 90% 不需要排序。您可以使用nth_element 一次来分离底部的 5%,然后再次(在枢轴右侧 95% 处)分离顶部的 5%。然后是线性传递来计算平均值。三个O(n) 步骤,根本没有O(n log n) 排序。

标签: c++ algorithm nth-element


【解决方案1】:

那么这里的第 n 个元素在哪里?

第 n 个元素是索引 2 处的 2,因为这是您在传递 begin()+2 时所要求的。

如果 [first, last) 已排序,则 nth 指向的元素将更改为该位置将出现的任何元素。

这意味着,如果对向量进行了排序,则元素的顺序将是

0 1 2 3 4 5 6 7 8 9 
    ^--- begin() + 2

您要求在索引 2(第 3 个位置)处具有第 3 个最大元素,这就是算法的作用。

此外,它将所有较小的元素放在前面,将所有较大的元素放在后面:

!(*j &lt; *i)(对于第一个版本,或 comp(*j, *i) == false 对于第二个版本)满足范围 [first, nth) 中的任何 i 和范围 [nth, last) 中的任何 j。

让我们使用索引而不是迭代器,那么对于任何i &lt; 2 和任何j &gt; 2,它都拥有v[i] &lt; v[j]。换句话说,10 都小于2 3 6 7 8 5 4 9 中的任何元素。

【讨论】:

  • 但是v.begin() + 2 的值是6 不是2? 2 在我的输出中而不是原始序列中。
  • @ItachiUchiwa "如果 [first, last) 被排序,则 nth 指向的元素将更改为该位置将出现的任何元素。 2 是 对向量进行排序后将出现在索引 2 处的值
  • @Itachi Uchiwa 查看编辑
  • 最后一件事:我可以为教育目的实现类似的算法吗?
  • @ItachiUchiwa 你可以吗?我无法回答。 cppreference 页面上有实现的链接:libstdc++libc++
【解决方案2】:

在解决你的问题之前,我会先解释一下我的代码

例如我有这样的代码

int m_array_biasa[8] {3,2,10,45,33,56,23,47};

我平时使用它就像

std::nth_element(m_array_biasa, m_array_biasa + 4, m_array_biasa + 8);

所以这里的第n个元素是4[33],std::nth_element的规则是第n个左边的数字必须小于等于,右边的数字必须大于n个

别忘了,数据必须从小到大排序(默认)

原来的数据

3,2,10,45,33,56,23,47

改为

2 3 10 23 33 45 47 56

我的第n个是4[33],所以上面的规则适用(不包括排序结果)

结果是

3 2 10 23 33 56 45 47

注意上面,33的位置并没有改变,但是有时候会有点混乱,比如我们把33改成1,那么结果

2 1 3 10 23 45 47 56

这里发生了什么,为什么数字1移动了(被23代替),为什么没有像之前的数字33一样,我之前说过我们要先对数据进行排序(见上面的排序),它转出索引 nth[4] 是数字 23 ,那么数字 1 被数字 23 替换,为什么要替换?,见 nth_element 规则

现在回答你的问题。

std::vector<int> v{ 9, 3, 6, 2, 1, 7, 8, 5, 4, 0 };
std::nth_element(v.begin(), v.begin() + 2, v.end());

v.begin() 包含 9,v.begin() + 2 包含 6,记住,nth_element 会先排序

0 1 2 3 4 5 6 7 8 9

你的输出是

1 0 2 3 6 7 8 5 4 9

上面的第n个[2](根据你的v.begin()+2)p是2,所以这里的2就像其他数据的引用,2之前的数据必须小于它,它之后的数据必须是不止于此

【讨论】:

    猜你喜欢
    • 2013-07-28
    • 2011-05-03
    • 2020-07-29
    • 2012-07-14
    • 2017-02-14
    • 2015-11-16
    相关资源
    最近更新 更多