Sắp xếp một vectơ theo thứ tự giảm dần


310

Tôi có nên sử dụng

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

hoặc là

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

để sắp xếp một vectơ theo thứ tự giảm dần? Có bất kỳ lợi ích hoặc nhược điểm với cách tiếp cận này hay cách khác?


2
+1 Tôi nghĩ rằng câu trả lời là rõ ràng, nhưng câu hỏi này có một chút tầm thường thú vị. :)
wilmustell

3
Tôi sẽ bỏ phiếu cho lựa chọn đầu tiên, chỉ vì sau đó tôi sẽ không bao giờ phải đối phó với reverse_iterator.
evandrix

2
@wilmustell Một câu hỏi noob nhưng tại sao câu hỏi thứ hai nên sắp xếp theo thứ tự giảm dần? Chúng tôi đang đưa ra cùng một mảng như đầu vào cho phương thức sắp xếp. Chỉ là chúng ta đang đưa nó theo thứ tự ngược lại, vậy tại sao nó phải được sắp xếp theo thứ tự giảm dần và không tăng dần như trường hợp của ar.begin () và ar.end.
shshnk

6
@shshnk std::sort(b, e);đặt mức tối thiểu tại b(trong trường hợp của chúng tôi rbegin, vì vậy phần tử cuối cùng ) và tối đa tại e(trong trường hợp của chúng tôi rend, vì vậy phần tử đầu tiên ).
dòng chảy

Câu trả lời:


114

Thật ra, cái đầu tiên là một ý tưởng tồi. Sử dụng cái thứ hai hoặc cái này:

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

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

Bằng cách đó, mã của bạn sẽ không âm thầm phá vỡ khi ai đó quyết định numbersnên giữ longhoặc long longthay vì int.


1
@FredOverflow: Bạn đã vinh danh trong bình luận của bạn;)
user541686

2
Hoặc gắn bó với cái đầu tiên. Sử dụng một typedef cho numberContainer - một ý tưởng hay để ai đó CÓ THỂ trao đổi lâu dài - và viết: std :: sort (Numbers.begin (), Numbers.end (), std :: Greater <numContainer :: value_type> ( ));
RichardHowells

1
+1 Cái đầu tiên thực sự khó hiểu. Cái gì greaterhơn cái kia? rbeginrendđược thực hiện cho một mục đích cụ thể.
Abhishek Divekar

6
Tại sao không chỉ std::greater<typename decltype(numbers)::value_type>()hoặc một cái gì đó?
einpoklum

1
Câu trả lời này đã lỗi thời - bạn có thể sử dụng std::greater<>()kể từ C ++ 14.
Nikolai

70

Sử dụng đầu tiên:

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

Đó là rõ ràng về những gì đang xảy ra - ít cơ hội hiểu sai rbeginnhư begin, ngay cả với một nhận xét. Nó rõ ràng và dễ đọc, đó chính xác là những gì bạn muốn.

Ngoài ra, cái thứ hai có thể kém hiệu quả hơn cái thứ nhất có bản chất của các trình vòng lặp ngược, mặc dù bạn sẽ phải xác định nó để chắc chắn.


68

Với c ++ 14 bạn có thể làm điều này:

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

30

Cái này thì sao?

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

13
Một lý do có thể là để tránh sự phức tạp bổ sung: O (n * log (n)) + O (n) vs O (n * log (n))
greg

32
@greg O (n * log (n)) = O (n * log (n) + n). Chúng là hai cách định nghĩa cùng một bộ. Bạn có nghĩa là để nói "Điều này có thể chậm hơn."
pjvandehaar

4
@pjvandehaar Greg vẫn ổn. Anh ấy không nói rõ ràng, O (n * log (n) + n), anh ấy nói O (n * log (n)) + O (n). Bạn nói đúng rằng từ ngữ của anh ấy không rõ ràng (đặc biệt là việc anh ấy sử dụng sai từ phức tạp của từ này), nhưng bạn đã có thể trả lời một cách tử tế hơn. Ví dụ: Có lẽ bạn muốn sử dụng từ 'tính toán' thay vì từ 'độ phức tạp'. Đảo ngược các số là một bước O (n) không cần thiết thành bước O (n * log (n)) giống hệt nhau.
Ofek Gila

3
@OfekGila Hiểu biết của tôi là ký hiệu big-O là về các bộ chức năng, và ký hiệu liên quan =+chỉ là ý nghĩa tiện lợi . Trong trường hợp đó, O(n*log(n)) + O(n)là một ký hiệu thuận tiện O(n*log(n)) ∪ O(n)giống như O(n*log(n)). Từ "tính toán" là một gợi ý hay và bạn nói đúng về giai điệu.
pjvandehaar

