使用索引向量重新排序向量

44

我希望能够使用另一个向量来指定顺序,以重新排列向量中的项:

char   A[]     = { 'a', 'b', 'c' };
size_t ORDER[] = { 1, 0, 2 };

vector<char>   vA(A, A + sizeof(A) / sizeof(*A));
vector<size_t> vOrder(ORDER, ORDER + sizeof(ORDER) / sizeof(*ORDER));
<br>reorder_naive(vA, vOrder);
<br>// A is now { 'b', 'a', 'c' }
以下是一种效率低下的实现方式,需要复制向量:
void reorder_naive(vector<char>& vA, const vector<size_t>& vOrder)  
{   
    assert(vA.size() == vOrder.size());  
    vector vCopy = vA; // Can we avoid this?  
    for(int i = 0; i < vOrder.size(); ++i)  
        vA[i] = vCopy[ vOrder[i] ];  
}  

有没有更有效率的方式,例如使用swap()函数?


2
不要在函数名中使用全大写字母,或者其他任何地方。只有 #define 可以这样做。 - GManNickG
1
关于ORDER的内容存在歧义。它是相应字母应存储的索引,还是应该在此位置存储的字母的索引?在给定的示例中,这两种解释都是正确的,尽管它们是不同的。 - chmike
我原本想的是后者,但两种解释得到的结果完全相同。 - Marc Eaddy
8
如果有人正在寻找一个高效版本的上述“reorder_naive”,请勿使用下面提出的解决方案。它们计算的是问题的前一种而不是后一种解释(参见上面的注释),但是并未提供相同的结果。 - gg349
14个回答

31
这个算法基于chmike的算法,但重新排序索引向量是const。对于所有11!个[0..10]的排列,此函数与他的算法相同。复杂度为O(N ^ 2),其中N是输入的大小,或更精确地说,是最大orbit的大小。
请参见下面的优化O(N)解决方案,该解决方案修改了输入。
template< class T >
void reorder(vector<T> &v, vector<size_t> const &order )  {   
    for ( int s = 1, d; s < order.size(); ++ s ) {
        for ( d = order[s]; d < s; d = order[d] ) ;
        if ( d == s ) while ( d = order[d], d != s ) swap( v[s], v[d] );
    }
}

这是我投入更多精力的STL风格版本。它比原来快了约47%(也就是说,在[0..10]范围内快了近两倍!),因为它尽早地完成所有交换操作,然后返回结果。重新排序的向量由若干轨道组成,每个轨道在达到第一个成员时重新排序。当最后几个元素不包含轨道时,它会更快。
template< typename order_iterator, typename value_iterator >
void reorder( order_iterator order_begin, order_iterator order_end, value_iterator v )  {   
    typedef typename std::iterator_traits< value_iterator >::value_type value_t;
    typedef typename std::iterator_traits< order_iterator >::value_type index_t;
    typedef typename std::iterator_traits< order_iterator >::difference_type diff_t;
    
    diff_t remaining = order_end - 1 - order_begin;
    for ( index_t s = index_t(), d; remaining > 0; ++ s ) {
        for ( d = order_begin[s]; d > s; d = order_begin[d] ) ;
        if ( d == s ) {
            -- remaining;
            value_t temp = v[s];
            while ( d = order_begin[d], d != s ) {
                swap( temp, v[d] );
                -- remaining;
            }
            v[s] = temp;
        }
    }
}

最后,为了彻底回答这个问题,有一种变体会破坏重新排序向量(用-1填充)。对于[0..10]的排列,它比前一个版本快约16%。因为覆盖输入可以实现动态编程,所以在某些具有更长序列的情况下渐近地更快,时间复杂度为O(N)。
template< typename order_iterator, typename value_iterator >
void reorder_destructive( order_iterator order_begin, order_iterator order_end, value_iterator v )  {
    typedef typename std::iterator_traits< value_iterator >::value_type value_t;
    typedef typename std::iterator_traits< order_iterator >::value_type index_t;
    typedef typename std::iterator_traits< order_iterator >::difference_type diff_t;
    
    diff_t remaining = order_end - 1 - order_begin;
    for ( index_t s = index_t(); remaining > 0; ++ s ) {
        index_t d = order_begin[s];
        if ( d == (diff_t) -1 ) continue;
        -- remaining;
        value_t temp = v[s];
        for ( index_t d2; d != s; d = d2 ) {
            swap( temp, v[d] );
            swap( order_begin[d], d2 = (diff_t) -1 );
            -- remaining;
        }
        v[s] = temp;
    }
}

