【问题标题】:Find max/min of vector of vectors查找向量的向量的最大值/最小值
【发布时间】:2015-10-11 21:52:55
【问题描述】:

找到向量的向量的最大/最小项的最有效和标准 (C++11/14) 方法是什么?

std::vector<std::vector<double>> some_values{{5,0,8},{3,1,9}};

想要的最大元素是 9

想要的最小元素是0

【问题讨论】:

  • std::minmax_element 用于内部向量。
  • 为什么不使用 2 个嵌套循环?其他方式可能不太可读。
  • @HumamHelfawi:对于外循环,直接使用标准算法似乎更复杂。
  • @HumamHelfawi 向量的向量不是连续存储的,每个内部向量都存储在其自己的动态分配内存的连续块中,因此您不能真正将它们视为“一维”。您可以更改存储二维数组的方式,使其连续存储,然后您可以稍微简化实现。
  • @TonyD 已经完成了

标签: c++ c++11 vector max min


【解决方案1】:

这是一个多线程解决方案,它为通用类型T 返回一个迭代器(或抛出)到最大值(假设operator&lt; 是为T 定义的)。请注意,最重要的优化是对“列”执行内部最大操作,以利用 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 还不是标准库的一部分,但这里有一个您可以使用的实现。

#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());
}

【讨论】:

  • 请注意,如果内部向量为空,则会失败。
  • @Jarod42 你说得对,谢谢。现在应该在所有情况下都可以工作(或抛出)......不再那么漂亮了:(
【解决方案2】:

如果您使用boost::multi_array&lt;double, 2&gt; 而不是std::vector&lt;std::vector&lt;double&gt;&gt;,它会很简单:

auto minmax = std::minmax_element(values.data(), values.data() + values.num_elements());

Live demo.

【讨论】:

  • 如果 boost 可用,这是一个有趣的答案。对我来说它是可用的.. 我会看到它非常感谢
【解决方案3】:

朴素的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);
    }
}

【讨论】:

  • 简洁明了。迄今为止最好的答案恕我直言。有时简单是最好的。
【解决方案4】:

您至少必须查看每个元素,因此,正如 Anony-mouse 所提到的,复杂性至少为 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);
    }
}

【讨论】:

  • 寻找最小值和最大值的复杂度是O(n),而不是O(n^2)。它需要两个循环来完成这一事实并不重要。每个元素只检查一次。
  • 请注意,如果内部向量为空,则会失败。
【解决方案5】:

您可以使用 Eric Niebler 的 range-v3 库轻松做到这一点(这显然还不是标准的,但希望在不久的将来会出现):

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 是 min 元素的迭代器; p.second 最大。

(range-v3 确实有一个 minmax_element 的实现,但不幸的是,它需要一个 ForwardRange,而 view::join 只给了我一个 InputRange,所以我不能使用它。)

