web-dev-qa-db-ja.com

インデックスのベクトルを使用してベクトルを並べ替える

別のベクターを使用して順序を指定し、ベクター内のアイテムを並べ替えたいと思います。

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

reorder_naive(vA, vOrder);
// 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()を使用するより効率的な方法はありますか?

35
Marc Eaddy

Chmikeのアルゴリズムを改善しました。この関数は、11個すべてについて彼と一致しています! (0..10)の順列が並べ替えベクトルとして渡されます。また、並べ替えベクトルは変更されません。

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)のほぼ2倍の速度です!)。並べ替えベクトルはいくつかの軌道で構成され、各軌道は最初のメンバーに到達すると並べ替えられます。最後のいくつかの要素に軌道が含まれていない場合は、より高速になります。

template< typename order_iterator, typename value_iterator >
void reorder( order_iterator order_begin, order_iterator order_end, value_iterator v )  {   
    typedef typename iterator_traits< value_iterator >::value_type value_t;
    typedef typename iterator_traits< order_iterator >::value_type index_t;
    typedef typename 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で埋めます。)前のバージョンよりも約16%高速です。これは醜いタイプキャストを使用していますが、それに対処します。これは11をカバーします! 〜= 2.2 GHzラップトップで、オーバーヘッドを除いて、4.25秒で11文字の40milの順列。

template< typename order_iterator, typename value_iterator >
void reorder_destructive( order_iterator order_begin, order_iterator order_end, value_iterator v )  {
    typedef typename iterator_traits< value_iterator >::value_type value_t;
    typedef typename iterator_traits< order_iterator >::value_type index_t;
    typedef typename 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;
    }
}
29
Potatoswatter

これが正しいコードです

void REORDER(vector<char>& 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] );
        }
    }
}

n-1個の要素が配置されている場合、最後のn番目の要素が確実に配置されているため、1つのテストを保存できることに注意してください。

終了時に、vAとvOrderは適切に順序付けられます。

このアルゴリズムは、各スワップが要素を最終位置に移動するため、最大でn-1のスワッピングを実行します。そして、vOrderで最大2Nのテストを実行する必要があります。

7
chmike

VOrderには、希望する順序でインデックスのセットが含まれているように見えます(たとえば、インデックスによる並べ替えの出力)。ここでのコード例は、vOrderの「サイクル」に従います。ここで、インデックスのサブセット(vOrderのすべてである可能性があります)をたどると、サブセットを循環し、サブセットの最初のインデックスで終了します。

「サイクル」に関するWiki記事

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

次の例では、すべてのスワップが少なくとも1つの要素を適切な場所に配置します。このコード例では、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 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 < sizeof(A)/sizeof(A[0]); 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;
        }
    }
}
4
rcgldr

ORDER配列を変更しても問題がない場合は、ORDERベクトルを並べ替え、各並べ替え操作で対応する値を交換する実装でも、ベクトル要素でうまくいくと思います。

3
andreas buykx

時期尚早に最適化しないでください。最適化する必要がある場所と内容を測定して決定します。パフォーマンスが問題にならない多くの場所では、保守が難しく、バグが発生しやすい複雑なコードで終わる可能性があります。

そうは言っても、早期に悲観的にしないでください。コードを変更せずに、コピーの半分を削除できます。

    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コピー操作よりもコストが低いか、メモリフットプリントによって実際に制約されている場合です。

O(1)スペース要件を使用して並べ替えを行うことは興味深い知的演習ですが、99.9%の場合、より簡単な答えがニーズに応えます。

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(values[index]);
    }
    values = std::move(out);
}

メモリ要件を超えて、これが遅いと私が考えることができる唯一の方法は、outのメモリがvaluesおよびindicesのメモリとは異なるキャッシュページにあるためです。

0
Tim MB

あなたはそれを再帰的に行うことができると思います-このようなもの(チェックされていませんが、それはアイデアを与えます):

// 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のオーバーヘッドはあります。このアプローチについての考え、誰か?

0
Smashery

タイトルと質問では、ベクターをvOrderの注文と同じ手順で注文する必要があるのか​​、それともvOrderに目的の注文のインデックスがすでに含まれているのかは明確ではありません。最初の解釈にはすでに満足のいく答えがあります(chmikeとPotatoswatterを参照)。後者についていくつか考えを追加します。オブジェクトTの作成および/またはコピーコストが関連する場合

template <typename T>
void reorder( std::vector<T> & data, std::vector<std::size_t> & order )
{
 std::size_t i,j,k;
  for(i = 0; i < order.size() - 1; ++i) {
    j = order[i];
    if(j != i) {
      for(k = i + 1; order[k] != i; ++k);
      std::swap(order[i],order[k]);
      std::swap(data[i],data[j]);
    }
  }
}

オブジェクトの作成コストが小さく、メモリが問題にならない場合(ドリビアを参照):

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
}

Dribeasanswerの2つのコードは異なることをすることに注意してください。

0
fortuna

あなたのコードは壊れています。 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;
} 

上記は少し効率的です。

0
dirkgently

ベクトルを反復処理するのはO(n)操作です。これに勝るものはありません。

0
JH.

@Potatoswatterのソリューションを使用して、複数のベクトルを3番目のベクトルで並べ替えようとしましたが、Armadilloのsort_indexから出力されたインデックスのベクトルで上記の関数を使用した出力によって本当に混乱しました。 sort_indexからのベクトル出力(以下のarma_indsベクトル)から@Potatoswatterのソリューションで使用できるもの(以下のnew_inds)に切り替えるには、次のようにします。

vector<int> new_inds(arma_inds.size());
for (int i = 0; i < new_inds.size(); i++) new_inds[arma_inds[i]] = i;
0
lune

スペースの複雑さ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
hmofrad