3
@Potatoswatter:是的,这就是stackoverflow的工作方式...我从未真正理解为什么一个人会做(大部分的)编辑,感觉他们好像想阻止你改进你的答案一样... - Pieter
2
你有没有任何示例代码可以展示这个工作原理?我甚至无法使用gcc编译第二个版本。而第一个版本也不能正确地重新排序我的向量。 - Alec Jacobson
2
@mangledorf:http://ideone.com/TsWbu - 请注意,重新排序向量包含数据向量中每个对应元素的最终位置,这与选择哪个初始数据向量元素应出现在每个最终位置的重新排序向量不同。在这方面,OP示例存在歧义。我的代码可能可以调整为另一种方式工作,但我现在没有时间:v(。 - Potatoswatter
3
看起来这个算法(至少第二个版本)即使元素已经在其位置上,也会进行交换。因此,如果您使用vector顺序为012等进行调用,它实际上会进行大量的复制操作。 - Adam Badura
1
我不打算测试这个,但我非常确定针对未移动对象(单轨道)的优化是在第一段代码外循环的开头添加 if ( s == order[ s ] ) continue;,或者在另外两个代码中用 order_begin 替换 order - Potatoswatter
显示剩余8条评论

10

向量的原地重新排序

警告: 关于排序索引的语义含义存在歧义。两种含义均在此得到回答。

将向量元素移动到索引的位置

交互版本在这里

#include <iostream>
#include <vector>
#include <assert.h>

using namespace std;

void REORDER(vector<double>& vA, vector<size_t>& vOrder)  
{   
    assert(vA.size() == vOrder.size());

    // for all elements to put in place
    for( int i = 0; i < vA.size() - 1; ++i )
    { 
        // while the element i is not yet in place 
        while( i != vOrder[i] )
        {
            // swap it with the element at its final place
            int alt = vOrder[i];
            swap( vA[i], vA[alt] );
            swap( vOrder[i], vOrder[alt] );
        }
    }
}

int main()
{
    std::vector<double> vec {7, 5, 9, 6};
    std::vector<size_t> inds {1, 3,  0, 2};
    REORDER(vec, inds);
    for (size_t vv = 0; vv < vec.size(); ++vv)
    {
        std::cout << vec[vv] << std::endl;
    }
    return 0;
}

输出

9
7
6
5

请注意,您可以节省一个测试,因为如果n-1个元素已经就位,则最后第n个元素肯定在正确的位置。

退出时,vA和vOrder都已正确排序。

该算法最多执行n-1次交换,因为每次交换都将元素移动到其最终位置。我们必须对vOrder进行最多2N次测试。

从索引位置开始绘制向量中的元素

在此处进行互动尝试here

#include <iostream>
#include <vector>
#include <assert.h>

template<typename T>
void reorder(std::vector<T>& vec, std::vector<size_t> vOrder)
{
    assert(vec.size() == vOrder.size());
            
    for( size_t vv = 0; vv < vec.size() - 1; ++vv )
    {
            if (vOrder[vv] == vv)
            {
                continue;
            }
            size_t oo;
            for(oo = vv + 1; oo < vOrder.size(); ++oo)
            {
                if (vOrder[oo] == vv)
                {
                    break;
                }
            }
            std::swap( vec[vv], vec[vOrder[vv]] );
            std::swap( vOrder[vv], vOrder[oo] );
    }
}

