C ++ сортировка и отслеживание индексов


216

Используя C ++ и, надеюсь, стандартную библиотеку, я хочу отсортировать последовательность выборок в порядке возрастания, но я также хочу запомнить исходные индексы новых выборок.

Например, у меня есть набор или вектор, или матрица образцов A : [5, 2, 1, 4, 3]. Я хочу отсортировать их так, чтобы они были B : [1,2,3,4,5], но я также хочу запомнить исходные индексы значений, чтобы я мог получить другой набор, который будет: C : [2, 1, 4, 3, 0 ]- который соответствует индексу каждого элемента в 'B', в оригинале ' А».

Например, в Matlab вы можете сделать:

 [a,b]=sort([5, 8, 7])
 a = 5 7 8
 b = 1 3 2

Кто-нибудь может увидеть хороший способ сделать это?

Ответы:


298

Используя C++11 лямбд:

#include <iostream>
#include <vector>
#include <numeric>      // std::iota
#include <algorithm>    // std::sort, std::stable_sort

using namespace std;

template <typename T>
vector<size_t> sort_indexes(const vector<T> &v) {

  // initialize original index locations
  vector<size_t> idx(v.size());
  iota(idx.begin(), idx.end(), 0);

  // sort indexes based on comparing values in v
  // using std::stable_sort instead of std::sort
  // to avoid unnecessary index re-orderings
  // when v contains elements of equal values 
  stable_sort(idx.begin(), idx.end(),
       [&v](size_t i1, size_t i2) {return v[i1] < v[i2];});

  return idx;
}

Теперь вы можете использовать возвращенный индексный вектор в итерациях, таких как

for (auto i: sort_indexes(v)) {
  cout << v[i] << endl;
}

Вы также можете указать исходный вектор индекса, функцию сортировки, компаратор или автоматически изменить порядок v в функции sort_indexes, используя дополнительный вектор.


4
Понравился этот ответ. Если ваш компилятор не поддерживает лямбда-выражения, вы можете использовать класс: template <typename T> class CompareIndicesByAnotherVectorValues ​​{std :: vector <T> * _values; public: CompareIndicesByAnotherVectorValues ​​(std :: vector <T> * values): _values ​​(значения) {} public: bool operator () (const int & a, const int & b) const {return ( _values) [a]> ( _values) [ Ь]; }};
Йоав

2
Мне тоже нравится этот ответ, нет необходимости копировать исходный вектор для создания вектора пар.
Headmyshoulder

29
Вместо ручной работы for (size_t i = 0; i != idx.size(); ++i) idx[i] = i;я предпочитаю стандартstd::iota( idx.begin(), idx.end(), 0 );
Wyck

6
использовать #include <numeric>для йота ()
kartikag01

6
iotaнаименее очевидно названный алгоритм из всей стандартной библиотеки C ++.
Сет Джонсон

87

Вы можете отсортировать std :: pair вместо просто int: первый int - исходные данные, второй int - исходный индекс. Затем укажите компаратор, который сортирует только по первому целому. Пример:

Your problem instance: v = [5 7 8]
New problem instance: v_prime = [<5,0>, <8,1>, <7,2>]

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

typedef std::pair<int,int> mypair;
bool comparator ( const mypair& l, const mypair& r)
   { return l.first < r.first; }
// forgetting the syntax here but intent is clear enough

Результат std :: sort для v_prime с использованием этого компаратора должен быть:

v_prime = [<5,0>, <7,2>, <8,1>]

Вы можете очистить индексы, пройдя вектор, захватывая .second из каждой пары std ::.


1
Именно так я и поступил бы. Основная функция сортировки не отслеживает старые и новые позиции, так как это добавило бы лишние ненужные накладные расходы.
the_mandrill

8
Недостаток этой функции в том, что она требует перераспределения памяти для всех значений.
Йоав

1
Это, очевидно, работоспособный подход, но у него есть недостаток, заключающийся в том, что вы должны изменить исходный контейнер с «контейнера чисел» на «контейнер пар».
Руслан

