Bir vektörü azalan sırada sıralama


310

Kullanmalı mıyım

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

veya

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

vektörü azalan düzende sıralamak için? Bir yaklaşımla ya da diğeriyle herhangi bir fayda ya da dezavantaj var mı?


2
+1 Cevabın açık olduğunu düşünüyorum, ancak bu sorunun ilginç bir trivium var. :)
wilhelmtell

3
İlk seçeneğe oy verirdim, çünkü o zaman hiç biriyle uğraşmak zorunda kalmayacağım reverse_iterator.
evandrix

2
@wilhelmtell Bir çaylak sorusu ama ikincisi neden azalan düzende sıralanmalıdır? Sort yöntemine girdi ile aynı diziyi veriyoruz. Sadece ters sırada veriyoruz, bu yüzden neden ar.begin () ve ar.end'de olduğu gibi azalan sırada ve artan sırada sıralanmamalıdır.
shshnk

6
@shshnk std::sort(b, e);, minimum değerini b(bizim durumumuzda rbegin, bu nedenle son eleman) ve maksimum değerini e(bizim durumumuzda rend, ilk eleman) koyar .
fredoverflow

Yanıtlar:


114

Aslında ilki kötü bir fikir. İkincisini veya şunu kullanın :

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

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

Bu şekilde, birisinin numberstutması gerektiğine longveya long longyerine karar verdiğinizde kodunuz sessizce kırılmaz int.


1
@FredOverflow: Yorumunuzda onurları yaptınız;)
user541686

2
Veya ilki ile sopa. NumberContainer için bir typedef kullanın - birisinin uzun süre değiştirebilmesi için iyi bir fikir - ve şunu yazın: std :: sort (numbers.begin (), numbers.end (), std :: bigger <numContainer :: value_type> ( ));
RichardHowells

1
+1 Birincisi gerçekten kafa karıştırıcı. Diğerinden greaterdaha nedir ? rbeginve rendbelirli bir amaç için yapılmıştır.
Abhishek Divekar

6
Neden sadece std::greater<typename decltype(numbers)::value_type>()falan değil ?
einpoklum

1
Bu cevap eski - std::greater<>()C ++ 14'ten beri kullanabilirsiniz .
Nikolai

70

İlkini kullanın:

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

Misreading az şans - O ne olup bittiğini açık olduğunu rbeginolarak beginhatta bir yorum ile. Net ve okunabilir olan tam olarak ne istediğinizi.

Ayrıca, ikincisi, ters yineleyicilerin doğası göz önüne alındığında ilkinden daha az verimli olabilir, ancak emin olmak için profilinizi oluşturmanız gerekir.


68

C ++ 14 ile bunu yapabilirsiniz:

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

30

Peki buna ne dersin?

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

13
Ek karmaşıklığı önlemek için bir neden olabilir: O (n * log (n)) + O (n) vs O (n * log (n))
greg

32
@greg O (n * log (n)) = O (n * log (n) + n). Aynı seti tanımlamanın iki yoludur. "Bu daha yavaş olabilir" demek istiyorsun.
pjvandehaar

4
@pjvandehaar Greg iyidir. Açıkça O (n * log (n) + n) demedi, O (n * log (n)) + O (n) dedi. İfadesinin belirsiz olduğu konusunda haklısınız (özellikle karmaşıklık kelimesini kötüye kullandığı), ancak daha nazik bir şekilde cevap verebilirdiniz. Örneğin: Belki 'karmaşıklık' kelimesi yerine 'hesaplama' kelimesini kullanmak istediniz. Sayıları tersine çevirmek, aksi takdirde aynı O (n * log (n)) adımına giden gereksiz O (n) adımıdır.
Ofek Gila

3
@OfekGila Anladığım kadarıyla, büyük O gösterimi işlev kümeleriyle ilgilidir ve gösterim içeren =ve +sadece anlam ve kolaylıktır . Bu durumda, O(n*log(n)) + O(n)onunla O(n*log(n)) ∪ O(n)aynı olan uygun bir gösterimdir O(n*log(n)). "Hesaplama" kelimesi iyi bir öneri ve ton hakkında haklısın.
pjvandehaar

