Tri d'un vecteur par ordre décroissant


310

Dois-je utiliser

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

ou

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

trier un vecteur par ordre décroissant? Y a-t-il des avantages ou des inconvénients avec l'une ou l'autre approche?


2
+1 Je pense que la réponse est évidente, mais cette question contient un petit truc intéressant. :)
wilhelmtell

3
Je voterais pour la première option, juste parce qu'alors je n'aurai jamais à m'occuper de reverse_iterator.
evandrix

2
@wilhelmtell Une question noob mais pourquoi la seconde devrait-elle être triée par ordre décroissant? Nous donnons le même tableau en entrée à la méthode de tri. C'est juste que nous le donnons dans l'ordre inverse, alors pourquoi devrait-il être trié dans l'ordre décroissant et non croissant comme ce serait le cas avec ar.begin () et ar.end.
shshnk

6
@shshnk std::sort(b, e);met le minimum à b(dans notre cas rbegin, donc le dernier élément) et le maximum à e(dans notre cas rend, donc le premier élément).
fredoverflow

Réponses:


114

En fait, la première est une mauvaise idée. Utilisez soit le second , soit ceci:

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

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

De cette façon, votre code ne se cassera pas en silence lorsque quelqu'un décide de numbersmaintenir longou long longau lieu de int.


1
@FredOverflow: Vous avez fait les honneurs dans votre commentaire;)
user541686

2
Ou restez avec le premier. Utilisez un typedef pour le numberContainer - une bonne idée pour que quelqu'un puisse passer à long long - et écrivez: std :: sort (numbers.begin (), numbers.end (), std :: Greater <numContainer :: value_type> ( ));
RichardHowells

1
+1 Le premier est vraiment déroutant. Qu'est-ce greaterque l'autre? rbeginet rendont été faites dans un but précis.
Abhishek Divekar

6
Pourquoi pas juste std::greater<typename decltype(numbers)::value_type>()ou quelque chose?
einpoklum

1
Cette réponse est obsolète - vous pouvez l'utiliser std::greater<>()depuis C ++ 14.
Nikolai

70

Utilisez le premier:

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

Il est explicite de ce qui se passe - moins de chance de mal interpréter rbegincomme begin, même avec un commentaire. C'est clair et lisible, c'est exactement ce que vous voulez.

De plus, le second peut être moins efficace que le premier étant donné la nature des itérateurs inversés, bien que vous deviez le profiler pour en être sûr.


68

Avec c ++ 14, vous pouvez faire ceci:

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

30

Et ça?

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

13
Une raison pourrait être d'éviter la complexité supplémentaire: O (n * log (n)) + O (n) vs O (n * log (n))
greg

32
@greg O (n * log (n)) = O (n * log (n) + n). Ce sont deux façons de définir le même ensemble. Vous voulez dire "Cela pourrait être plus lent."
pjvandehaar

4
@pjvandehaar Greg va bien. Il n'a explicitement pas dit, O (n * log (n) + n), il a dit O (n * log (n)) + O (n). Vous avez raison de dire que sa formulation n'est pas claire (en particulier son utilisation abusive du mot complexité), mais vous auriez pu répondre d'une manière plus gentille. Par exemple: vous vouliez peut-être utiliser le mot «calcul» au lieu du mot «complexité». L'inversion des nombres est une étape O (n) inutile vers une étape O (n * log (n)) par ailleurs identique.
Ofek Gila

3
@OfekGila Ma compréhension est que la notation big-O concerne les ensembles de fonctions, et la notation impliquant =et ne +sont que des commodités signifiant et . Dans ce cas, O(n*log(n)) + O(n)est une notation pratique O(n*log(n)) ∪ O(n)qui est la même que O(n*log(n)). Le mot «calcul» est une bonne suggestion et vous avez raison sur le ton.
pjvandehaar

22

Au lieu d'un foncteur comme Mehrdad l'a proposé, vous pouvez utiliser une fonction Lambda.

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

16

Selon ma machine, le tri d'un long longvecteur de [1..3000000] à l'aide de la première méthode prend environ 4 secondes, tandis que l'utilisation de la seconde prend environ deux fois plus de temps. Cela dit quelque chose, évidemment, mais je ne comprends pas non plus pourquoi. Pensez simplement que ce serait utile.

Même chose rapportée ici .

Comme l'a dit Xeo, -O3ils utilisent à peu près le même temps pour terminer.


12
Vous n'avez peut-être tout simplement pas compilé avec les optimisations activées? Cela ressemble beaucoup aux reverse_iteratoropérations qui n'étaient pas intégrées, et étant donné qu'elles ne sont qu'un wrapper autour des itérateurs réels, il n'est pas étonnant qu'elles prennent le double du temps sans s'aligner.
Xeo

@Xeo Même si elles étaient intégrées, certaines implémentations utilisent un ajout par déréférence.
Pubby du

@ildjarn: Parce que c'est comme ça? La base()fonction membre, par exemple, retourne l'itérateur encapsulé.
Xeo

1
@Xeo Maintenant, ils finissent tous les deux en une seconde. Merci!
zw324

3
@Xeo: Je le reprends; la norme impose en fait ce qui std::vector<>::reverse_iteratorest mis en œuvre en termes de std::reverse_iterator<>. Bizarre; aujourd'hui j'ai appris. :-P
ildjarn

11

La première approche fait référence:

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

Vous pouvez utiliser la première approche car vous obtenez plus d'efficacité que la seconde.
Complexité temporelle de la première approche inférieure à la seconde.


C'est la même réponse que celle de mrexciting. La remarque sur la complexité n'est pas non plus claire pour moi.
Philipp Claßen

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

4
pour être une réponse valable, vous devriez envisager d'écrire quelque chose sur les avantages / inconvénients de vos méthodes par rapport aux méthodes de l'OP
Stefan Hegny

3

TL; DR

Utilisez n'importe lequel. Ce sont presque les mêmes.

Réponse ennuyeuse

Comme d'habitude, il y a des avantages et des inconvénients.

Utilisation std::reverse_iterator:

  • Lorsque vous triez des types personnalisés et que vous ne souhaitez pas implémenter operator>()
  • Quand tu es trop paresseux pour taper std::greater<int>()

À utiliser std::greaterlorsque:

  • Lorsque vous voulez avoir un code plus explicite
  • Lorsque vous voulez éviter d'utiliser des itérateurs inverses obscurs

Quant aux performances, les deux méthodes sont tout aussi efficaces. J'ai essayé le benchmark suivant:

#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;
}

Avec cette ligne de commande:

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

Les horaires sont les mêmes. Valgrind signale le même nombre d'échecs de cache.


2

Je ne pense pas que vous devriez utiliser l'une ou l'autre des méthodes de la question car elles sont toutes deux déroutantes, et la seconde est fragile comme Mehrdad le suggère.

Je recommanderais ce qui suit, car il ressemble à une fonction de bibliothèque standard et rend son intention claire:

#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
C'est comme mille fois plus déroutant que d'utiliser simplement le std::greatercomparateur ....
Apollys prend en charge Monica

@Apollys Je suis d'accord pour dire qu'à partir de C ++ 14, std :: Greater <> ressemble à la solution préférée. Si vous n'avez pas C ++ 14, cela pourrait toujours être utile si vous voulez exclure toute surprise avec std :: Greater <int> (par exemple, lorsque les types changent à un moment donné de int à long).
Philipp Claßen

2

Vous pouvez soit utiliser le premier ou essayer le code ci-dessous qui est tout aussi efficace

sort(&a[0], &a[n], greater<int>());
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.