19

Предположим, что данный вектор

A=[2,4,3]

Создать новый вектор

V=[0,1,2] // indicating positions

Сортировка V и, сравнивая вместо сравнения элементов V, сравнивают соответствующие элементы A

 //Assume A is a given vector with N elements
 vector<int> V(N);
 int x=0;
 std::iota(V.begin(),V.end(),x++); //Initializing
 sort( V.begin(),V.end(), [&](int i,int j){return A[i]<A[j];} );

Люблю твой ответ. Вы даже можете использовать std::iota()для более изящной инициализацииmap
Нимрод Мораг

Да, мы можем использовать это! Спасибо за предложение
MysticForce

12

Я написал общую версию индекса сортировки.

template <class RAIter, class Compare>
void argsort(RAIter iterBegin, RAIter iterEnd, Compare comp, 
    std::vector<size_t>& indexes) {

    std::vector< std::pair<size_t,RAIter> > pv ;
    pv.reserve(iterEnd - iterBegin) ;

    RAIter iter ;
    size_t k ;
    for (iter = iterBegin, k = 0 ; iter != iterEnd ; iter++, k++) {
        pv.push_back( std::pair<int,RAIter>(k,iter) ) ;
    }

    std::sort(pv.begin(), pv.end(), 
        [&comp](const std::pair<size_t,RAIter>& a, const std::pair<size_t,RAIter>& b) -> bool 
        { return comp(*a.second, *b.second) ; }) ;

    indexes.resize(pv.size()) ;
    std::transform(pv.begin(), pv.end(), indexes.begin(), 
        [](const std::pair<size_t,RAIter>& a) -> size_t { return a.first ; }) ;
}

Использование такое же, как и в std :: sort, за исключением контейнера индекса для получения отсортированных индексов. тестирование:

int a[] = { 3, 1, 0, 4 } ;
std::vector<size_t> indexes ;
argsort(a, a + sizeof(a) / sizeof(a[0]), std::less<int>(), indexes) ;
for (size_t i : indexes) printf("%d\n", int(i)) ;

вы должны получить 2 1 0 3. для компиляторов без поддержки c ++ 0x, замените выражение lamba как шаблон класса:

template <class RAIter, class Compare> 
class PairComp {
public:
  Compare comp ;
  PairComp(Compare comp_) : comp(comp_) {}
  bool operator() (const std::pair<size_t,RAIter>& a, 
    const std::pair<size_t,RAIter>& b) const { return comp(*a.second, *b.second) ; }        
} ;

и переписать std :: sort как

std::sort(pv.begin(), pv.end(), PairComp(comp)()) ;

Привет, хки! Как нам создать экземпляр этой шаблонной функции? Он имеет два типовых имени шаблона, и один из них является итератором, что делает эту ситуацию очень редкой. Не могли бы вы помочь?
Скотт Ян

12
vector<pair<int,int> >a;

for (i = 0 ;i < n ; i++) {
    // filling the original array
    cin >> k;
    a.push_back (make_pair (k,i)); // k = value, i = original index
}

sort (a.begin(),a.end());

for (i = 0 ; i < n ; i++){
    cout << a[i].first << " " << a[i].second << "\n";
}

Теперь aсодержит как наши значения, так и соответствующие им индексы в отсортированном виде.

a[i].first = valueв i«ом.

a[i].second = idx в исходном массиве.


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

На самом деле мне больше всего нравится это решение - мой вектор имеет размер 4 или около того, и я застрял до C ++ 11 и не могу использовать лямбды. Спасибо Адитья Асвал.
stephanmg

6

Я столкнулся с этим вопросом и понял, что сортировка итераторов напрямую будет способом сортировки значений и отслеживания индексов; Нет необходимости определять дополнительный контейнер pairs of (value, index), который полезен, когда значения являются большими объектами; Итераторы обеспечивают доступ как к значению, так и к индексу:

/*
 * a function object that allows to compare
 * the iterators by the value they point to
 */
