Сортування вектора у порядку зменшення


310

Чи варто використовувати

std::sort(numbers.begin(), numbers.end(), std::greater<int>());

або

std::sort(numbers.rbegin(), numbers.rend());   // note: reverse iterators

сортувати вектор у порядку зменшення? Чи є якісь переваги чи недоліки при одному чи іншому підході?


2
+1 Я думаю, що відповідь очевидна, але це запитання має цікаву дрібницю. :)
wilhelmtell

3
Я б проголосував за перший варіант, просто тому, що тоді мені більше не доведеться мати справу reverse_iterator.
evandrix

2
@wilhelmtell Питання щодо noob, але чому другий слід сортувати у порядку зменшення? Ми даємо той же масив, що і вхід до методу сортування. Просто ми надаємо його у зворотному порядку, тож чому його слід сортувати за низхідним, а не висхідним порядком, як це було б у справі ar.begin () та ar.end.
шшнк

6
@shshnk std::sort(b, e);ставить мінімум на b(у нашому випадку rbegin- останній елемент) і максимум на e(у нашому випадку rend- перший елемент).
fredoverflow

Відповіді:


114

Власне, перша - погана ідея. Використовуйте або другий , або цей:

struct greater
{
    template<class T>
    bool operator()(T const &a, T const &b) const { return a > b; }
};

std::sort(numbers.begin(), numbers.end(), greater());

Таким чином ваш код не мовчить зламатися, коли хтось вирішить, що він numbersповинен утримувати longабо long longзамість цього int.


1
@FredOverflow: Ви відзначилися у своєму коментарі;)
користувач541686

2
Або дотримуйтесь першого. Використовуйте typedef для numberContainer - хороша ідея, щоб хтось МОГУ мінявся на довгий довгий - і записуйте: std :: sort (numbers.begin (), numbers.end (), std :: більший <numContainer :: value_type> ( ));
RichardHowells

1
+1 Перший дійсно заплутаний. Що є greaterіншим? rbeginі rendбули зроблені з певною метою.
Абхішек Дівекар

6
Чому б не просто std::greater<typename decltype(numbers)::value_type>()чи щось?
einpoklum

1
Ця відповідь застаріла - її можна використовувати std::greater<>()з C ++ 14.
Микола

70

Скористайтеся першим:

std::sort(numbers.begin(), numbers.end(), std::greater<int>());

Це явно що відбувається - менше шансів спотворення , rbeginяк begin, навіть з коментарем. Це чітко і читабельно, що саме ви хочете.

Крім того, другий може бути менш ефективним, ніж перший, враховуючи характер зворотних ітераторів, хоча вам доведеться переглядати це, щоб бути впевненим.


68

За допомогою c ++ 14 ви можете це зробити:

std::sort(numbers.begin(), numbers.end(), std::greater<>());

30

Як що до цього?

std::sort(numbers.begin(), numbers.end());
std::reverse(numbers.begin(), numbers.end());

13
Причиною може стати уникнення додаткової складності: O (n * log (n)) + O (n) vs O (n * log (n))
greg

32
@greg O (n * log (n)) = O (n * log (n) + n). Вони є двома способами визначення одного і того ж набору. Ви хочете сказати "Це може бути повільніше".
pjvandehaar

4
@pjvandehaar Грег - це добре. Він явно не сказав: O (n * log (n) + n), він сказав O (n * log (n)) + O (n). Ви маєте рацію, що його формулювання незрозуміле (особливо його неправильне використання складності слова), але ви могли відповісти добрішим чином. Напр .: Можливо, ви мали намір використовувати слово "обчислення" замість слова "складність". Повернення чисел є непотрібним кроком O (n) до інакше однакового кроку O (n * log (n)).
Офек Гіла

3
@OfekGila Моє розуміння полягає в тому, що позначення big-O - це набір функцій, і позначення, що включають =і +є лише зручністю значення і . У цьому випадку, O(n*log(n)) + O(n)це зручне позначення, для O(n*log(n)) ∪ O(n)якого те саме, що O(n*log(n)). Слово "обчислення" - це гарна пропозиція, і ви маєте рацію щодо тону.
pjvandehaar

22

Замість функтора, як запропонував Мехрдад, ви можете використовувати функцію лямбда.

sort(numbers.begin(), numbers.end(), [](const int a, const int b) {return a > b; });

16

За моєю машиною, сортування long longвектора [1..3000000] за допомогою першого методу займає приблизно 4 секунди, тоді як використання другого займає приблизно вдвічі більше часу. Це, очевидно, щось говорить, але я не розумію, чому також. Просто подумайте, що це було б корисно.

Тут повідомляється те саме .

За словами Xeo, -O3вони використовують приблизно той самий час, щоб закінчити.


