std::minmax_element
:返回一个由最小元素迭代器作为第一个元素和最大元素迭代器作为第二个元素组成的 pair。
std::min_element
:返回范围 [first,last) 中最小元素的迭代器。
std::max_element
:返回范围 [first,last) 中最大元素的迭代器。
std::minmax_element
是否使用 对整个列表进行排序 来实现?
从 std::minmax_element
返回的 pair 处理开销值得吗?
std::minmax_element
:返回一个由最小元素迭代器作为第一个元素和最大元素迭代器作为第二个元素组成的 pair。
std::min_element
:返回范围 [first,last) 中最小元素的迭代器。
std::max_element
:返回范围 [first,last) 中最大元素的迭代器。
std::minmax_element
是否使用 对整个列表进行排序 来实现?
从 std::minmax_element
返回的 pair 处理开销值得吗?
不用担心 std::minmax_element
会进行任何排序。它会在遍历范围时始终保持原样。它更高效的原因是可以一次性找到最大值和最小值,在分别查找最大值和最小值时需要两次完整遍历。
std::minmax_element
的复杂度为 max(floor(3/2(N−1)), 0)
,而 std::max_element
和 std::min_element
分别是 max(N-1,0)
,所以使用 std::minmax_element
可以减少约25%的操作。
此外,std::minmax_element
找到的是最后一个最大元素,而 std::max_element
找到的是第一个最大元素。
因此,如果您需要找到范围内的最小值和最大值,则应使用 std::minmax_element
。如果您只需要最小值或最大值,则应使用专门的版本。使用即将推出的C++17标准和结构化绑定处理从 std::minmax_element
返回的结果将变得更加容易。您将能够编写
auto [min, max] = std::minmax_element(...);
现在,对偶中的第一个元素存储在min
中,第二个元素存储在max
中。
minmax_element
如何工作的内容,这也有助于解释为什么它(通常)比分别运行 min_element
和 max_element
更好,并讨论一些特定情况下它表现不佳的情况。vector<int>
或类似的范围内:每个对的两个元素之间的比较具有不可预测的结果,导致处理器中的分支预测失败(尽管仅在数据被更或多或少随机排序的情况下才是如此);当前编译器不总是将分支转换为条件传输,因为它们可能可以。此外,更复杂的算法更难以进行编译器矢量化。int
等)元素类型提供重载的 minmax_element
函数,其中使用默认比较器的天真算法。虽然标准规定了比较次数的限制,但是如果这些比较的效果不能被观察到,则实际计算的数量并不重要,只要时间复杂度相同(在这两种情况下都为 O(N)
)。但是,尽管这可能在数据随机排序时提供更好的性能,但它可能会在数据有序时产生更差的性能。min_element
和 max_element
实际上可能比使用 minmax_element
稍微快一些。然而,对于已排序的数据,minmax_element
比分别使用 min_element
和 max_element
快得多。在我的系统(Haswell 处理器)上,下面的代码(使用 gcc -O3 -std=c++11 -march=native
编译,GCC 版本 5.4),一次样本运行显示 min/max 分别为 692 毫秒,minmax 为 848 毫秒。当然,不同的运行之间会有一些变化,但这些值似乎是典型值。v.push_back(r(gen))
替换为 v.push_back(i)
),性能会有很大的差异:对于分别使用 min/max,它大约为 728 毫秒,而对于 minmax 组合,则降至 246 毫秒。#include <iostream>
#include <vector>
#include <algorithm>
#include <random>
#include <chrono>
constexpr int numEls = 100000000;
void recresult(std::vector<int> *v, int min, int max)
{
// Make sure the compiler doesn't optimize out the values:
__asm__ volatile (
""
:
: "rm"(v), "rm"(min), "rm"(max)
);
}
int main(int argc, char **argv)
{
using namespace std;
std::mt19937 gen(0);
uniform_int_distribution<> r(0, 100000);
vector<int> v;
for (int i = 0; i < numEls; i++) {
v.push_back(r(gen));
}
// run once for warmup
int min = *min_element(v.begin(), v.end());
int max = *max_element(v.begin(), v.end());
recresult(&v, min, max);
// min/max separately:
{
auto starttime = std::chrono::high_resolution_clock::now();
for (int i = 0; i < 5; i++) {
int min = *min_element(v.begin(), v.end());
int max = *max_element(v.begin(), v.end());
recresult(&v, min, max);
}
auto endtime = std::chrono::high_resolution_clock::now();
auto millis = std::chrono::duration_cast<std::chrono::milliseconds>(endtime - starttime).count();
cout << "min/max element: " << millis << " milliseconds." << endl;
}
// run once for warmup
auto minmaxi = minmax_element(v.begin(), v.end());
recresult(&v, *(minmaxi.first), *(minmaxi.second));
// minmax together:
{
auto starttime = std::chrono::high_resolution_clock::now();
for (int i = 0; i < 5; i++) {
minmaxi = minmax_element(v.begin(), v.end());
recresult(&v, *(minmaxi.first), *(minmaxi.second));
}
auto endtime = std::chrono::high_resolution_clock::now();
auto millis = std::chrono::duration_cast<std::chrono::milliseconds>(endtime - starttime).count();
cout << "minmax element: " << millis << " milliseconds." << endl;
}
return 0;
}
*min_element
向量化被忽略的问题。 - Marc Glisseminimum_till_now
和maximum_till_now
,并且在完整遍历结束时返回它们吗? - Saurav Sahustd::minmax_element
的时间复杂度:
最多对谓词进行 max(floor(3/2(N−1)), 0) 次应用,其中 N = std::distance(first, last)。
std::min_element
时间复杂度(与 max_element
相同):
恰好进行 max(N-1,0) 次比较,其中 N = std::distance(first, last)。
忽略 max
和 floor
,我们得到:
(N-1) * 2 vs 3/2 (N-1)
minmax_element
可以得到使用max_element
+min_element
所需比较次数的3/4
,甚至更少。
minmax_element
利用了<
运算符的传递性,通过同时比较两个元素,它知道如果正在更新最小值,则无需为最大值进行比较。也就是说,如果a < b
,那么我们只需要检查min(a, current_min)
和max(b, current_max)
,反之亦然。这个算法与
std::make_pair(std::min_element(), std::max_element())
不同,不仅效率不同,而且该算法找到的是最后一个最大元素,而std::max_element
找到的是第一个最大元素。
max_so_far
,那么我们就不需要与min_so_far
进行比较。这是因为如果它大于max_so_far
,那么它就不能小于min_so_far
。因此,可以节省一次比较。但我不明白它怎么可能只有1.5N这么少。(它是否首先将每个元素与前一个元素进行比较,然后再与min_so_far
或max_so_far
进行比较?) - Aaron McDaidm,M,a,b
->m < M
,a < b
,因此min
是min(m,a)
,max
是max(M,b)
。 因此,通过成对比较新元素。 - Jarod42