template < class RAIter, class Compare >
class IterSortComp
{
    public:
        IterSortComp ( Compare comp ): m_comp ( comp ) { }
        inline bool operator( ) ( const RAIter & i, const RAIter & j ) const
        {
            return m_comp ( * i, * j );
        }
    private:
        const Compare m_comp;
};

template <class INIter, class RAIter, class Compare>
void itersort ( INIter first, INIter last, std::vector < RAIter > & idx, Compare comp )
{ 
    idx.resize ( std::distance ( first, last ) );
    for ( typename std::vector < RAIter >::iterator j = idx.begin( ); first != last; ++ j, ++ first )
        * j = first;

    std::sort ( idx.begin( ), idx.end( ), IterSortComp< RAIter, Compare > ( comp ) );
}

как для примера использования:

std::vector < int > A ( n );

// populate A with some random values
std::generate ( A.begin( ), A.end( ), rand );

std::vector < std::vector < int >::const_iterator > idx;
itersort ( A.begin( ), A.end( ), idx, std::less < int > ( ) );

теперь, например, 5-й наименьший элемент в отсортированном векторе будет иметь значение, **idx[ 5 ]а его индекс в исходном векторе будет distance( A.begin( ), *idx[ 5 ] )или просто *idx[ 5 ] - A.begin( ).


3

Есть еще один способ решить эту проблему, используя карту:

vector<double> v = {...}; // input data
map<double, unsigned> m; // mapping from value to its index
for (auto it = v.begin(); it != v.end(); ++it)
    m[*it] = it - v.begin();

Это уничтожит неуникальные элементы. Если это не приемлемо, используйте мультикарту:

vector<double> v = {...}; // input data
multimap<double, unsigned> m; // mapping from value to its index
for (auto it = v.begin(); it != v.end(); ++it)
    m.insert(make_pair(*it, it - v.begin()));

Чтобы вывести индексы, переберите карту или мультикарту:

for (auto it = m.begin(); it != m.end(); ++it)
    cout << it->second << endl;

3

Прекрасное решение от @Lukasz Wiklendt! Хотя в моем случае мне нужно что-то более общее, поэтому я немного изменил:

template <class RAIter, class Compare>
vector<size_t> argSort(RAIter first, RAIter last, Compare comp) {

  vector<size_t> idx(last-first);
  iota(idx.begin(), idx.end(), 0);

  auto idxComp = [&first,comp](size_t i1, size_t i2) {
      return comp(first[i1], first[i2]);
  };

  sort(idx.begin(), idx.end(), idxComp);

  return idx;
}

Пример: Найти индексы, сортирующие вектор строк по длине, за исключением первого элемента, который является фиктивным.

vector<string> test = {"dummy", "a", "abc", "ab"};

auto comp = [](const string &a, const string& b) {
    return a.length() > b.length();
};

const auto& beginIt = test.begin() + 1;
vector<size_t> ind = argSort(beginIt, test.end(), comp);

for(auto i : ind)
    cout << beginIt[i] << endl;

печатает:

abc
ab
a

3

Рассмотрите возможность использования std::multimapв соответствии с предложением @Ulrich Eckhardt. Просто код можно сделать еще проще.

Дано

std::vector<int> a = {5, 2, 1, 4, 3};  // a: 5 2 1 4 3

Сортировать в среднем времени вставки

std::multimap<int, std::size_t> mm;
for (std::size_t i = 0; i != a.size(); ++i)
    mm.insert({a[i], i});

Чтобы получить значения и оригинальные индексы

std::vector<int> b;
std::vector<std::size_t> c;
for (const auto & kv : mm) {
    b.push_back(kv.first);             // b: 1 2 3 4 5
    c.push_back(kv.second);            // c: 2 1 4 3 0
}

Причина предпочитают std::multimapк std::map, чтобы позволить одинаковые значения в исходных векторов. Также обратите внимание, что, в отличие от std::map, operator[]не определено для std::multimap.


2

Сделайте функцию std::pairin, затем сортируйте пару:

общая версия:

template< class RandomAccessIterator,class Compare >
auto sort2(RandomAccessIterator begin,RandomAccessIterator end,Compare cmp) ->
   std::vector<std::pair<std::uint32_t,RandomAccessIterator>>
{
    using valueType=typename std::iterator_traits<RandomAccessIterator>::value_type;
    using Pair=std::pair<std::uint32_t,RandomAccessIterator>;

    std::vector<Pair> index_pair;
    index_pair.reserve(std::distance(begin,end));

    for(uint32_t idx=0;begin!=end;++begin,++idx){
        index_pair.push_back(Pair(idx,begin));
    }

    std::sort( index_pair.begin(),index_pair.end(),[&](const Pair& lhs,const Pair& rhs){
          return cmp(*lhs.second,*rhs.second);
    });

    return index_pair;
}

ideone


1

Являются ли элементы в векторе уникальными? Если это так, скопируйте вектор, отсортируйте одну из копий с помощью STL Sort, и вы сможете найти, какой индекс имел каждый элемент в исходном векторе.

Если вектор должен обрабатывать дубликаты, я думаю, вам лучше реализовать собственную процедуру сортировки.


1

Ну, мое решение использует технику остатка. Мы можем поместить значения при сортировке в верхние 2 байта, а индексы элементов - в нижние 2 байта:

int myints[] = {32,71,12,45,26,80,53,33};

for (int i = 0; i < 8; i++)
   myints[i] = myints[i]*(1 << 16) + i;

Затем отсортируйте массив myintsкак обычно:

std::vector<int> myvector(myints, myints+8);
sort(myvector.begin(), myvector.begin()+8, std::less<int>());

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

for (std::vector<int>::iterator it = myvector.begin(); it != myvector.end(); ++it)
   std::cout << ' ' << (*it)%(1 << 16);

Конечно, этот метод работает только для относительно небольших значений в исходном массиве myints(то есть тех, которые могут вмещаться в верхние 2 байта int). Но у этого есть дополнительное преимущество различения идентичных значений myints: их индексы будут напечатаны в правильном порядке.


1

Если это возможно, вы можете построить массив позиций с помощью функции find, а затем отсортировать массив.

Или, может быть, вы можете использовать карту, где ключом будет элемент, а значения - список его позиций в следующих массивах (A, B и C)

Это зависит от последующего использования этих массивов.


0

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

input array=>a
duplicate array=>b
vector=>c(Stores the indices(position) of the orignal array
Syntax:
for(i=0;i<n;i++)
c.push_back(binarysearch(b,n,a[i]));`

Здесь binarysearch - это функция, которая принимает массив, размер массива, элемент поиска и возвращает позицию искомого элемента.


-1

Есть много способов. Довольно простое решение - использовать 2D вектор.

#include <algorithm>
#include <iostream>
#include <vector>
using namespace std;

int main() {
 vector<vector<double>> val_and_id;
 val_and_id.resize(5);
 for (int i = 0; i < 5; i++) {
   val_and_id[i].resize(2); // one to store value, the other for index.
 }
 // Store value in dimension 1, and index in the other:
 // say values are 5,4,7,1,3.
 val_and_id[0][0] = 5.0;
 val_and_id[1][0] = 4.0;
 val_and_id[2][0] = 7.0;
 val_and_id[3][0] = 1.0;
 val_and_id[4][0] = 3.0;

 val_and_id[0][1] = 0.0;
 val_and_id[1][1] = 1.0;
 val_and_id[2][1] = 2.0;
 val_and_id[3][1] = 3.0;
 val_and_id[4][1] = 4.0;

 sort(val_and_id.begin(), val_and_id.end());
 // display them:
 cout << "Index \t" << "Value \n";
 for (int i = 0; i < 5; i++) {
  cout << val_and_id[i][1] << "\t" << val_and_id[i][0] << "\n";
 }
 return 0;
}

Вот вывод:

   Index   Value
   3       1
   4       3
   1       4
   0       5
   2       7
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.