与min_element和max_element一起使用相比,使用minmax_element是否有效率上的优势?
Is there any efficiency advantage in using minmax_element over min_element and max_element together?
std::minmax_element
:返回一个数组对,其中指向最小元素的迭代器作为第一个元素,指向最大元素的迭代器作为第二个元素。
std::min_element
:返回指向[first, last)范围内最小元素的迭代器。
std::max_element
:返回指向[first, last)范围内最大元素的迭代器。
std::minmax_element
是否使用完整列表的排序来实现这一点?
从std::minmax_element
处理返回对的开销是否足够值得?
您不必担心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
更好,并讨论一些特定情况下,不执行得更好。
如果我们考虑一个简单的实现,您将维护最大值和最小值(以及它们对应的迭代器),并简单地遍历范围,将每个值与最小值和最大值进行比较,并在必要时进行调整。然而,这将给你总共2N个比较;虽然它可能比遍历列表两次执行得更好(由于更好地使用了局部性),但该规范要求(大约)进行3/2 N次比较。那怎么可能呢?
它通过处理成对而不是单独的项来工作。取范围内的前两项(#0和#1),我们可以比较它们,并将最大的分配给max-value,最小的分配给min-value。然后,我们比较接下来的两个项目(#3和#4),以确定其中哪个更大;我们将较大的值与max-value进行比较,将较小的值与min-value进行比较,并根据需要更新max-value/min-value。然后我们对每一对(#5和#6,然后#7和#8,等等)重复这个过程。
所以,每对需要三次比较——相互比较,然后最高的与当前的最大值比较,最低的与当前的最小值比较。这将需要的比较次数减少到3/2n !
根据下面的评论,然而,应该注意的是,当使用比较便宜的类型(或比较器)时,这种"改进的"算法往往会在现代处理器上产生比原始版本更差的性能-特别是,在vector<int>
或类似的范围内:每对元素之间的比较具有不可预测的结果,导致处理器中的分支预测失败(尽管这只在数据或多或少随机排序时才会发生);当前的编译器并不总是将分支转换为条件传输,尽管它们可能这样做。此外,编译器更难对更复杂的算法进行矢量化。
理论上,我认为c++库实现可以为minmax_element
函数提供重载,该函数使用原始(int
等)元素类型的朴素算法和默认比较器。虽然标准规定了比较次数的限制,但如果不能观察到这些比较的效果,那么实际计算的次数就不重要了,只要时间复杂度相同(在两种情况下都是- 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(i)
替换v.push_back(r(gen))
在下文中的程序中),性能是非常不同:对于单独的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;
}
是。你只迭代一次范围,而不是做两次。
std::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
,你得到3/4
的比较,你需要使用max_element
+ min_element
,或更好。
minmax_element
使用了<
运算符的传递性,它知道如果它在更新最小值,它不需要通过一次比较两个元素来比较最大值,即如果a < b
,我们只需要检查min(a, current_min)
和max(b, current_max)
,反之亦然。
值得注意的是:
该算法与std::make_pair(std::min_element(), std::max_element())的区别不仅在于效率,还在于该算法查找最后一个最大元素,而std::max_element查找第一个最大元素。
- 如何将 bsoncxx::d ocument::element 写入控制台
- [bsoncxx ]如何附加一个bsoncxx::d ocument::element TO bsoncxx::buil
- 在跟随中,哪个地方更有效率?
- 什么更有效率?在重载函数中或通过在基类函数中检查对象类型来实现
- 错误 C2864:'element::next':只能在类 (STRUCT) 中初始化静态常量整数数据成员
- 有点迂腐的问题:哪个更有效率
- QXmlStreamReader 阅读<element><value/></element>
- Met c++ 代码" #define ELEMENT(TYPE, FIELD)"
- gcc 未给出的 Clang 错误"attempted to construct a reference element in a tuple with an rvalue"
- 迭代器在std::unordered_map<int,std::vector<Element>>
- 语法错误,也想知道我是否可以使这段代码更有效率
- 'class Poco::XML::Element'没有名为'getNodeByPath'的成员
- 矢量中没有find(element)方法
- 是否可以在C++中使用命名变量(例如,键和值)而不是 .first 和 .second 进行 std::map<> "for element : container" 迭代?
- 生成序列 1,3,8,22,60,164 .如何使我的代码更有效率
- 在给定的 c++ 代码中"delete [] element"以实现一维数组的目的是什么?
- 如果程序在源代码中使用较短的名称,它是否更有效率
- 在这种情况下比较 std::vector 或 std::set 以获得时间复杂度 - 更有效率
- 在 gcc 中使用 erase(begin()) 从unordered_set "remove one element"很慢
- 如何从 gdcm::Tag 访问"group, element"字符串