从向量中最快擦除元素或更好地使用内存(排序基数)

Posted

技术标签:

【中文标题】从向量中最快擦除元素或更好地使用内存(排序基数)【英文标题】:Fastest erase element from vector or better use of memory (sorting radix) 【发布时间】:2017-03-15 20:49:53 【问题描述】:

我有一个麻烦,我从基数排序算法创建了一个实现,但我认为我可以使用更少的内存,而且我可以!但是......我这样做是在使用后擦除矢量的元素。问题:执行 3 分钟 vs 17 秒。如何更快地擦除元素?或者...如何更好地使用内存。

sort.hpp

#include <iostream>

#include <vector>
#include <algorithm>

unsigned digits_counter(long long unsigned x);

void radix( std::vector<unsigned> &vec )

  unsigned size = vec.size();
  if(size == 0);
  else 
    unsigned int max = *max_element(vec.begin(), vec.end());
    unsigned int digits = digits_counter( max );

    // FOR EVERY 10 POWER...
    for (unsigned i = 0; i < digits; i++) 
      std::vector < std::vector <unsigned>  > base(10, std::vector <unsigned> ());

#ifdef ERASE
      // GET EVERY NUMBER IN THE VECTOR AND
      for (unsigned j = 0; j < size; j++) 
    unsigned int digit = vec[0];

    // GET THE DIGIT FROM POSITION "i" OF THE NUMBER vec[j]
    for (unsigned k = 0; k < i; k++)
      digit /= 10;
    digit %= 10;

    // AND PUSH NUMBER IN HIS BASE BUCKET
    base[ digit ].push_back( vec[0] );
    vec.erase(vec.begin());
      

#else
      // GET EVERY NUMBER IN THE VECTOR AND
      for (unsigned j = 0; j < size; j++) 
    unsigned int digit = vec[j];

    // GET THE DIGIT FROM POSITION "i" OF THE NUMBER vec[j]
    for (unsigned k = 0; k < i; k++)
      digit /= 10;
    digit %= 10;

    // AND PUSH NUMBER IN HIS BASE BUCKET
    base[ digit ].push_back( vec[j] );
      
      vec.erase(vec.begin(), vec.end()); 
#endif

      for (unsigned j = 0; j < 10; j++)
    for (unsigned k = 0; k < base[j].size(); k++)
      vec.push_back( base[j][k] );
    
  



void fancy_sort( std::vector <unsigned> &v ) 
  if( v.size() <= 1 )
    return;
  if( v.size() == 2 ) 
    if (v.front() >= v.back())
      std::swap(v.front(), v.back());
    return;
  
  radix(v);

sort.cpp

#include <vector>

#include "sort.hpp"

using namespace std;

int main(void)

  vector <unsigned> vec;

  vec.resize(rand()%10000);

  for (unsigned j = 0; j < 10000; j++) 
    for (unsigned int i = 0; i < vec.size(); i++)
      vec[i] = rand() % 100;
    fancy_sort(vec);
  


  return 0;

我只是在学习……这是我的 Deitel C++ 的第二章。所以...如果有人有更复杂的解决方案...我可以学习如何使用它,难度无关紧要。

结果 没有擦除:

g++ -O3 -Wall sort.cpp && time ./a.out
./a.out  2.93s user 0.00s system 98% cpu 2.964 total

带擦除:

g++ -D ERASE -O3 -Wall sort.cpp && time ./a.out
./a.out  134.64s user 0.06s system 99% cpu 2:15.20 total

【问题讨论】:

您在构建程序时使用了哪些优化?如果是“调试”或未优化的构建,则结果毫无意义。您也没有提及您正在排序的数字数量,并且您没有发布minimal reproducible example。 您可以尝试创建一个固定大小的 std:vector,而不是对每个数字进行擦除/分配。分配和释放内存的开销可能会损害您的运行时间。 @PaulMcKenzie 此次编辑几乎完成,但您可以获得更好的想法。 【参考方案1】:

std::vector 不会从其中移除部件。这是你必须忍受的事实。您在速度和内存使用之间的权衡是一个经典问题。使用向量,任何移除(除了从它的末端)都是昂贵的并且是一种浪费。这是因为每次删除元素时,程序要么必须在内部重新分配数组,要么必须移动所有元素以填充空白。如果您继续使用向量,这是您永远无法克服的终极限制。

针对您的问题的向量:速度快但占用大量内存。

另一个(可能)最佳极端(内存方面)是std::list,它在任何地方删除任何东西都绝对没有问题。另一方面,访问元素只能通过遍历整个列表到元素,因为它基本上是一个doubly-linked list,你不能通过它的编号访问一个元素。

针对您的问题的列表:最佳内存使用率,但速度较慢,因为访问列表元素很慢。

最后,中间立场是std::deque。它们提供了向量和列表之间的中间地带。它们在内存中并不连续,但可以通过它们的编号找到项目。从中间移除元素并不一定会导致向量中的相同破坏。 Read this 了解更多信息。

deques 解决您的问题: 中间地带,取决于问题,可能对内存和访问都很快。

如果内存是您最关心的问题,那么一定要使用列表。那是最快的。如果您想要最通用的解决方案,请使用双端队列。也不要忽视将整个数组复制到另一个容器的可能性,对其进行排序,然后将其复制回来。根据数组的大小,这可能会有所帮助。

【讨论】:

或者..我可以从头到尾排序。这更快,对吧?你说消尾没那么贵。 @MoisesRojo 如果这适用于您的算法,那么可以。但同样,您可以从末尾删除元素。否则你会破坏性能。

以上是关于从向量中最快擦除元素或更好地使用内存(排序基数)的主要内容,如果未能解决你的问题,请参考以下文章

在 C++ 崩溃中使用向量向量进行基数排序

在向量的每个结构元素中重置值的最快方法?

漫画:什么是基数排序?

有没有更好的替代 std::remove_if 从向量中删除元素的方法?

从向量中擦除对象会导致双重释放[重复]

桶排序