int main()
{
    std::vector<double> vec {7, 5, 9, 6};
    std::vector<size_t> inds {1, 3,  0, 2};
    reorder(vec, inds);
    for (size_t vv = 0; vv < vec.size(); ++vv)
    {
        std::cout << vec[vv] << std::endl;
    }
    return 0;
}

输出

5
6
7
9

1
@dribeas 这不幸的是不正确的,但这也是我最初的想法。尝试使用序列2 3 1 0,你会发现1和0不会在正确的位置上。 - chmike
它出问题了,“vA [i]”最终包含一个循环中的最后一个索引“vA [k]”。 - Gregory Pakosz
2
抱歉更新了一个旧帖子,但这对我没有起作用。我不是检查和使用i和vOrder [i],而是必须使用vOrder [i]和vOrder [vOrder [i]]。我在下面添加了一个答案。 - rcgldr
为了澄清,我认为vOrder已经包含了按照所需顺序排列的一组索引(而不是复制重新创建vOrder所需的步骤),因此我添加了一个处理这种情况的答案。 - rcgldr
正如我在问题中所评论的,vOrder语义存在歧义。 - chmike
显示剩余3条评论

6
我觉得vOrder包含了按照所需顺序排序后的索引集合(例如按索引排序的输出)。此处的代码示例遵循vOrder中的“循环”,其中跟随子集(可以是vOrder的全部)的索引将循环遍历该子集,最终回到子集的第一个索引。
“循环”维基百科文章

https://en.wikipedia.org/wiki/Cyclic_permutation

在下面的例子中,每次交换都至少将一个元素放到它适当的位置。该代码示例有效地根据vOrder重新排序vA,同时将vOrder“取消排序”或“取消置换”回其原始状态(0 :: n-1)。如果vA按顺序包含值0到n-1,则重新排序后,vA将最终停留在vOrder开始的位置。
template <class T>
void reorder(vector<T>& vA, vector<size_t>& vOrder)  
{   
    assert(vA.size() == vOrder.size());

    // for all elements to put in place
    for( size_t i = 0; i < vA.size(); ++i )
    { 
        // while vOrder[i] is not yet in place 
        // every swap places at least one element in it's proper place
        while(       vOrder[i] !=   vOrder[vOrder[i]] )
        {
            swap( vA[vOrder[i]], vA[vOrder[vOrder[i]]] );
            swap(    vOrder[i],     vOrder[vOrder[i]] );
        }
    }
}

这种方法也可以更加高效地使用移动而不是交换来实现。在移动过程中需要一个临时对象来保存元素。以下是示例 C 代码,根据 I[] 中的索引重新排序 A[],同时对 I[] 进行排序:
void reorder(int *A, int *I, int n)
{    
int i, j, k;
int tA;
    /* reorder A according to I */
    /* every move puts an element into place */
    /* time complexity is O(n) */
    for(i = 0; i < n; i++){
        if(i != I[i]){
            tA = A[i];
            j = i;
            while(i != (k = I[j])){
                A[j] = A[k];
                I[j] = j;
                j = k;
            }
            A[j] = tA;
            I[j] = j;
        }
    }
}

sizeof(A) 是 sizeof(int*),而 sizeof(A[0]) 是 sizeof(int)。 - QuentinUK
@QuentinUK - 答案现在已经修复。 - rcgldr

3

如果可以修改ORDER数组,那么一种实现方法是对ORDER向量进行排序,并在每次排序操作时交换相应的值向量元素,我认为这样可以解决问题。


3

现有答案的调查

您问是否有“更有效的方法”。但是,您所说的有效率是什么意思,您的要求是什么?

Potatoswatter的答案O(N²)时间内使用O(1)附加空间,并且改变重新排序向量。

chmikercgldr给出的答案使用O(N)时间和O(1)附加空间,但是它们通过改变重新排序向量来实现这一点。

您最初的答案分配了新空间,然后将数据复制到其中,而Tim MB建议使用移动语义。然而,移动仍然需要一个地方来移动事物,并且像std::string这样的对象具有长度变量和指针。换句话说,基于移动的解决方案对于任何对象都需要O(N)分配,并且对于新向量本身只需要O(1)分配。我将在下面解释为什么这很重要。