【讨论】:

    【解决方案6】:

    无论您做什么,计算二维数组(或您的情况下为向量)中最大元素的任何有效方法都涉及O(n^2) 的复杂性,因为计算涉及n*n elements.Best 之间的比较易用性方面的方法是在vectors的vector上使用std::max_element。我不会深入研究。这里是reference.

    【讨论】:

    • 实际上只是O(N),其中N 是要比较的元素总数。将其物理布局为平均 N/K 子元素的 K 向量这一事实有点误导。
    • @TemplateRex 是对的。这是一个线性复杂度问题O(n)。我不知道为什么人们说二次元。
    • std::min_element 的@Anony-mouse 复杂度是指调用operator&lt; 的次数与元素总数N 的比值。除了编写嵌套循环之外,它们被分成几个 bin 的事实并不重要,但嵌套并不是将每个元素与其他元素进行比较的“循环”锦标赛。到目前为止,每个元素仅与最小值进行比较。对于 100 个数字,您有 99 次比较。对于 1000,您有 999。它是 O(N)
    • @Anony-mouse 根据我对 OP 问题的理解,他想要所有双打的最大和最小元素。无论这些元素的布局如何,只要检查一次,就可以在线性时间内找到最小值和最大值。
    • @Anony-mouse 行和列无关。对于rc 其中r*c = n 的任何值,复杂度为O(n)。就个人而言,将复杂性报告为 O(n^2) 具有误导性。不过我明白你的意思。我只是认为它会因为你需要一个嵌套循环而说它是二次的,这会让人们感到困惑。
    【解决方案7】:

    如果您创建一个自定义迭代器来迭代所有 doublevectorvector,那么简单的 std::minmax_element 就可以完成这项工作

    迭代器类似于:

    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;
    };
    

    而且用法可能

    // 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;
        }
    }
    

    Live example

    【讨论】:

      【解决方案8】:

      使用accumulate 函数,您可以编写:

      #include <iostream>
      #include <numeric>
      #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;
      }
      

      但我更喜欢好的、旧的 for 循环。

      可以扩展该示例以查找最小值和最大值:

      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));
                      });
      

      (在 real 代码中,您必须处理空向量情况)

      不幸的是,向量的向量没有连续存储在内存中,因此您没有包含所有值的单个块(这是向量向量不是矩阵的好模型的原因之一) .

      如果向量中包含很多元素,你可以利用它。

      由于每个子向量都是自治的,您可以使用std::async 异步填充包含每个子向量最大值的期货向量。

      【讨论】:

      • 不错。只有两件事:#include &lt;numeric&gt;accumulate 而不是#include &lt;algorithm&gt;。而且你需要在你的 lambda 中添加一个 if 来处理一个空向量。
      • @MartinMorterol 你说得对,我已经确定了答案。谢谢。
      【解决方案9】:

      最简单的方法是首先有一个函数来确定一个向量的最大/最小元素,比如一个名为的函数:

          double getMaxInVector(const vector<double>& someVec){}
      

      在这种情况下,通过引用传递(仅用于阅读目的)将更节省时间和空间(您不希望您的函数复制整个向量)。因此,在您确定向量向量的最大/最小元素的函数中,您将有一个嵌套循环,例如:

          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
      

      上述解决方案的问题在于效率低下。据我所知,这个算法通常会以 O(n^2log(n)) 的效率运行,这非常不起眼。但当然,它仍然是一个解决方案。尽管可能有标准算法可以为您找到向量的最大值/最小值,但编写自己的算法总是更有成就感,并且使用给定的算法通常不会提高效率,因为算法通常是相同的(对于确定最大值/最小值的小函数)。事实上,从理论上讲,标准函数的运行速度会稍微慢一些,因为这些函数是模板,必须确定它在运行时处理的类型。

      【讨论】:

      • @DevSolar 你说得对,这是一个荒谬的建议。我的意思是通过引用传递。
      【解决方案10】:

      假设我们有一个名为 some_values 的向量,如下所示

      7 4 2 0 
      4 8 10 8 
      3 6 7 6 
      3 9 19* 14
      

      定义一个一维向量,如下图

      vector<int> oneDimVector;
      for(int i = 0; i < 4; i++){
          for(int j = 0; j < 4; j++){
              oneDimVector.push_back(some_values[i][j]);
          }
      }
      

      然后找出该一维向量中的最大/最小元素,如下所示

      vector<int>::iterator maxElement = max_element(oneDimVector.begin(),oneDimVector.end());
      vector<int>::iterator minElement = min_element(oneDimVector.begin(),oneDimVector.end());
      

      现在你得到如下的最大/最小元素

      cout << "Max element is " << *maxElement << endl;
      cout << "Min element is " << *minElement << endl;
      

      【讨论】:

      • 这两个 for 循环根本就没有效率。正在重新分配
      • 我知道它效率低下,但是我回答这个问题是为了帮助正在学习cpp的新手,这个答案侧重于功能而不是效率。
      【解决方案11】:
      vector<vector<int>> vv = { vector<int>{10,12,43,58}, vector<int>{10,14,23,18}, vector<int>{28,47,12,90} };
      vector<vector<int>> vv1 = { vector<int>{22,24,43,58}, vector<int>{56,17,23,18}, vector<int>{11,12,12,90} };
      int matrix1_elem_sum=0;
      int matrix2_elem_sum = 0;
      for (size_t i = 0; i < vv.size(); i++)
      {
          matrix1_elem_sum += std::accumulate(vv[i].begin(), vv[i].end(), 0);
          matrix2_elem_sum += std::accumulate(vv1[i].begin(), vv1[i].end(), 0);
      
      }
      cout << matrix1_elem_sum <<endl;
      cout << matrix2_elem_sum << endl;
      int summ = matrix1_elem_sum + matrix2_elem_sum;
      cout << summ << endl;
      

      或优化变体:

      vector<vector<int>> vv = { vector<int>{10,12,43,58}, vector<int>{10,14,23,18}, vector<int>{28,47,12,90} };
      vector<vector<int>> vv1 = { vector<int>{22,24,43,58}, vector<int>{56,17,23,18}, vector<int>{11,12,12,90} };
      int summ=0;
      int matrix2_elem_sum = 0;
      for (size_t i = 0; i < vv.size(); i++)
      {
          summ += std::accumulate(vv[i].begin(), vv[i].end(), 0)+ std::accumulate(vv1[i].begin(), vv1[i].end(), 0);
      
      
      }
      cout << summ << endl;
       }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-06-02
        • 2015-05-04
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多