22

Mehrdad'ın önerdiği gibi bir işlev yerine bir Lambda işlevi kullanabilirsiniz.

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

16

long longMakineme göre, ilk yöntemi kullanarak [1..3000000] vektörünün sıralanması yaklaşık 4 saniye sürerken, ikinci yöntemi kullanmak yaklaşık iki kez sürmektedir. Açıkçası bir şey söylüyor, ama nedenini de anlamıyorum. Bunun yararlı olacağını düşünün.

Aynı şey burada da bildirildi .

Xeo tarafından söylendiği gibi, -O3bitirmek için yaklaşık aynı zamanı kullanıyorlar.


12
Belki de açık optimizasyonlarla derlemediniz mi? reverse_iteratorOperasyonlar inline edilmemiş gibi görünüyor ve gerçek iteratörlerin etrafında sadece bir sarıcı oldukları göz önüne alındığında, satır içi olmadan zamanın iki katını almaları şaşırtıcı değil.
Xeo

@Xeo Satırlı olsalar bile bazı uygulamalar her bir referans için bir ek kullanır.
Pubby

@ildjarn: Çünkü böyle mi? base()Örneğin döner için elemanı işlevi yineleyici tamamladı.
Xeo

1
@Xeo Şimdi ikisi de bir saniyede bitiyor. Teşekkürler!
zw324

3
@ Xeo: Geri alıyorum; Standart aslında görevstd::vector<>::reverse_iterator yönünden uygulanmaktadır std::reverse_iterator<>. Tuhaf; bugün öğrendim. :-P
ildjarn

11

İlk yaklaşım şunları ifade eder:

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

İkinciden daha fazla verim alması nedeniyle ilk yaklaşımı kullanabilirsiniz.
İlk yaklaşımın zaman karmaşıklığı ikincisinden daha az.


Bu mrexciting'in cevabı ile aynı cevaptır. Karmaşıklık hakkındaki sözler benim için de belirsiz.
Philipp Claßen

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

4
geçerli bir cevap olarak, OP'nin bahsetme yöntemlerine kıyasla avantajları / dezavantajları hakkında bir şeyler yazmayı düşünmelisiniz
Stefan Hegny

3

TL; DR

Herhangi birini kullanın. Neredeyse aynılar.

Sıkıcı cevap

Her zamanki gibi, artıları ve eksileri vardır.

Kullanım std::reverse_iterator:

  • Özel türleri sıralarken ve uygulamak istemediğinizde operator>()
  • Yazmak için çok tembel olduğunuzda std::greater<int>()

Aşağıdaki std::greaterdurumlarda kullanın :

  • Daha açık bir kod olmasını istediğinizde
  • Belirsiz ters yineleyiciler kullanmaktan kaçınmak istediğinizde

Performansa gelince, her iki yöntem de eşit derecede verimlidir. Aşağıdaki ölçütleri denedim:

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

Bu komut satırı ile:

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

Zamanlamalar aynı. Valgrind aynı sayıda önbellek kaybını rapor eder.


2

Her ikisinin de kafa karıştırıcı olduğu ve ikincisinin Mehrdad'ın önerdiği gibi kırılgan olduğu için sorudaki yöntemlerden birini kullanmanız gerektiğini düşünmüyorum.

Standart bir kütüphane işlevi gibi göründüğü ve amacını açıkladığı için aşağıdakileri savunurum:

#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
Bu sadece std::greaterkarşılaştırıcıyı kullanmaktan bin kat daha kafa karıştırıcı ...
Apollys, Monica

@Apollys C ++ 14 ile başlayarak std :: bigger <> tercih edilen çözüme benzediğini kabul ediyorum. C ++ 14'ünüz yoksa, std :: bigger <int> (örneğin, bir noktadaki türler int'den uzunluğa değiştiğinde) ile ilgili sürprizleri dışlamak istiyorsanız yine de yararlı olabilir.
Philipp Claßen

2

İlkini kullanabilir veya eşit derecede verimli olan aşağıdaki kodu deneyebilirsiniz

sort(&a[0], &a[n], greater<int>());
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.