保留重新排序向量

我们可能需要那个重新排序向量!排序成本为O(N log N)。但是,如果您知道将以相同方式对多个向量进行排序,例如在数组结构(SoA)上下文中,您可以排序一次,然后重复使用结果。这可以节省大量时间。

您可能还想对数据进行排序,然后取消排序。有了重新排序向量,您可以这样做。一个用例在于在GPU上执行基因组测序,其中通过将类似长度的序列分批处理来获得最大速度效率。我们不能依赖用户按此顺序提供序列,因此我们进行排序,然后取消排序。

那么,如果我们想要所有世界的最佳状态:O(N)处理而没有附加分配的成本,但也没有改变我们的排序向量(毕竟我们可能想要重复使用它),该怎么办?要找到那个世界,我们需要问:

为什么额外空间不好?

您可能不想分配额外空间的原因有两个。

第一个是您没有太多可用的空间。这可能发生在两种情况下:您正在使用具有有限内存的嵌入式设备。通常,这意味着您正在处理小型数据集,因此O(N²)解决方案在这里可能是可以接受的。但是,当您处理真正大型数据集时,这种情况是不可接受的,并且必须使用其中一个O(N)变异解决方案。

另一个额外空间不好的原因是因为分配很昂贵。对于较小的数据集,它可能比实际计算成本更高。因此,实现效率的一种方法是消除分配。
大纲
当我们改变排序向量时,我们这样做是为了指示元素是否处于其排列位置。我们可以使用位向量来表示相同的信息。但是,如果我们每次分配位向量都会很昂贵。
相反,我们可以通过将其重置为零来每次清除位向量。然而,每个函数使用会产生额外的O(N)成本。
相反,我们可以在向量中存储“版本”值,并在每个函数使用时递增该值。这为我们提供了O(1)访问、O(1)清除和平均分配成本。这类似于持久数据结构。缺点是如果我们过于频繁地使用排序函数,则需要重置版本计数器,尽管这样做的O(N)成本是摊销的。
这引出了一个问题:版本向量的最佳数据类型是什么?位向量最大化了缓存利用率,但每次使用后需要进行完整的O(N)重置。64位数据类型可能永远不需要重置,但缓存利用率较差。实验是找出答案的最佳方法。
两种排列类型
我们可以将排序向量视为具有两种意义:正向和反向。在正向意义上,向量告诉我们元素去哪里。在反向意义上,向量告诉我们元素来自哪里。由于排序向量隐式地是一个链接列表,所以反向意义需要O(N)额外空间,但是我们可以摊销分配成本。连续应用两个意义会将我们带回原始排序。
表现
在我的“Intel(R) Xeon(R) E-2176M CPU @ 2.70GHz”上单线程运行时,以下代码对长度为32,767的序列每次重新排序约需0.81毫秒。
代码
两种意义的完全注释代码及其测试:
#include <algorithm>
#include <cassert>
#include <random>
#include <stack>
#include <stdexcept>
#include <vector>

