C 矢量元素擦除与新向量创建

C++ vector element erase versus new vector creation

本文关键字:向量 创建 新向量 元素 擦除      更新时间:2023-10-16

我正在使用一个需要随机选择并有效删除的元素向量,直到满足条件,或直到选择了所有元素为止。但是,直到代码执行的后期阶段,它们实际上才会被删除,因此我需要维护有效的可用元素列表。我可以从第二个向量中删除元素,也可以每次重新创建它。请在下面查看我的代码的最小版本,显示了每次在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;
    }

很明显,在向量中间擦除元素要求基础系统通过其余元素进行迭代,并将它们"移动"到其新的有效位置。显然,如果擦除的元素接近向量的末端,则意味着更少的迭代。

我已经阅读了有关与擦除向量元素相关的成本的一些帖子,但是我还没有看到任何直接解决我问题的内容。擦除后"移动"元素的过程是否引入了开销,可以通过创建指向有效元素的新矢量来使其每次遍历所有元素的迭代更便宜?如上上面的代码示例。

欢呼,菲尔

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

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

删除后"移动"元素的过程是否引入了开销,可以通过创建指向有效的向量的新向量来使其每次遍历所有元素的迭代?

这完全取决于移动元素所涉及的内容。如果是上述指针,那么您可以移动很多元素,甚至在接近在新向量中分配内存的成本之前。通过"很多",我正在思考数百甚至数千个。

在上面的代码中,看起来可用性的向量是多余的。如果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();
  }
}

在我看来只有在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。在循环中进行任何内存分配总是昂贵的,因此请避免。