按降序对向量排序


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

2
@wilhelmtell一个菜鸟问题,但是为什么第二个应该降序排序?我们将与输入相同的数组作为sort方法的输入。只是我们给它相反的顺序,所以为什么要像ar.begin()和ar.end那样按降序而不是升序排序。
shshnk

6
@shshnk std::sort(b, e);将最小值放在b(在我们的情况下rbegin,是最后一个元素),将最大值放在e(在我们的情况下rend,因此是第一个元素)。
fredoverflow

Answers:


114

实际上,第一个是个坏主意。使用第二个,或者使用以下命令:

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:您在评论中获得了荣誉;)
2013年

2
或坚持第一个。为numberContainer使用typedef-一个好主意,以便某人可以交换到long long-并写:std :: sort(numbers.begin(),numbers.end(),std :: greater <numContainer :: value_type>( ));
RichardHowells

1
+1第一个真的很令人困惑。什么greater比其他?rbegin并且rend是为特定目的而制作的。
Abhishek Divekar '16

6
为什么不只是std::greater<typename decltype(numbers)::value_type>()什么?
einpoklum

1
这个答案已经过时了- std::greater<>()从C ++ 14开始就可以使用。
Nikolai

70

使用第一个:

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

它清楚地表明了正在发生的事情- 甚至带有评论的误读rbegin为的机会也更少begin。清晰易读,这正是您想要的。

同样,考虑到反向迭代器的性质,第二种方法的效率可能比第一种方法低,尽管您必须对其进行剖析才能确定。


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)vs O(n * log(n))
greg

32
@greg O(n * log(n))= O(n * log(n)+ n)。它们是定义相同集合的两种方法。您的意思是说“这可能会慢一些。”
pjvandehaar'3

4
@pjvandehaar Greg很好。他明确地没有说O(n * log(n)+ n),他说的是O(n * log(n))+ O(n)。没错,他的措词尚不清楚(特别是他对“复杂性”一词的误用),但您可以用更友善的方式回答。例如:也许您打算使用“计算”​​一词而不是“复杂性”一词。将数字取反是不必要的O(n)步骤,否则就是相同的O(n * log(n))步骤。
Ofek Gila '18

3
@OfekGila我的理解是big-O表示法是关于函数集的,并且表示法涉及=+只是方便的含义。在这种情况下,O(n*log(n)) + O(n)是一个方便的符号,O(n*log(n)) ∪ O(n)与相同O(n*log(n))。“计算”一词是一个很好的建议,您对音调是正确的。
pjvandehaar '18 -10-3


16

根据我的机器,long long使用第一种方法对向量[1..3000000]进行排序大约需要4秒钟,而使用第二种方法则需要大约两倍的时间。这显然可以说些什么,但我也不明白为什么。只是认为这会有所帮助。

同样的事情在这里报道。

正如Xeo所说,与-O3他们使用大约相同的时间来完成。


12
您是否可能没有启用优化功能进行编译?听起来很像reverse_iterator是没有内联的操作,并且考虑到它们只是实际迭代器的包装,所以难怪它们花了两倍的时间不进行内联。
Xeo 2012年

@Xeo即使内联它们,某些实现也会为每个取消引用使用附加值。
Pubby 2012年

@ildjarn:因为那样吗?base()例如,成员函数返回包装的迭代器。
Xeo 2012年

1
@Xeo现在它们都在一秒钟内完成。谢谢!
zw324 2012年

3
@Xeo:我拿回去;标准的实际任务std::vector<>::reverse_iterator是在以下方面实现的std::reverse_iterator<>。怪异的 今天我学到了。:-P
ildjarn

11

第一种方法是:

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

您可以使用第一种方法,因为它比第二种方法效率更高。
第一种方法的时间复杂度小于第二种方法。


这与mrexciting的答案相同。我也不清楚有关复杂性的说法。
PhilippClaßen18年

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

4
要获得有效答案,您应该考虑写一些关于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

我不认为您应该在问题中使用这两种方法中的任何一种,因为它们都令人困惑,并且第二种方法如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比较....
Apollys支持莫妮卡

@Apollys我同意从C ++ 14开始,std :: greater <>看起来像是首选的解决方案。如果您没有C ++ 14,如果您想排除std :: greater <int>带来的任何意外(例如,当类型从int更改为long时),它仍然可能很有用。
PhilippClaßen18年

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.