///@brief Reorder a vector by moving its elements to indices indicted by another 
///       vector. Takes O(N) time and O(N) space. Allocations are amoritzed.
///
///@param[in,out] values   Vector to be reordered
///@param[in]     ordering A permutation of the vector
///@param[in,out] visited  A black-box vector to be reused between calls and
///                        shared with with `backward_reorder()`
template<class ValueType, class OrderingType, class ProgressType>
void forward_reorder(
  std::vector<ValueType>          &values,
  const std::vector<OrderingType> &ordering,
  std::vector<ProgressType>       &visited
){
  if(ordering.size()!=values.size()){
    throw std::runtime_error("ordering and values must be the same size!");
  }

  //Size the visited vector appropriately. Since vectors don't shrink, this will
  //shortly become large enough to handle most of the inputs. The vector is 1
  //larger than necessary because the first element is special.
  if(visited.empty() || visited.size()-1<values.size());
    visited.resize(values.size()+1);

  //If the visitation indicator becomes too large, we reset everything. This is
  //O(N) expensive, but unlikely to occur in most use cases if an appropriate
  //data type is chosen for the visited vector. For instance, an unsigned 32-bit
  //integer provides ~4B uses before it needs to be reset. We subtract one below
  //to avoid having to think too much about off-by-one errors. Note that
  //choosing the biggest data type possible is not necessarily a good idea!
  //Smaller data types will have better cache utilization.
  if(visited.at(0)==std::numeric_limits<ProgressType>::max()-1)
    std::fill(visited.begin(), visited.end(), 0);

  //We increment the stored visited indicator and make a note of the result. Any
  //value in the visited vector less than `visited_indicator` has not been
  //visited.
  const auto visited_indicator = ++visited.at(0);

  //For doing an early exit if we get everything in place
  auto remaining = values.size();

  //For all elements that need to be placed
  for(size_t s=0;s<ordering.size() && remaining>0;s++){
    assert(visited[s+1]<=visited_indicator);

    //Ignore already-visited elements
    if(visited[s+1]==visited_indicator)
      continue;

    //Don't rearrange if we don't have to
    if(s==visited[s])
      continue;

    //Follow this cycle, putting elements in their places until we get back
    //around. Use move semantics for speed.
    auto temp = std::move(values[s]);
    auto i = s;
    for(;s!=(size_t)ordering[i];i=ordering[i],--remaining){
      std::swap(temp, values[ordering[i]]);
      visited[i+1] = visited_indicator;
    }
    std::swap(temp, values[s]);
    visited[i+1] = visited_indicator;
  }
}



///@brief Reorder a vector by moving its elements to indices indicted by another 
///       vector. Takes O(2N) time and O(2N) space. Allocations are amoritzed.
///
///@param[in,out] values   Vector to be reordered
///@param[in]     ordering A permutation of the vector
///@param[in,out] visited  A black-box vector to be reused between calls and
///                        shared with with `forward_reorder()`
template<class ValueType, class OrderingType, class ProgressType>
void backward_reorder(
  std::vector<ValueType>          &values,
  const std::vector<OrderingType> &ordering,
  std::vector<ProgressType>       &visited
){
  //The orderings form a linked list. We need O(N) memory to reverse a linked
  //list. We use `thread_local` so that the function is reentrant.
  thread_local std::stack<OrderingType> stack;

  if(ordering.size()!=values.size()){
    throw std::runtime_error("ordering and values must be the same size!");
  }

  //Size the visited vector appropriately. Since vectors don't shrink, this will
  //shortly become large enough to handle most of the inputs. The vector is 1
  //larger than necessary because the first element is special.
  if(visited.empty() || visited.size()-1<values.size());
    visited.resize(values.size()+1);

  //If the visitation indicator becomes too large, we reset everything. This is
  //O(N) expensive, but unlikely to occur in most use cases if an appropriate
  //data type is chosen for the visited vector. For instance, an unsigned 32-bit
  //integer provides ~4B uses before it needs to be reset. We subtract one below
  //to avoid having to think too much about off-by-one errors. Note that
  //choosing the biggest data type possible is not necessarily a good idea!
  //Smaller data types will have better cache utilization.
  if(visited.at(0)==std::numeric_limits<ProgressType>::max()-1)
    std::fill(visited.begin(), visited.end(), 0);

  //We increment the stored visited indicator and make a note of the result. Any
  //value in the visited vector less than `visited_indicator` has not been
  //visited.  
  const auto visited_indicator = ++visited.at(0);

  //For doing an early exit if we get everything in place
  auto remaining = values.size();

  //For all elements that need to be placed
  for(size_t s=0;s<ordering.size() && remaining>0;s++){
    assert(visited[s+1]<=visited_indicator);

    //Ignore already-visited elements
    if(visited[s+1]==visited_indicator)
      continue;

    //Don't rearrange if we don't have to
    if(s==visited[s])
      continue;

    //The orderings form a linked list. We need to follow that list to its end
    //in order to reverse it.
    stack.emplace(s);
    for(auto i=s;s!=(size_t)ordering[i];i=ordering[i]){
      stack.emplace(ordering[i]);
    }

    //Now we follow the linked list in reverse to its beginning, putting
    //elements in their places. Use move semantics for speed.
    auto temp = std::move(values[s]);
    while(!stack.empty()){
      std::swap(temp, values[stack.top()]);
      visited[stack.top()+1] = visited_indicator;
      stack.pop();
      --remaining;
    }
    visited[s+1] = visited_indicator;
  }
}



