ベクトルを降順に並べ替える


310

使うべきか

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

または

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

ベクトルを降順に並べ替えるには?どちらのアプローチにもメリットやデメリットはありますか?


2
+1答えは明白だと思いますが、この質問には興味深いトリビウムがあります。:)
wilhelmtell 2012年

3
私は最初のオプションに投票します。それは、に対処する必要がないためreverse_iteratorです。
evandrix 2013年

2
@wilhelmtell noob質問ですが、なぜ2番目の質問は降順でソートする必要があるのですか?sortメソッドへの入力と同じ配列を与えています。それは単に逆の順序で与えているだけなので、ar.begin()とar.endの場合のように、降順で並べ替えるのではなく、昇順で並べ替えるべきなのです。
shshnk

6
@shshnk std::sort(b, e);は、最小値b(この場合rbegin最後の要素)と最大値e(この場合rend最初の要素)を配置します。
fredoverflow

回答:


114

実際、最初のものは悪い考えです。2番目のいずれか、またはこれを使用します。

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

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

誰かが決定したとき、その方法は、あなたのコードは黙って中断されませんnumbers保持すべきlonglong longの代わりにint


1
@FredOverflow:コメントで栄誉を行いました;)
user541686

2
または最初のものに固執する。numberContainerにtypedefを使用します-誰かがlong longにスワップできるようにするための良いアイデアです-次のように記述します:std :: sort(numbers.begin()、numbers.end()、std :: greater <numContainer :: value_type>( ));
RichardHowells 2013年

1
+1最初のものは本当に混乱しています。greater他のものとは何ですか?rbeginそしてrend、特定の目的のために作られました。
Abhishek Divekar

6
なぜだけではなく、std::greater<typename decltype(numbers)::value_type>()か何か?
einpoklum 2016

1
この回答は古くなっていますstd::greater<>()-C ++ 14以降で使用できます。
ニコライ

70

最初を使用:

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

これは、何が起こっているかを明確に示しています- コメントがあっても、と誤解さrbeginれる可能性は低くbeginなっています。それは明確で読みやすく、まさにあなたが望んでいるものです。

また、2番目のものは、リバース反復子の性質上、最初のものよりも効率が悪い場合がありますが、確実にプロファイルする必要があります。


68

c ++ 14でこれを行うことができます:

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

30

これはどうですか?

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

13
理由は、追加の複雑さを回避することである可能性があります:O(n * log(n))+ O(n)対O(n * log(n))
集計

32
@greg O(n * log(n))= O(n * log(n)+ n)。これらは、同じセットを定義する2つの方法です。「これは遅いかもしれません」と言うことを意味します。
pjvandehaar 2016年

4
@pjvandehaarグレッグは元気です。彼はO(n * log(n)+ n)と明示的には言わず、O(n * log(n))+ O(n)と述べた。彼の言い回しは不明確(特に、彼が複雑さという言葉を誤用している)だと思うが、もっと親切に答えることができたはずだ。例:多分あなたは「複雑さ」という言葉の代わりに「計算」という言葉を使うつもりでした。数値を逆にすることは、それ以外は同一のO(n * log(n))ステップへの不必要なO(n)ステップです。
Ofek Gila '30

3
@OfekGila私の理解では、ビッグO記法は、関数のセットについてです、と表記が関与することである=+意味だけで便利です。その場合、O(n*log(n)) + O(n)はとO(n*log(n)) ∪ O(n)同じ便利な表記ですO(n*log(n))。「計算」という言葉は良い提案であり、あなたはその調子について正しい。
pjvandehaar 2018年

22

Mehrdadが提案したファンクタの代わりに、Lambda関数を使用できます。

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

16

私のマシンによると、long long最初の方法を使用して[1..3000000]のベクトルをソートするには約4秒かかりますが、2番目の方法を使用すると約2倍の時間がかかります。それは明らかに何かを言いますが、私もその理由を理解していません。これが役に立つと思うだけです。

同じことがここで報告されました

Xeoが言ったように、-O3彼らは終了するのにほぼ同じ時間を使います。


12
最適化をオンにしてコンパイルしていないのではないでしょうか?非常に多くのように聞こえるreverse_iteratorの操作をインライン化し、それらが実際のイテレータ単なるラッパーだと与えられていなかった、それは彼らがインライン化せずに二重の時間がかかるのも不思議ではありません。
Xeo

@Xeoインライン化されている場合でも、一部の実装では逆参照ごとに追加を使用します。
Pubby

@ildjarn:そんな感じだから?base()例えば、メンバ関数は、ラップされたイテレータを返します。
Xeo

1
@Xeo今、彼らは両方ともすぐに終了します。ありがとう!
zw324 2012年

3
@Xeo:私はそれを取り戻します。標準は、実際にに関して実装されることを義務付けています。奇妙な; 今日私は学びました。:-Pstd::vector<>::reverse_iteratorstd::reverse_iterator<>
ildjarn

11

最初のアプローチは次のとおりです。

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

2つ目よりも効率が上がるため、最初の方法を使用できます。
最初のアプローチの時間の複雑さは2番目のアプローチよりも少ないです。


これはmrexcitingの答えと同じです。複雑さについての発言も不明瞭です。
PhilippClaßen18年

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

4
有効な回答になるには、OPの言及方法とOPの利点/欠点について何か書くことを検討する必要があります
Stefan Hegny

3

TL; DR

任意を使用します。彼らはほとんど同じです。

退屈な答え

いつものように、長所と短所があります。

使用std::reverse_iterator

  • カスタムタイプをソートしていて、実装したくない場合 operator>()
  • 入力が面倒なとき std::greater<int>()

次のstd::greater場合に使用:

  • より明示的なコードが必要な場合
  • あいまいな逆反復子の使用を避けたい場合

パフォーマンスに関しては、どちらの方法も同等に効率的です。次のベンチマークを試しました。

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

このコマンドラインで:

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

タイミングは同じです。Valgrindは同じ数のキャッシュミスを報告します。


2

質問ではどちらの方法も使用するべきではないと思います。どちらも混乱を招き、2番目の方法はMehrdadが示唆するように壊れやすいためです。

標準ライブラリ関数のように見え、その意図を明確にするため、以下を推奨します。

#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
これは、std::greaterコンパレータを使用するよりも1000倍も混乱します...
Apollysはモニカ

@Apollys C ++ 14以降、std :: greater <>が推奨されるソリューションのように見えることに同意します。C ++ 14がない場合でも、std :: greater <int>を使用して予期せぬ事態を排除したい場合(たとえば、ある時点での型がintからlongに変更された場合)は有用です。
PhilippClaßen18年

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.