Sorting hanyalah algoritma lain
Di halaman sebelumnya kamu sudah melihat bahwa library standar menyediakan algoritma siap pakai yang bekerja pada range apa pun lewat iterator. Sorting adalah algoritma yang paling sering kamu pakai, dan ia punya halaman sendiri karena ada beberapa bagian tajamnya: urutan kustom, stabilitas, dan satu aturan yang kalau dilanggar akan memberimu undefined behavior, bukan sekadar jawaban yang salah.
Andalan utamanya adalah std::sort dari <algorithm>. Kamu memberinya awal dan akhir sebuah range, lalu ia menyusun ulang elemen-elemennya di tempat dalam urutan naik:
Tidak ada salinan yang dibuat: vector itu sendiri yang disusun ulang. Di baliknya, std::sort biasanya berupa introsort (quicksort yang beralih ke heapsort bila perlu), dengan rata-rata O(n log n). Itu hampir selalu lebih cepat dan jauh lebih kecil kemungkinan salahnya daripada menulis algoritma sorting sendiri.
Ia juga bekerja pada array C biasa, kamu cukup menggambarkan range-nya dengan pointer:
Urutan kustom dengan comparator
Secara default std::sort mengurutkan elemen dengan operator<. Untuk mengurutkan dengan cara lain, kirim argumen ketiga: comparator yang menerima dua elemen dan mengembalikan true kalau elemen pertama harus berada sebelum elemen kedua.
Lambda sangat cocok untuk ini. Urutan menurun cukup ditulis a > b:
Untuk kasus umum urutan menurun pada tipe bawaan, library bahkan menyediakan comparator siap pakai, yaitu greater<T>() dari <functional>:
#include <functional>
sort(nums.begin(), nums.end(), greater<int>()); // same as a > b
Comparator juga merupakan cara untuk mengurutkan berdasarkan sesuatu selain nilainya sendiri, misalnya mengurutkan string berdasarkan panjangnya, bukan berdasarkan abjad:
Terima parameter comparator sebagai const& untuk apa pun yang lebih besar dari beberapa byte (seperti string). Menyalin setiap elemen di setiap perbandingan benar-benar pemborosan.
Mengurutkan struct berdasarkan field
Di program sungguhan kamu biasanya mengurutkan kumpulan struct berdasarkan salah satu field-nya. Comparator cukup mengambil field yang kamu pedulikan. Di sini kita mengurutkan orang berdasarkan umur, dari yang termuda:
Perhatikan bahwa Linus dan Dennis sama-sama berumur 25. Di sini mereka keluar dalam urutan relatif aslinya, tapi std::sort tidak menjamin hal itu. Kalau urutan relatif elemen yang sama itu penting, pakai std::stable_sort, yang mempertahankannya (dengan sedikit biaya performa):
Untuk menentukan pemenang saat nilainya sama secara sengaja, misalnya mengurutkan berdasarkan umur lalu berdasarkan nama sesuai abjad, bandingkan key kedua hanya saat key utamanya sama. std::tie membuatnya rapi:
Jebakan strict weak ordering
Ini kesalahan paling berbahaya dalam sorting di C++, karena hasilnya bukan jawaban yang salah, melainkan undefined behavior, yang sering berarti crash atau pembacaan di luar batas.
std::sort mengharuskan comparator-mu mendefinisikan strict weak ordering. Aturan praktisnya: comp(x, x) harus false untuk elemen x mana pun. Dengan kata lain, sebuah elemen tidak pernah berada "sebelum" dirinya sendiri. Itulah yang diberikan < dan >, dan itulah yang dilanggar <= dan >=:
// BUG: returns true when a == b, violating strict weak ordering.
sort(v.begin(), v.end(), [](int a, int b) {
return a <= b; // undefined behavior - may crash on some inputs
});
Dengan <=, comparator mengklaim bahwa 5 berada sebelum 5 yang lain, dan itu kontradiktif. std::sort lalu bisa menggeser pointer melewati ujung range. Input yang kecil kadang kelihatan berjalan normal, dan itulah yang membuat bug ini menakutkan: ia bisa lolos dari tes lalu crash di production. Solusinya cukup <:
Jebakan klasik kedua: sorting membuat tidak valid apa pun yang menunjuk ke dalam range itu. Iterator, pointer, dan indeks yang kamu simpan sebelum sorting tidak lagi merujuk ke elemen logis yang sama setelahnya, karena elemen-elemennya sudah berpindah. Tentukan ulang posisi yang kamu butuhkan setelah sorting, jangan sebelumnya.
Mengurutkan sebagian range
Kadang kamu tidak butuh semuanya terurut, kamu hanya ingin beberapa yang teratas. Mengurutkan seluruh vector hanya untuk membaca tiga elemen pertama itu boros. std::partial_sort hanya menyusun elemen yang kamu minta dan membiarkan sisanya dalam urutan yang tidak ditentukan, dan itu lebih murah:
Kalau kamu hanya butuh satu elemen yang seharusnya berada di posisi tertentu, seperti median, std::nth_element bekerja lebih sedikit lagi: ia menaruh elemen yang tepat di indeks itu, dengan semua yang lebih kecil sebelumnya dan semua yang lebih besar sesudahnya, dengan rata-rata O(n).
Pakai keduanya saat "terurut penuh" lebih dari yang sebenarnya dibutuhkan masalahmu. Keduanya menghemat waktu yang nyata pada data besar.
Berikutnya: template
Sudahkah kamu memperhatikan bahwa std::sort yang sama bisa menangani int, string, dan struct Person buatanmu sendiri, dan greater<int>() bisa saja menjadi greater<string>()? Keumuman itu bukan sihir, melainkan template, mekanisme yang membuat satu potong kode bisa bekerja untuk tipe apa pun yang dipasang pemanggilnya. Di halaman berikutnya kita akan melihat cara menulis fungsi dan class template sendiri, supaya kodemu bisa bekerja dengan tipe apa pun, sama seperti algoritma yang selama ini kamu pakai.
Pertanyaan yang sering diajukan
Bagaimana cara mengurutkan vector di C++?
Sertakan <algorithm> lalu panggil sort(v.begin(), v.end()). Ini mengurutkan elemen di tempatnya dalam urutan naik memakai operator<. Untuk mengurutkan array, kirim arr dan arr + n (atau begin(arr) / end(arr)).
Bagaimana cara mengurutkan secara menurun di C++?
Kirim comparator yang mengembalikan a > b: sort(v.begin(), v.end(), [](int a, int b){ return a > b; });. Kamu juga bisa memakai yang bawaan, sort(v.begin(), v.end(), greater<int>()); dari <functional>.
Kenapa comparator C++ milikku membuat std::sort crash?
Comparator-mu harus berupa strict weak ordering: ia harus mengembalikan false saat kedua argumennya sama. Memakai <= atau >= (yang mengembalikan true untuk elemen yang sama) melanggar aturan itu dan termasuk undefined behavior: std::sort bisa membaca di luar batas lalu crash. Selalu bandingkan dengan < atau >.