int main(){
  std::mt19937 gen;
  std::uniform_int_distribution<short> value_dist(0,std::numeric_limits<short>::max());
  std::uniform_int_distribution<short> len_dist  (0,std::numeric_limits<short>::max());
  std::vector<short> data;
  std::vector<short> ordering;
  std::vector<short> original;

  std::vector<size_t> progress;

  for(int i=0;i<1000;i++){
    const int len = len_dist(gen);
    data.clear();
    ordering.clear();
    for(int i=0;i<len;i++){
      data.push_back(value_dist(gen));
      ordering.push_back(i);
    }

    original = data;

    std::shuffle(ordering.begin(), ordering.end(), gen);

    forward_reorder(data, ordering, progress);

    assert(original!=data);

    backward_reorder(data, ordering, progress);

    assert(original==data);
  }  
}

1

在99.9%的情况下,更简单的答案将满足您的需求,但使用O(1)空间要求进行重新排序是一个有趣的智力练习:

void permute(vector<T>& values, const vector<size_t>& indices)  
{   
    vector<T> out;
    out.reserve(indices.size());
    for(size_t index: indices)
    {
        assert(0 <= index && index < values.size());
        out.push_back(std::move(values[index]));
    }
    values = std::move(out);
}

除了内存要求之外,我能想到这个过程变慢的唯一原因是由于 out 的内存与 valuesindices 不在同一个缓存页中。

1

不要过早地进行优化。先进行测量,然后确定需要在哪些地方进行优化以及如何进行优化。否则,你可能会得到复杂的代码,难以维护,并且在许多性能不是问题的地方容易出现错误。

话虽如此,也不要过早地进行悲观优化。在不改变代码的情况下,你可以删除一半的副本:

    template <typename T>
    void reorder( std::vector<T> & data, std::vector<std::size_t> const & order )
    {
       std::vector<T> tmp;         // create an empty vector
       tmp.reserve( data.size() ); // ensure memory and avoid moves in the vector
       for ( std::size_t i = 0; i < order.size(); ++i ) {
          tmp.push_back( data[order[i]] );
       }
       data.swap( tmp );          // swap vector contents
    }

这段代码创建一个足够大的空向量,并按顺序执行单个副本。最后,有序的原始向量会被交换。这将减少复制,但仍需要额外的内存。

如果您想要进行原地移动, 一个简单的算法可以是:

template <typename T>
void reorder( std::vector<T> & data, std::vector<std::size_t> const & order )
{
   for ( std::size_t i = 0; i < order.size(); ++i ) {
      std::size_t original = order[i];
      while ( i < original )  {
         original = order[original];
      }
      std::swap( data[i], data[original] );
   }
}

应该检查和调试此代码。简而言之,每个步骤中的算法将元素定位于第i个位置。首先,我们确定原始元素在数据向量中现在放置在哪个位置。如果原始位置已经被算法触及(它在第i个位置之前),则原始元素被交换到order[original]位置。那么,该元素可能已经移动过...

这个算法在整数操作的数量上大致为O(N^2),因此理论上比最初的O(N)算法的性能时间更差。但如果N^2的交换操作(最坏情况)比N个复制操作便宜,或者如果您真的受到内存占用的限制,则可以弥补它。


你的算法是最优的,如果vOrder不能被修改或者N很小的话。你的算法可能会花费更多时间扫描vOrder,但只会交换vA的值。我的算法交换vOrder和vA的值,这可能比使用小的N值重新扫描vOrder更昂贵。 - chmike
哦,'tmp.resize(data.size());' 应该改为 'tmp.reserve(...)',因为你使用的是 'tmp.push_back(...)'。如果要保留'resize',则插入应为'tmp[i] = ...'。 - rotoglup