12
Ви, можливо, просто не компілювали з увімкненими оптимізаціями? Звучить дуже, що reverse_iteratorоперації не були накреслені, і враховуючи, що вони просто обгортка навколо фактичних ітераторів, недарма вони беруть вдвічі більше часу, не вкладаючи їх.
Xeo

@Xeo Навіть якщо вони були накреслені, деякі реалізації використовують додавання на дереференцію.
Паббі

@ildjarn: Тому що це так? Наприклад, base()функція-член повертає обернутий ітератор.
Ксео

1
@Xeo Тепер вони обоє закінчують за секунду. Дякую!
zw324

3
@Xeo: я беру його назад; фактично мандат, який std::vector<>::reverse_iteratorреалізується в умовах std::reverse_iterator<>. Химерність; сьогодні я дізнався. :-P
ildjarn

11

Перший підхід стосується:

    std::sort(numbers.begin(), numbers.end(), std::greater<>());

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


Це та сама відповідь, що і відповідь mrexciting. Зауваження про складність мені також незрозуміле.
Філіп Класен

7
bool comp(int i, int j) { return i > j; }
sort(numbers.begin(), numbers.end(), comp);

4
щоб бути правильною відповіддю, варто розглянути питання про переваги / недоліки
згаданих вами

3

TL; DR

Використовуйте будь-який. Вони майже однакові.

Нудна відповідь

Як завжди, є плюси і мінуси.

Використання std::reverse_iterator:

  • Коли ви сортуєте власні типи та не хочете їх реалізувати operator>()
  • Коли ти лінивий набрати std::greater<int>()

Використовувати, std::greaterколи:

  • Коли ви хочете мати більш явний код
  • Коли ви хочете уникати використання незрозумілих ітераторів зворотного ходу

Щодо продуктивності, обидва способи однаково ефективні. Я спробував наступний орієнтир:

#include <algorithm>
#include <chrono>
#include <iostream>
#include <fstream>
#include <vector>

using namespace std::chrono;

/* 64 Megabytes. */
#define VECTOR_SIZE (((1 << 20) * 64) / sizeof(int))
/* Number of elements to sort. */
#define SORT_SIZE 100000

int main(int argc, char **argv) {
    std::vector<int> vec;
    vec.resize(VECTOR_SIZE);

    /* We generate more data here, so the first SORT_SIZE elements are evicted
       from the cache. */
    std::ifstream urandom("/dev/urandom", std::ios::in | std::ifstream::binary);
    urandom.read((char*)vec.data(), vec.size() * sizeof(int));
    urandom.close();

    auto start = steady_clock::now();
#if USE_REVERSE_ITER
    auto it_rbegin = vec.rend() - SORT_SIZE;
    std::sort(it_rbegin, vec.rend());
#else
    auto it_end = vec.begin() + SORT_SIZE;
    std::sort(vec.begin(), it_end, std::greater<int>());
#endif
    auto stop = steady_clock::now();

    std::cout << "Sorting time: "
          << duration_cast<microseconds>(stop - start).count()
          << "us" << std::endl;
    return 0;
}

За допомогою цього командного рядка:

g++ -g -DUSE_REVERSE_ITER=0 -std=c++11 -O3 main.cpp \
    && valgrind --cachegrind-out-file=cachegrind.out --tool=cachegrind ./a.out \
    && cg_annotate cachegrind.out
g++ -g -DUSE_REVERSE_ITER=1 -std=c++11 -O3 main.cpp \
    && valgrind --cachegrind-out-file=cachegrind.out --tool=cachegrind ./a.out \
    && cg_annotate cachegrind.out

std::greater demo std::reverse_iterator demo

Терміни однакові. Valgrind повідомляє про стільки ж пропусків кешу.


2

Я не думаю, що ви не повинні використовувати жоден із методів у питанні, оскільки вони обидва заплутані, а другий - тендітний, як пропонує Мехрдад.

Я б виступав за наступне, оскільки це виглядає як стандартна функція бібліотеки і робить її намір зрозумілим:

#include <iterator>

template <class RandomIt>
void reverse_sort(RandomIt first, RandomIt last)
{
    std::sort(first, last, 
        std::greater<typename std::iterator_traits<RandomIt>::value_type>());
}

2
Це ніби в тисячу разів більше заплутано, ніж просто використання std::greaterкомпаратора ....
Аполліс підтримує Моніку

@Apollys Я погоджуюся, що починаючи з C ++ 14, std :: veće <> виглядає як краще рішення. Якщо у вас немає C ++ 14, це все одно може бути корисним, якщо ви хочете виключити будь-які сюрпризи з std :: більшим <int> (наприклад, коли типи в певний момент змінюються від int до long).
Філіп Класен

2

Ви можете скористатися першим або спробувати код, який нижче є однаково ефективним

sort(&a[0], &a[n], greater<int>());
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.