Ordinamento di un vettore in ordine decrescente


310

Dovrei usare

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

o

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

ordinare un vettore in ordine decrescente? Ci sono vantaggi o svantaggi con un approccio o l'altro?


2
+1 Penso che la risposta sia ovvia, ma questa domanda ha un po 'di trivio interessante. :)
Wilhelmtell,

3
Vorrei votare per la prima opzione, solo perché non dovrò mai occuparmene reverse_iterator.
evandrix,

2
@wilhelmtell Una domanda noob, ma perché la seconda dovrebbe essere in ordine decrescente? Stiamo fornendo lo stesso array di input per il metodo di ordinamento. È solo che lo stiamo dando in ordine inverso, quindi perché dovrebbe essere ordinato in ordine decrescente e non crescente come nel caso di ar.begin () e ar.end.
shshnk,

6
@shshnk std::sort(b, e);mette il minimo a b(nel nostro caso rbegin, quindi l' ultimo elemento) e il massimo a e(nel nostro caso rend, quindi il primo elemento).
Fredoverflow,

Risposte:


114

In realtà, la prima è una cattiva idea. Usa il secondo o questo:

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

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

In questo modo il tuo codice non si romperà silenziosamente quando qualcuno decide di numbersconservare longo long longinvece di int.


1
@FredOverflow: hai fatto gli onori nel tuo commento;)
user541686

2
O attenersi al primo. Usa un typedef per numberContainer - una buona idea in modo che qualcuno PUO 'scambiare a lungo - e scrivi: std :: sort (numbers.begin (), numbers.end (), std :: Greater <numContainer :: value_type> ( ));
RichardHowells,

1
+1 Il primo è davvero confuso. Cos'è greaterl'altro? rbegine rendsono stati realizzati per uno scopo specifico.
Abhishek Divekar,

6
Perché non solo std::greater<typename decltype(numbers)::value_type>()o qualcosa del genere?
einpoklum,

1
Questa risposta è obsoleta: puoi usarla std::greater<>()dal C ++ 14.
Nikolai,

70

Usa il primo:

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

È esplicito quello che sta succedendo - meno possibilità di interpretare male rbegincome begin, anche con un commento. È chiaro e leggibile che è esattamente quello che vuoi.

Inoltre, il secondo potrebbe essere meno efficiente del primo, data la natura degli iteratori inversi, anche se si dovrebbe profilarlo per sicurezza.


68

Con c ++ 14 puoi fare questo:

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

30

Che dire di questo?

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

13
Un motivo potrebbe essere quello di evitare la complessità aggiuntiva: O (n * log (n)) + O (n) vs O (n * log (n))
greg

32
@greg O (n * log (n)) = O (n * log (n) + n). Sono due modi per definire lo stesso set. Intendi dire "Potrebbe essere più lento".
pjvandehaar,

4
@pjvandehaar Greg sta bene. Non ha esplicitamente detto, O (n * log (n) + n), ha detto O (n * log (n)) + O (n). Hai ragione sul fatto che la sua formulazione non è chiara (in particolare l'uso improprio della parola complessità), ma avresti potuto rispondere in modo più gentile. Ad esempio: forse intendevi usare la parola "computazione" invece della parola "complessità". L'inversione dei numeri è un passaggio O (n) non necessario a un passaggio O (n * log (n)) altrimenti identico.
Ofek Gila,

3
@OfekGila La mia comprensione è che la notazione big-O riguarda insiemi di funzioni, e la notazione che coinvolge =e +sono solo significato di convenienze e . In tal caso, O(n*log(n)) + O(n)è una notazione conveniente per la O(n*log(n)) ∪ O(n)quale è uguale a O(n*log(n)). La parola "calcolo" è un buon suggerimento e hai ragione sul tono.
pjvandehaar,

22

Invece di un funzione come proposto da Mehrdad, potresti usare una funzione Lambda.

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

16

Secondo la mia macchina, l'ordinamento di un long longvettore di [1..3000000] utilizzando il primo metodo richiede circa 4 secondi, mentre l'utilizzo del secondo richiede circa il doppio del tempo. Questo dice qualcosa, ovviamente, ma non capisco neanche perché. Basti pensare che questo sarebbe utile.

Stessa cosa riportata qui .

Come detto da Xeo, -O3usano quasi lo stesso tempo per finire.


12
Forse non hai semplicemente compilato con le ottimizzazioni attivate? Sembra che le reverse_iteratoroperazioni non siano state completate e dato che sono solo un involucro attorno agli iteratori effettivi, non c'è da meravigliarsi che prendano il doppio del tempo senza allinearsi.
Xeo,

@Xeo Anche se sono stati integrati, alcune implementazioni usano un'aggiunta per dereference.
Pubby

@ildjarn: Perché è così? La base()funzione membro, ad esempio, restituisce l'iteratore avvolto.
Xeo,

1
@Xeo Ora finiscono entrambi in un secondo. Grazie!
zw324,

3
@Xeo: lo riprendo; lo standard in realtà impone che std::vector<>::reverse_iteratorè implementato in termini di std::reverse_iterator<>. Bizzarro; oggi ho imparato. :-P
ildjarn,

11

Il primo approccio si riferisce:

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

È possibile utilizzare il primo approccio per ottenere più efficienza del secondo.
La complessità temporale del primo approccio è inferiore alla seconda.


Questa è la stessa risposta di quella di mrexciting. Anche l'osservazione sulla complessità non è chiara per me.
Philipp Claßen,

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

4
per essere una risposta valida, dovresti prendere in considerazione la possibilità di scrivere qualcosa sui vantaggi / svantaggi dei tuoi metodi di menzione rispetto al PO
Stefan Hegny,

3

TL; DR

Usa qualsiasi Sono quasi uguali.

Risposta noiosa

Come al solito, ci sono pro e contro.

Utilizzare std::reverse_iterator:

  • Quando si ordinano tipi personalizzati e non si desidera implementare operator>()
  • Quando sei troppo pigro per scrivere std::greater<int>()

Utilizzare std::greaterquando:

  • Quando vuoi avere un codice più esplicito
  • Quando si desidera evitare l'uso di iteratori inversi oscuri

Per quanto riguarda le prestazioni, entrambi i metodi sono ugualmente efficienti. Ho provato il seguente 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;
}

Con questa riga di 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

I tempi sono gli stessi. Valgrind riporta lo stesso numero di errori nella cache.


2

Non penso che dovresti usare nessuno dei due metodi nella domanda poiché sono entrambi confusi, e il secondo è fragile come suggerisce Mehrdad.

Suggerirei quanto segue, in quanto sembra una funzione di libreria standard e chiarisce la sua intenzione:

#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
Questo è come mille volte più confuso rispetto al semplice utilizzo del std::greatercomparatore ....
Apollys supporta Monica il

@Accordi Sono d'accordo che a partire da C ++ 14, std :: greater <> sembra la soluzione preferita. Se non hai C ++ 14, potrebbe comunque essere utile se vuoi escludere qualsiasi sorpresa con std :: greater <int> (ad esempio, quando i tipi cambiano da int a long).
Philipp Claßen,

2

Puoi usare il primo o provare il codice qui sotto che è ugualmente efficiente

sort(&a[0], &a[n], greater<int>());
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.