0
我想出了这个解决方案,其空间复杂度为O(max_val - min_val + 1),但它可以与std::sort集成,并受益于std::sort时间复杂度O(n log n)
std::vector<int32_t> dense_vec = {1, 2, 3};
std::vector<int32_t> order = {1, 0, 2};

int32_t max_val = *std::max_element(dense_vec.begin(), dense_vec.end());
std::vector<int32_t> sparse_vec(max_val + 1);

int32_t i = 0;
for(int32_t j: dense_vec)
{
    sparse_vec[j] = order[i];
    i++;
}

std::sort(dense_vec.begin(), dense_vec.end(),
    [&sparse_vec](int32_t i1, int32_t i2) {return sparse_vec[i1] < sparse_vec[i2];});

编写此代码时做出以下假设:

  • 向量值从零开始。
  • 向量不包含重复的值。
  • 我们有足够的内存可以牺牲,以便使用std::sort

0

我想你可以用递归的方式来实现 - 大概是这样的(未经检查,但可以给出思路):

// Recursive function
template<typename T>
void REORDER(int oldPosition, vector<T>& vA, 
             const vector<int>& vecNewOrder, vector<bool>& vecVisited)
{
    // Keep a record of the value currently in that position,
    // as well as the position we're moving it to.
    // But don't move it yet, or we'll overwrite whatever's at the next
    // position. Instead, we first move what's at the next position.
    // To guard against loops, we look at vecVisited, and set it to true
    // once we've visited a position.
    T oldVal = vA[oldPosition];
    int newPos = vecNewOrder[oldPosition];
    if (vecVisited[oldPosition])
    {
        // We've hit a loop. Set it and return.
        vA[newPosition] = oldVal;
        return;
    }
    // Guard against loops:
    vecVisited[oldPosition] = true;

    // Recursively re-order the next item in the sequence.
    REORDER(newPos, vA, vecNewOrder, vecVisited);

    // And, after we've set this new value, 
    vA[newPosition] = oldVal;
}

// The "main" function
template<typename T>
void REORDER(vector<T>& vA, const vector<int>& newOrder)
{
    // Initialise vecVisited with false values
    vector<bool> vecVisited(vA.size(), false);

    for (int x = 0; x < vA.size(); x++)
    {
        REORDER(x, vA, newOrder, vecVisited);
    }
}

当然,你需要考虑vecVisited的开销。对于这种方法,有什么想法吗?


在第一次阅读时,我觉得这大致会导致相同的内存和时间成本。虽然你没有复制到另一个向量,但你要复制到和向量中元素一样多的局部变量。我需要更努力地进行解释来测试其正确性。 - David Rodríguez - dribeas
是的,那也是我的想法。实际上,由于堆栈帧,它可能会使用更多的空间,并且由于方法调用开销,需要更多的时间。但无论如何,这还是一个有趣的智力锻炼;-P - Smashery

0

你的代码出问题了。你不能给vA赋值,而且你需要使用模板参数。

vector<char> REORDER(const vector<char>& vA, const vector<size_t>& vOrder)  
{   
    assert(vA.size() == vOrder.size());  
    vector<char> vCopy(vA.size()); 
    for(int i = 0; i < vOrder.size(); ++i)  
        vCopy[i] = vA[ vOrder[i] ];  
    return vA;
} 

以上代码略微更加高效。


1
OP想要重新排序vA向量。因此原型应为:void REORDER(vector<char>& vA,const vector<size_t>& vOrder)。 - Donotalo
1
返回一个向量将导致必须创建副本的结果,根据我的理解,这是 OP 想要避免的。你的方法的另一个优化是不创建大小元素的向量,而是保留内存并使用 push_backs。这将消除在复制之前默认构造所有元素的成本。 - David Rodríguez - dribeas

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接