การเรียงลำดับเวกเตอร์ตามลำดับจากมากไปน้อย


310

ฉันควรใช้

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

หรือ

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

จะจัดเรียงเวกเตอร์ตามลำดับจากมากไปน้อยเป็นอย่างไร มีประโยชน์หรือข้อเสียเปรียบกับวิธีใดวิธีหนึ่งหรือไม่?


2
+1 ฉันคิดว่าคำตอบนั้นชัดเจน แต่คำถามนี้มีเรื่องเล็กน้อยที่น่าสนใจ :)
wilhelmtell

3
ฉันจะลงคะแนนเสียงให้ตัวเลือกแรกเพียงเพราะแล้วฉันจะไม่เคยมีการจัดการกับreverse_iterator's
evandrix

2
@wilhelmtell คำถาม noob แต่ทำไมคนที่สองควรเรียงลำดับจากมากไปน้อย เราให้อาเรย์เดียวกับวิธีการเรียงลำดับ มันเป็นเพียงแค่ว่าเราจะให้มันในลำดับย้อนกลับดังนั้นทำไมมันควรจะเรียงลำดับจากมากไปน้อยและไม่ได้เรียงตามลำดับเช่นกรณีที่มี ar.begin () และ ar.end
shshnk

6
@shshnk std::sort(b, e);กำหนดขั้นต่ำที่b(ในกรณีของเราrbeginดังนั้นองค์ประกอบสุดท้าย ) และสูงสุดที่e(ในกรณีของเราrendดังนั้นองค์ประกอบแรก )
fredoverflow

คำตอบ:


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ควรถือlongหรือแทนlong longint


1
@FredOverflow: คุณได้รับเกียรติในความคิดเห็นของคุณ;)
user541686

2
หรือติดกับอันแรก ใช้ typedef สำหรับ numberContainer - เป็นความคิดที่ดีเพื่อให้ใครบางคนสามารถสลับเป็นความยาว - และเขียน: std :: sort (numbers.begin (), numbers.end (), std :: Greater <numContainer :: value_type> ( ));
RichardHowells

1
+1 คนแรกสับสนจริงๆ เป็นอะไรgreaterมากกว่าคนอื่น ๆ ? rbeginและrendถูกสร้างขึ้นเพื่อวัตถุประสงค์เฉพาะ
Abhishek Divekar

6
ทำไมไม่เพียงstd::greater<typename decltype(numbers)::value_type>()หรืออะไร
einpoklum

1
คำตอบนี้ล้าสมัยแล้ว - คุณสามารถใช้std::greater<>()ตั้งแต่ C ++ 14
นิโคไล

70

ใช้ครั้งแรก:

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

มันเป็นที่ชัดเจนของสิ่งที่เกิดขึ้น - มีโอกาสน้อยกว่าของ misreading rbeginเป็นbeginแม้จะมีการแสดงความคิดเห็น มันชัดเจนและสามารถอ่านได้ซึ่งเป็นสิ่งที่คุณต้องการ

นอกจากนี้อันที่สองอาจมีประสิทธิภาพน้อยกว่าครั้งแรกที่กำหนดลักษณะของตัววนซ้ำแบบย้อนกลับแม้ว่าคุณจะต้องทำโปรไฟล์ให้แน่ใจ



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
@ รวม O (n * log (n)) = O (n * log (n) + n) พวกเขาเป็นสองวิธีในการกำหนดชุดเดียวกัน คุณหมายถึงว่า "สิ่งนี้อาจช้ากว่านี้"
pjvandehaar

4
@pjvandehaar Greg ไม่เป็นไร เขาไม่ได้บอกอย่างชัดเจนว่า O (n * log (n) + n) เขาพูดว่า O (n * log (n)) + O (n) คุณพูดถูกว่าถ้อยคำของเขาไม่ชัดเจน (โดยเฉพาะการใช้คำที่ซับซ้อนในทางที่ผิด) แต่คุณอาจตอบด้วยวิธีที่สุภาพกว่า เช่นคุณอาจหมายถึงการใช้คำว่า 'การคำนวณ' แทนคำว่า 'ความซับซ้อน' การกลับหมายเลขเป็นขั้นตอน O (n) ที่ไม่จำเป็นไปยังขั้นตอน O (n * log (n)) ที่เหมือนกัน
Ofek Gila

