Classificando um vetor em ordem decrescente


310

Devo usar

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

ou

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

classificar um vetor em ordem decrescente? Existem benefícios ou desvantagens em uma abordagem ou outra?


2
+1 Acho que a resposta é óbvia, mas essa pergunta tem um pouco de trivial interessante. :)
wilhelmtell

3
Eu votaria na primeira opção, só porque então eu nunca terei que lidar com reverse_iteratoras.
precisa saber é o seguinte

2
@wilhelmtell Uma pergunta noob, mas por que o segundo deveria ser ordenado em ordem decrescente? Estamos fornecendo a mesma matriz de entrada para o método de classificação. É apenas que estamos dando a ordem inversa, por que ela deve ser classificada em ordem decrescente e não crescente, como seria o caso de ar.begin () e ar.end.
shshnk

6
@shshnk std::sort(b, e);coloca o mínimo em b(no nosso caso rbegin, portanto, o último elemento) e o máximo em e(no nosso caso rend, o primeiro elemento).
Fredoverflow 6/06/16

Respostas:


114

Na verdade, o primeiro é uma má ideia. Use o segundo , ou este:

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

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

Dessa forma, seu código não será interrompido silenciosamente quando alguém decidir que numbersdeve aguardar longou em long longvez de int.


1
@FredOverflow: Você fez as honras em seu comentário;)
user541686

2
Ou fique com o primeiro. Use um typedef para o numberContainer - uma boa idéia para que alguém possa trocar por muito tempo - e escreva: std :: sort (números.begin (), números.end (), std :: maior <numContainer :: value_type> ( ));
21413 RichardHowells

1
+1 O primeiro é realmente confuso. O greaterque é o outro? rbegine rendforam feitos para uma finalidade específica.
Abhishek Divekar

6
Por que não apenas std::greater<typename decltype(numbers)::value_type>()ou algo assim?
Einpoklum

1
Esta resposta está desatualizada - você pode usar std::greater<>()desde o C ++ 14.
Nikolai

70

Use o primeiro:

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

É explícito o que está acontecendo - menos chance de interpretar mal rbegincomobegin , mesmo com um comentário. É claro e legível, exatamente o que você deseja.

Além disso, o segundo pode ser menos eficiente que o primeiro, dada a natureza dos iteradores reversos, embora você precise criar um perfil para ter certeza.


68

Com o c ++ 14, você pode fazer isso:

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

30

Que tal isso?

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

13
Uma razão podia ser para evitar a complexidade adicional: O (n * log (n)) + O (n) vs S (n * log (n))
Greg

32
@greg O (n * log (n)) = O (n * log (n) + n). São duas maneiras de definir o mesmo conjunto. Você quer dizer "Isso pode ser mais lento".
pjvandehaar

4
@pjvandehaar Greg está bem. Ele não disse explicitamente: O (n * log (n) + n), ele disse O (n * log (n)) + O (n). Você está certo de que as palavras dele não são claras (especialmente o uso indevido da palavra complexidade), mas você poderia ter respondido de maneira mais gentil. Ex .: Talvez você pretenda usar a palavra 'computação' em vez da palavra 'complexidade'. A reversão dos números é uma etapa O (n) desnecessária para uma etapa O (n * log (n)) idêntica.
Ofek Gila

3
@OfekGila Meu entendimento é que a notação big-O refere-se a conjuntos de funções, e notação envolvendo =e +são apenas conveniências e significando . Nesse caso, O(n*log(n)) + O(n)é uma notação conveniente para a O(n*log(n)) ∪ O(n)qual é o mesmo que O(n*log(n)). A palavra "computação" é uma boa sugestão e você está certo sobre o tom.
Pjvandehaar #

22

Em vez de um functor como Mehrdad propôs, você poderia usar uma função Lambda.

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

16

De acordo com minha máquina, classificar um long long vetor de [1..3000000] usando o primeiro método leva cerca de 4 segundos, enquanto o uso do segundo leva cerca de duas vezes o tempo. Isso diz algo, obviamente, mas também não entendo o porquê. Apenas pense que isso seria útil.

A mesma coisa relatada aqui .

Como disse Xeo, -O3eles usam quase o mesmo tempo para terminar.


12
Talvez você não tenha compilado com as otimizações ativadas? Parece que as reverse_iteratoroperações não foram incorporadas e, como são apenas um invólucro dos iteradores reais, não é de admirar que eles demorem o dobro do tempo sem inline.
Xeo 26/01

@Xeo Mesmo que tenham sido incorporadas, algumas implementações usam uma adição por desreferência.
Pubby

@ildjarn: Porque é assim? A base()função de membro, por exemplo, retorna o iterador agrupado.
Xeo 26/01/12

1
@ Xeo Agora, ambos terminam em um segundo. Obrigado!
Zw324

3
@ Xeo: eu retiro; o padrão realmente exige que std::vector<>::reverse_iteratorseja implementado em termos de std::reverse_iterator<>. Bizarro; hoje eu aprendi. :-P
ildjarn

11

A primeira abordagem refere-se:

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

Você pode usar a primeira abordagem por obter mais eficiência que a segunda.
A complexidade do tempo da primeira abordagem é menor que a segunda.


Esta é a mesma resposta que a de mrexciting. A observação sobre complexidade também não está clara para mim.
Philipp Claßen

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

4
ser uma resposta válida, você deve considerar escrever algo sobre vantagens / desvantagens da sua vs. o OP é menciona métodos
Stefan Hegny

3

TL; DR

Use qualquer. Eles são quase os mesmos.

Resposta chata

Como sempre, existem prós e contras.

Use std::reverse_iterator:

  • Quando você está classificando tipos personalizados e não deseja implementar operator>()
  • Quando você está com preguiça de digitar std::greater<int>()

Use std::greaterquando:

  • Quando você deseja ter um código mais explícito
  • Quando você deseja evitar o uso de iteradores reversos obscuros

Quanto ao desempenho, ambos os métodos são igualmente eficientes. Eu tentei o seguinte benchmark:

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

Com esta linha de comando:

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

Os horários são os mesmos. O Valgrind relata o mesmo número de falhas de cache.


2

Não acho que você deva usar um dos métodos da pergunta, pois ambos são confusos, e o segundo é frágil, como sugere Mehrdad.

Eu defenderia o seguinte, pois parece uma função de biblioteca padrão e torna clara sua intenção:

#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
Isso é mil vezes mais confuso do que apenas usar o std::greatercomparador ....
Apollys suporta Monica

@ Apollys Concordo que, começando com C ++ 14, std :: maior <> se parece com a solução preferida. Se você não possui o C ++ 14, ainda assim pode ser útil se você deseja excluir qualquer surpresa com std :: maior <int> (por exemplo, quando os tipos em algum momento mudam de int para longo).
Philipp Claßen

2

Você pode usar o primeiro ou tentar o código abaixo, que é igualmente eficiente

sort(&a[0], &a[n], greater<int>());
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.