Quelle est la manière la plus efficace et la plus standard (C++ 11/14) de trouver l’élément max/min du vecteur de vecteurs?
std::vector<std::vector<double>> some_values{{5,0,8},{3,1,9}};
l'élément max recherché est 9
l'élément recherché est 0
Toute méthode efficace pour calculer le maximum d'éléments dans un tableau à deux dimensions (ou un vecteur dans votre cas) implique une complexité de O(n^2)
indépendamment de ce que vous faites, car le calcul implique une comparaison entre les éléments n*n
. est d'utiliser std::max_element
sur le vecteur de vecteurs.Je ne vais pas approfondir les détails.Voici la référence .
Voici une solution multithread qui renvoie un itérateur (ou des lancers) au maximum pour le type général T
(en supposant que operator<
est défini pour T
). Notez que l'optimisation la plus importante consiste à effectuer les opérations max internes sur les «colonnes» afin d'exploiter le classement en colonnes majeur de C++.
#include <vector>
#include <algorithm>
template <typename T>
typename std::vector<T>::const_iterator max_element(const std::vector<std::vector<T>>& values)
{
if (values.empty()) throw std::runtime_error {"values cannot be empty"};
std::vector<std::pair<typename std::vector<T>::const_iterator, bool>> maxes(values.size());
threaded_transform(values.cbegin(), values.cend(), maxes.begin(),
[] (const auto& v) {
return std::make_pair(std::max_element(v.cbegin(), v.cend()), v.empty());
});
auto it = std::remove_if(maxes.begin(), maxes.end(), [] (auto p) { return p.second; });
if (it == maxes.begin()) throw std::runtime_error {"values cannot be empty"};
return std::max_element(maxes.begin(), it,
[] (auto lhs, auto rhs) {
return *lhs.first < *rhs.first;
})->first;
}
threaded_transform
ne fait pas (encore) partie de la bibliothèque standard, mais voici une implémentation que vous pourriez utiliser.
#include <vector>
#include <thread>
#include <algorithm>
#include <cstddef>
template <typename InputIterator, typename OutputIterator, typename UnaryOperation>
OutputIterator threaded_transform(InputIterator first, InputIterator last, OutputIterator result, UnaryOperation op, unsigned num_threads)
{
std::size_t num_values_per_threads = std::distance(first, last) / num_threads;
std::vector<std::thread> threads;
threads.reserve(num_threads);
for (int i = 1; i <= num_threads; ++i) {
if (i == num_threads) {
threads.Push_back(std::thread(std::transform<InputIterator,
OutputIterator, UnaryOperation>,
first, last, result, op));
} else {
threads.Push_back(std::thread(std::transform<InputIterator,
OutputIterator, UnaryOperation>,
first, first + num_values_per_threads,
result, op));
}
first += num_values_per_threads;
result += num_values_per_threads;
}
for (auto& thread : threads) thread.join();
return result;
}
template <typename InputIterator, typename OutputIterator, typename UnaryOperation>
OutputIterator threaded_transform(InputIterator first, InputIterator last, OutputIterator result, UnaryOperation op)
{
return threaded_transform<InputIterator, OutputIterator, UnaryOperation>(first, last, result, op, std::thread::hardware_concurrency());
}
Si vous utilisiez un boost::multi_array<double, 2>
au lieu d'un std::vector<std::vector<double>>
, ce serait aussi simple que:
auto minmax = std::minmax_element(values.data(), values.data() + values.num_elements());
Si vous créez un itérateur personnalisé pour parcourir toute la double
de votre vector
de vector
, un simple std::minmax_element
fait le travail.
itérateur est quelque chose comme:
class MyIterator : public std::iterator<std::random_access_iterator_tag, double>
{
public:
MyIterator() : container(nullptr), i(0), j(0) {}
MyIterator(const std::vector<std::vector<double>>& container,
std::size_t i,
std::size_t j) : container(&container), i(i), j(j)
{
// Skip empty container
if (i < container.size() && container[i].empty())
{
j = 0;
++(*this);
}
}
MyIterator(const MyIterator& rhs) = default;
MyIterator& operator = (const MyIterator& rhs) = default;
MyIterator& operator ++() {
if (++j >= (*container)[i].size()) {
do {++i;} while (i < (*container).size() && (*container)[i].empty());
j = 0;
}
return *this;
}
MyIterator operator ++(int) { auto it = *this; ++(*this); return it; }
MyIterator& operator --() {
if (j-- == 0) {
do { --i; } while (i != 0 && (*container)[i].empty());
j = (*container)[i].size();
}
return *this;
}
MyIterator operator --(int) { auto it = *this; --(*this); return it; }
double operator *() const { return (*container)[i][j]; }
bool operator == (const MyIterator& rhs) const {
return container == rhs.container && i == rhs.i && j == rhs.j;
}
bool operator != (const MyIterator& rhs) const { return !(*this == rhs); }
private:
const std::vector<std::vector<double>>* container;
std::size_t i;
std::size_t j;
};
Et l'utilisation peut être
// Helper functions for begin/end
MyIterator MyIteratorBegin(const std::vector<std::vector<double>>& container)
{
return MyIterator(container, 0, 0);
}
MyIterator MyIteratorEnd(const std::vector<std::vector<double>>& container)
{
return MyIterator(container, container.size(), 0);
}
int main() {
std::vector<std::vector<double>> values = {{5,0,8}, {}, {3,1,9}};
auto b = MyIteratorBegin(values);
auto e = MyIteratorEnd(values);
auto p = std::minmax_element(b, e);
if (p.first != e) {
std::cout << "min is " << *p.first << " and max is " << *p.second << std::endl;
}
}
Vous devez au moins regarder chaque élément, ainsi, comme l'a mentionné Anony-mouse, la complexité sera au moins O (n ^ 2) .
#include <vector>
#include <limits>
#include <algorithm>
int main() {
std::vector<std::vector<double>> some_values;
double max = std::numeric_limits<double>::lowest();
for (const auto& v : some_values)
{
double current_max = *std::max_element(v.cbegin(), v.cend());
max = max < current_max ? current_max : max; // max = std::max(current_max, max);
}
}
En utilisant la fonction accumulate
vous pourriez écrire:
#include <algorithm>
#include <iostream>
#include <vector>
int main()
{
std::vector<std::vector<double>> m{ {5, 0, 8}, {3, 1, 9} };
double x = std::accumulate(m.begin(), m.end(), m[0][0],
[](double max, const std::vector<double> &v)
{
return std::max(max,
*std::max_element(v.begin(),
v.end()));
});
std::cout << x << '\n';
return 0;
}
mais je préférerais le bon, vieux for-loop.
L'exemple peut être étendu pour trouver les valeurs min et max:
std::accumulate(m.begin(), m.end(),
std::make_pair(m[0][0], m[0][0]),
[](std::pair<double, double> minmax, const std::vector<double> &v)
{
auto tmp(std::minmax_element(v.begin(), v.end()));
return std::make_pair(
std::min(minmax.first, *tmp.first),
std::max(minmax.second, *tmp.second));
});
Malheureusement, un vecteur de vecteur n'est pas stocké de manière contiguë en mémoire, vous n'avez donc pas un seul bloc contenant toutes les valeurs (c'est l'une des raisons pour lesquelles un vecteur de vecteur n'est pas un bon modèle pour une matrice).
Vous pouvez tirer parti d'un vecteur de vecteur s'il contient beaucoup d'éléments.
Étant donné que chaque sous-vecteur est autonome, vous pouvez utiliser std :: async pour remplir de manière asynchrone un vecteur de futures contenant la valeur maximale de chaque sous-vecteur.
Vous pouvez le faire assez facilement avec la bibliothèque range-v3 de Eric Niebler (qui, à l’évidence, n’est pas encore standard, mais nous espérons qu’elle le sera dans un avenir pas trop éloigné):
vector<vector<double>> some_values{{5,0,8},{3,1,9}};
auto joined = some_values | ranges::view::join;
auto p = std::minmax_element(joined.begin(), joined.end());
p.first
est un itérateur de l'élément min; p.second
au maximum.
(range-v3 a une implémentation de minmax_element, mais malheureusement, il nécessite un ForwardRange et view :: join ne me donne qu'un InputRange, je ne peux donc pas l'utiliser.)
La manière simple for loop
:
T max_e = std::numeric_limits<T>::min();
for(const auto& v: vv) {
for(const auto& e: v) {
max_e = std::max(max_e, e);
}
}
La méthode la plus simple serait d’avoir d’abord une fonction pour déterminer les éléments max/min d’un vecteur, disons une fonction appelée:
double getMaxInVector(const vector<double>& someVec){}
Passer par référence (uniquement à des fins de lecture) dans ce cas sera beaucoup plus économe en espace et en temps (vous ne voulez pas que votre fonction copie un vecteur entier). Ainsi, dans votre fonction de détermination de l'élément max/min d'un vecteur de vecteurs, vous auriez une boucle imbriquée, telle que:
for(size_t x= 0; x < some_values.size(); x++){
for(size_t y = 0; y < x.size(); y++){
// y represents the vectors inside the vector of course
// current max/min = getMax(y)
// update max/min after inner loop finishes and x increments
// by comparing it with previous max/min
Le problème avec la solution ci-dessus est son inefficacité. De ma connaissance, cet algorithme fonctionnera généralement sur l’efficacité de O (n ^ 2log (n)), ce qui est assez peu impressionnant. Mais bien sûr, c'est toujours une solution. Bien que certains algorithmes standard puissent trouver pour vous le maximum/min d’un vecteur, il est toujours plus compliqué d’écrire le vôtre, et utiliser ce que vous donnez ne fera généralement rien pour améliorer l’efficacité car l’algorithme sera généralement le même ( pour les petites fonctions qui déterminent max/min). En fait, théoriquement, les fonctions standard seraient exécutées légèrement plus lentement car ces fonctions sont des modèles qui doivent déterminer le type avec lequel elles traitent au moment de l'exécution.
Disons que nous avons un vecteur nommé some_values , comme indiqué ci-dessous
7 4 2 0
4 8 10 8
3 6 7 6
3 9 19* 14
définir un vecteur unidimensionnel comme indiqué ci-dessous
vector<int> oneDimVector;
for(int i = 0; i < 4; i++){
for(int j = 0; j < 4; j++){
oneDimVector.Push_back(some_values[i][j]);
}
}
Trouvez ensuite un élément maximum/minimum dans ce vecteur unidimensionnel, comme indiqué ci-dessous
vector<int>::iterator maxElement = max_element(oneDimVector.begin(),oneDimVector.end());
vector<int>::iterator minElement = min_element(oneDimVector.begin(),oneDimVector.end());
Maintenant, vous obtenez les éléments max/min comme ci-dessous
cout << "Max element is " << *maxElement << endl;
cout << "Min element is " << *minElement << endl;