3
เข้าใจ @OfekGila ของฉันอยู่ที่สัญกรณ์ใหญ่-O เป็นเรื่องเกี่ยวกับชุดของฟังก์ชั่นและสัญกรณ์ที่เกี่ยวข้อง=และ+เป็นเพียงสิ่งอำนวยความสะดวกความหมายและ ในกรณีที่O(n*log(n)) + O(n)เป็นสัญกรณ์สะดวกสำหรับการซึ่งเป็นเช่นเดียวกับO(n*log(n)) ∪ O(n) O(n*log(n))คำว่า "การคำนวณ" เป็นคำแนะนำที่ดีและคุณพูดถูก
pjvandehaar

22

แทนที่จะเป็นนักแสดงนำเสนอตาม Mehrdad คุณสามารถใช้ฟังก์ชั่นแลมบ์ดา

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

16

ตามเครื่องของฉันการเรียงlong longเวกเตอร์ [1..3000000] โดยใช้วิธีแรกใช้เวลาประมาณ 4 วินาทีในขณะที่การใช้ครั้งที่สองใช้เวลาประมาณสองครั้ง แต่ฉันไม่เข้าใจว่าทำไม แค่คิดว่านี่จะเป็นประโยชน์

สิ่งเดียวกันรายงานที่นี่

ดังที่ Xeo กล่าวโดย-O3พวกเขาใช้เวลาเดียวกันในการทำให้เสร็จ


12
คุณอาจไม่ได้คอมไพล์ด้วยการเพิ่มประสิทธิภาพที่เปิดอยู่หรือไม่ ฟังดูคล้ายกับreverse_iteratorการปฏิบัติการที่ไม่ได้อยู่ในบรรทัดและเนื่องจากพวกเขาเป็นเพียงเสื้อคลุมรอบตัววนซ้ำจริง ๆ จึงไม่น่าแปลกใจเลยที่พวกเขาจะใช้เวลาเป็นสองเท่าโดยไม่ต้องมีอินไลน์
Xeo

@Xeo แม้ว่าพวกเขาจะถูกนำมาใช้ในการใช้งานบางอย่างเพิ่มการอ้างอิงต่อ
Pubby

@ildjarn: เพราะมันเป็นอย่างนั้น? base()ฟังก์ชันสมาชิกเช่นส่งกลับ iterator ห่อ
Xeo

1
@Xeo ตอนนี้พวกเขาทั้งสองเสร็จในไม่กี่วินาที ขอบคุณ!
zw324

3
@Xeo: ฉันจะเอามันกลับมา; มาตรฐานจริงเอกสารที่จะดำเนินการในแง่ของstd::vector<>::reverse_iterator std::reverse_iterator<>แปลกประหลาด; วันนี้ฉันเรียน :-P
ildjarn

11

วิธีแรกหมายถึง:

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

คุณอาจใช้วิธีแรกเนื่องจากประสิทธิภาพเพิ่มขึ้นกว่าวินาที
ความซับซ้อนของเวลาวิธีแรกคือน้อยกว่าที่สอง


นี่เป็นคำตอบเดียวกับที่ mrexciting ใช้ ข้อสังเกตเกี่ยวกับความซับซ้อนยังไม่ชัดเจนสำหรับฉัน
Philipp Claßen

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 รองรับ Monica

@Apollys ฉันยอมรับว่าเริ่มต้นด้วย C ++ 14, std :: Greater <> ดูเหมือนว่าโซลูชันที่ต้องการ หากคุณไม่มี C ++ 14 อาจเป็นประโยชน์หากคุณต้องการแยกความประหลาดใจด้วย std :: มากกว่า <int> (เช่นเมื่อประเภทที่บางจุดเปลี่ยนจาก int เป็นยาว)
Philipp Claßen

2

คุณสามารถใช้อันแรกหรือลองใช้โค้ดด้านล่างซึ่งมีประสิทธิภาพเท่าเทียมกัน

sort(&a[0], &a[n], greater<int>());
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.