web-dev-qa-db-fra.com

Tri d'un vecteur par ordre décroissant

Devrais-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?

289
fredoverflow

En fait, la première est une mauvaise idée. Utilisez soit le second one, 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());

Ainsi, votre code ne se cassera pas silencieusement quand quelqu'un décidera numbers devrait contenir long ou long long au lieu de int.

107
Mehrdad

Utilisez le premier:

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

C'est explicite de ce qui se passe - moins de risque de mauvaise lecture rbegin comme begin, même avec un commentaire. C'est clair et lisible et c'est exactement ce que vous voulez.

En outre, le second peut être moins efficace que le premier compte tenu de la nature des itérateurs inversés, bien que vous deviez le profiler pour en être sûr.

66
Pubby

Avec c ++ 14, vous pouvez faire ceci:

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

Et ça?

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

Au lieu d'un foncteur proposé par Mehrdad, vous pourriez utiliser une fonction Lambda.

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

Selon ma machine, trier un vecteur long long 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 pourquoi non plus. Pensez simplement que cela serait utile.

Même chose rapportée ici .

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

15
zw324

La première approche concerne:

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

Vous pouvez utiliser la première approche pour obtenir plus d'efficacité que la seconde.
La complexité temporelle de la première approche est inférieure à celle de la seconde.

10
rashedcs

L'approche la plus courte est:

std::sort(v.rbegin(), v.rend());
8
Alexey
bool comp(int i, int j) { return i > j; }
sort(numbers.begin(), numbers.end(), comp);
6
user7069426

Je ne pense pas que vous devriez utiliser l'une des méthodes de la question car elles prêtent à confusion, et la seconde est fragile, comme le suggère Mehrdad.

Je préconise les éléments suivants, car ils ressemblent à une fonction de bibliothèque standard et expliquent clairement son intention:

#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>());
}
1
Martin Broadhurst

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

sort(&a[0], &a[n], greater<int>());
1
Krish Munot

TL; DR

Utilisez tout. Ils sont presque les mêmes.

Réponse ennuyeuse

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

Utilisez std::reverse_iterator:

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

Utilisez std::greater lorsque:

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

En ce qui concerne les performances, les deux méthodes sont tout aussi efficaces. J'ai essayé le repère 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 demostd::reverse_iterator demo

Les horaires sont les mêmes. Valgrind signale le même nombre de manquements dans le cache.

0
ivaigult