【问题标题】:C++ vector element erase versus new vector creationC++ 向量元素擦除与新向量创建
【发布时间】:2017-11-04 18:15:43
【问题描述】:

我正在处理需要随机选择并有效删除的元素向量,直到满足任何一个条件,或者直到所有元素都被选中。但是,直到代码执行的某个后期阶段,它们才会真正被删除,因此我需要维护一个有效、可用元素的列表。我可以从第二个向量中删除元素,或者我可以每次都重新创建它。请在下面查看我的代码的最小版本,该示例显示了在 while 循环中每次创建向量的位置:

    Random mRandom; // Pseudo-random number generator
    std::vector< Element* > mElements;
    for( unsigned index = 0; index < ARBITRARY_VALUE; index++ )
      mElements.push_back( new Element( ) );

    std::vector< bool > removedElements;
    bool condition = true;

    while( condition == true ) {
      std::vector< unsigned > availableIndices;

      for( unsigned index = 0; index < mElements.size( ); index++ ) {
        if( removedElements[ index ] == false )
          availableIndices.push_back( index );
      }

      if( availableIndices.size( ) > 0 ) {
        unsigned maximum = availableIndices.size( ) - 1;
        unsigned randomIndex = mRandom.GetUniformInt( maximum ); // Zero to max
        removedElements[ availableIndices[ randomIndex ] ] = true;
        Element* element = mElements[ availableIndices[ randomIndex ] ];
        condition = element->DoStuff( ); // May change condition and exit while
      } else
        break;
    }

很明显,擦除向量中间的元素需要底层系统遍历剩余元素并将它们“移动”到新的有效位置。显然,如果擦除的元素接近向量的末尾,这意味着更少的迭代。

我已经阅读了一些关于擦除矢量元素相关成本的帖子,但我没有看到任何直接解决我的问题的内容。擦除后“移动”元素的过程是否会引入开销,通过创建一个指向有效元素的新向量,每次迭代所有元素时都会变得更便宜?就像我上面的代码示例一样。

干杯,菲尔

【问题讨论】:

  • 看起来您希望std::stable_partition 对最终将被删除的元素进行分区。
  • mElements 中的顺序重要吗?如果没有,那么您可以简单地使用std::swap(mElements[randomIndex], mElements[--cur_size]);“删除”一个元素(其中cur_size 在循环之前用mElements.size() 初始化)。换句话说,将“已移除”的元素移动到最后,在进一步处理中忽略它们。如果需要,您可以在最后一次erase 它们。

标签: c++ performance vector processing-efficiency cpu-speed


【解决方案1】:

我无法评论解决您的问题的最佳方法,因为我还不确定函数或算法的实际要求是什么(即元素是否应该保持顺序?不可用的元素会再次可用吗?如果他们这样做了,那么订购会很重要吗?等等)

但是,关于最后一个问题:

在擦除之后“移动”元素的过程是否会引入开销,从而通过创建指向有效元素的新向量来降低每次迭代所有元素的成本?

这完全取决于移动元素所涉及的内容。如果它是一个指针,如上所述,那么您甚至可以在接近在新向量中分配内存的成本之前移动很多元素。而且“很多”我认为是数百甚至数千。

在上面的代码中,可用性向量似乎是多余的。如果Element 指针位于availableIndicies 的向量中,则它是可用的。

如果我正确理解了意图,我想我可能会按照以下几行进行重构:

#include <vector>
#include <random>

struct Element
{
  bool doStuff();
};


struct ElementAvailability
{
  ElementAvailability(std::vector<Element*> const& storage)
  : storage_(storage)
  {}

  void resync()
  {
    // will requre an allocation at most once if storage_ does not grow
    available_ = storage_;
  }

  std::size_t availableCount() const {
    return available_.size();
  }

  Element* removeAvailable(std::size_t index) {
    auto pe = available_[index];
    available_.erase(std::begin(available_) + index);
    return pe;
  }

  void makeUnavailable(std::size_t available_i)
  {
    available_.erase(std::next(std::begin(available_), available_i));
  }

private:
  std::vector<Element*> const& storage_;
  std::vector<Element*> available_;
};

// I have used a std random engine because I don't know your library
auto eng = std::default_random_engine(std::random_device()());

void test(std::vector<Element*>const& elems)
{
  auto available = ElementAvailability(elems);

  bool condition = true;
  auto getCount =[&condition, &available] () -> std::size_t 
  {
    if (condition) {
      available.resync();
      auto count = available.availableCount();
      return count;
    }
    else {
      return 0;
    }
  };

  while (auto count = getCount()) {
    auto range = std::uniform_int_distribution<std::size_t>(0, count - 1);
    auto index = range(eng);
    auto candidate = available.removeAvailable(index);
    condition = candidate->doStuff();
  }
}

【讨论】:

    【解决方案2】:

    您提出的随机消除元素的问题在我看来只能在O(n^2) 时间和O(n) 空间复杂度中解决。因为您必须传递所有元素一次,并且在每次传递时,您必须在仍然存在的元素序列中找到一个随机索引并维护该序列。可能很少有具有不同算法原语的方法。下面我将介绍我的解决方案,该解决方案在以 CPU/内存操作友好的方式实现这一目标的同时存档。

    void runRandomTasks() {
      Random mRandom; // Pseudo-random number generator
      std::vector<Element*> mElements;
      for (unsigned index = 0; index < ARBITRARY_VALUE; ++index) {
        mElements.push_back(new Element);
      }
      size_t current_size = mElements.size();
      if (!current_size)
        return;
      std::vector<Element*> current_elements(current_size, nullptr);
      for (unsigned index = 0; index < current_size; ++index) {
        current_elements[index] = mElements[index];
      }
      Element** last_ptr = &current_elements[0] + current_size - 1;
    
      bool condition = true;
    
      while (condition && current_size) {
        unsigned random_size = mRandom.GetUniformInt(current_size - 1) + 1; // Zero to max
        Element** ptr = last_ptr;
        while (true) {
          random_size -= (bool)(*ptr);
          if (random_size) {
            --ptr;
          } else {
            break;
          }
        }
    
        condition = (*ptr)->DoStuff(); // May change condition and exit while
        *ptr = nullptr;
        --current_size;
      }
    }
    

    关于向量元素擦除的问题,您可以在我的解决方案中找到一个找到随机索引的循环,这在时间复杂度上相当于向量中的元素擦除,但由于元素移位是一个较小的常数省略,而是检查元素值是否为0。在循环中进行任何内存分配总是代价高昂,因此请避免这样做。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-03-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多