22

Thay vì functor như Mehrdad đề xuất, bạn có thể sử dụng hàm Lambda.

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

16

Theo máy của tôi, việc sắp xếp một long longvectơ [1..3000000] bằng phương pháp đầu tiên mất khoảng 4 giây, trong khi sử dụng lần thứ hai mất khoảng hai lần thời gian. Điều đó nói lên điều gì đó, rõ ràng, nhưng tôi cũng không hiểu tại sao. Chỉ cần nghĩ rằng điều này sẽ hữu ích.

Điều tương tự được báo cáo ở đây .

Như đã nói của Xeo, với việc -O3họ sử dụng cùng một lúc để hoàn thành.


12
Bạn có thể không biên dịch với tối ưu hóa bật? Âm thanh rất giống với các reverse_iteratorhoạt động không được nội tuyến và cho rằng chúng chỉ là một trình bao bọc xung quanh các trình vòng lặp thực tế, không có gì lạ khi chúng mất gấp đôi thời gian mà không cần nội tuyến.
Xèo

@Xeo Ngay cả khi chúng được nội tuyến, một số triển khai sử dụng một bổ sung cho mỗi sự bổ sung.
Pubby

@ildjarn: Vì nó như thế? Các base()hàm thành viên cho ví dụ trả về bọc iterator.
Xèo

1
@Xeo Bây giờ cả hai kết thúc sau một giây. Cảm ơn!
zw324

3
@Xeo: Tôi lấy lại; tiêu chuẩn thực nhiệm vụ đó std::vector<>::reverse_iteratorđược thực hiện trong điều kiện của std::reverse_iterator<>. Kỳ quái; ngày hôm nay tôi đã học. :-P
ildjarn

11

Cách tiếp cận đầu tiên đề cập đến:

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

Bạn có thể sử dụng phương pháp đầu tiên vì đạt được hiệu quả cao hơn lần thứ hai.
Độ phức tạp thời gian của cách tiếp cận thứ nhất ít hơn lần thứ hai.


Đây là câu trả lời giống như câu trả lời của mrexciting. Nhận xét về sự phức tạp cũng không rõ ràng đối với tôi.
Philipp Claßen

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

4
để trở thành một câu trả lời hợp lệ, bạn nên cân nhắc viết một cái gì đó về ưu điểm / nhược điểm của phương pháp đề cập của bạn so với OP
Stefan Hegny 22/03/2017

3

TL; DR

Sử dụng bất kỳ. Chúng gần như giống nhau.

Chán câu trả lời

Như thường lệ, có những ưu và nhược điểm.

Sử dụng std::reverse_iterator:

  • Khi bạn sắp xếp các loại tùy chỉnh và bạn không muốn thực hiện operator>()
  • Khi bạn quá lười để gõ std::greater<int>()

Sử dụng std::greaterkhi:

  • Khi bạn muốn có nhiều mã rõ ràng hơn
  • Khi bạn muốn tránh sử dụng các trình vòng lặp ngược tối nghĩa

Đối với hiệu suất, cả hai phương pháp đều hiệu quả như nhau. Tôi đã thử điểm chuẩn sau:

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

Với dòng lệnh này:

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

Thời gian là như nhau. Valgrind báo cáo cùng một số lỗi bộ nhớ cache.


2

Tôi không nghĩ bạn nên sử dụng một trong hai phương pháp trong câu hỏi vì cả hai đều khó hiểu và phương pháp thứ hai rất mong manh như Mehrdad gợi ý.

Tôi sẽ ủng hộ những điều sau đây, vì nó trông giống như một chức năng thư viện tiêu chuẩn và làm cho ý định của nó rõ ràng:

#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
Điều này giống như khó hiểu hơn gấp ngàn lần so với việc chỉ sử dụng bộ std::greaterso sánh ....
Apollys hỗ trợ Monica

@Apollys Tôi đồng ý rằng bắt đầu với C ++ 14, std :: lớn hơn <> trông giống như giải pháp ưa thích. Nếu bạn không có C ++ 14, nó vẫn có thể hữu ích nếu bạn muốn loại trừ bất kỳ sự ngạc nhiên nào với std :: Greater <int> (ví dụ: khi các loại tại một số điểm thay đổi từ int sang long).
Philipp Claßen

2

Bạn có thể sử dụng mã đầu tiên hoặc thử mã dưới đây có hiệu quả tương đương

sort(&a[0], &a[n], greater<int>());
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.