Изменение порядка векторов с использованием вектора индексов - PullRequest
32 голосов
/ 08 мая 2009

Я хотел бы изменить порядок элементов в векторе, используя другой вектор для указания порядка:

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 ()?

Ответы [ 13 ]

26 голосов
/ 12 августа 2009

Я улучшил алгоритм 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)!), Потому что он делает все перестановки как можно раньше, а затем возвращается. Вектор переупорядочения состоит из ряда орбит, и каждая орбита переупорядочивается при достижении своего первого члена. Это быстрее, когда последние несколько элементов не содержат орбиты.

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! ~ = 40 млн. Перестановок из 11 символов за 4,25 секунды, не считая накладных расходов, на моем ноутбуке с частотой 2,2 ГГц.

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;
    }
}
4 голосов
/ 05 марта 2014

Мне кажется, что vOrder содержит набор индексов в нужном порядке (например, вывод сортировки по индексу). Пример кода здесь следует за «циклами» в vOrder, где после поднабора (может быть все из vOrder) индексов будет циклически проходить через подмножество, заканчиваясь на первом индексе поднабора.

Вики статья о "циклах"

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

В следующем примере каждый своп помещает хотя бы один элемент на свое место. Этот пример кода эффективно переупорядочивает vA в соответствии с vOrder, при этом «разупорядочивая» или «не переставляя» 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, переупорядочивает A [] в соответствии с индексами в I [], также сортирует 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 голосов
/ 08 мая 2009

Вот правильный код

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-й элемент, безусловно, на месте.

На выходе vA и vOrder правильно упорядочены.

Этот алгоритм выполняет самое большее n-1 обмен, потому что каждый обмен перемещает элемент в его окончательное положение. И нам нужно будет сделать не более 2N тестов на vOrder.

3 голосов
/ 08 мая 2009

Если можно изменить массив ORDER, то реализация, которая сортирует вектор ORDER и при каждой операции сортировки также меняет местами соответствующие значения векторных элементов, может быть, на мой взгляд.

1 голос
/ 08 мая 2009

Никогда не оптимизировать преждевременно. Измерить, а затем определить, где нужно оптимизировать и что. Вы можете закончить сложным кодом, который трудно поддерживать и подвержен ошибкам во многих местах, где производительность не является проблемой.

С этим, как говорится, не пессимизировать рано. Без изменения кода вы можете удалить половину своих копий:

    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-й позицией), то исходный элемент менялся местами в порядке [оригинальная] позиция. Опять же, этот элемент уже может быть перемещен ...

Этот алгоритм примерно равен O (N ^ 2) по числу целочисленных операций и, следовательно, теоретически хуже по времени выполнения по сравнению с исходным алгоритмом O (N). Но это может компенсировать, если операции обмена N ^ 2 (в худшем случае) стоят меньше, чем операции копирования N, или если вы действительно ограничены объемом памяти.

0 голосов
/ 20 августа 2018

Я придумал это решение, которое имеет космическую сложность 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 голосов
/ 27 сентября 2017

Это интересное интеллектуальное упражнение для переупорядочения с требованием места 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 голосов
/ 04 августа 2017

Я пытался использовать решение @ Potatoswatter, чтобы отсортировать несколько векторов по третьему, и был действительно смущен выходом из вышеперечисленных функций из вектора индексов, выведенного из 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 голосов
/ 27 января 2014

Из заголовка и вопроса неясно, нужно ли упорядочивать вектор с теми же шагами, что и для заказа 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]);
    }
  }
}

Если стоимость создания вашего объекта невелика и память не имеет значения (см. Dribeas):

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
}

Обратите внимание, что два кода в ответе на dribeas делают разные вещи.

0 голосов
/ 08 мая 2009

Следует избегать копирования вектора:

void REORDER(vector<char>& vA, const vector<size_t>& vOrder)  
{   
    assert(vA.size() == vOrder.size()); 
    for(int i = 0; i < vOrder.size(); ++i)
        if (i < vOrder[i])
            swap(vA[i], vA[vOrder[i]]);
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...