Mengapa Radix Sort tidak digunakan lebih sering?

31

Ini stabil dan memiliki kompleksitas waktu O (n). Seharusnya lebih cepat daripada algoritma seperti Quicksort dan Mergesort, namun saya jarang melihatnya menggunakannya.

Queequeg
sumber
2
Lihat di sini: en.wikipedia.org/wiki/Radix_sort#Efficiency Efisiensinya O (kn) dan mungkin tidak lebih baik daripada O (n * log (n)).
FrustratedWithFormsDesigner
2
Radix sort sering digunakan dalam sistem waktu nyata yang lunak seperti game. Apakah atau tidak satu algoritma mengungguli yang lain, seperti biasa, tergantung pada semua parameter masalah, bukan hanya kompleksitas yang terikat
awdz9nld
@FrustratedWithFormsDesigner Mungkin wiki telah berubah? Saya tidak melihat referensi ke `n log (n) lagi, FWIW ...
rogerdpack
Boost memiliki (di tempat varian): boost.org/doc/libs/1_62_0/libs/sort/doc/html/sort/sort_hpp.html tapi ya, saya pikir orang tidak tahu itu ada ... baik itu atau mereka semua hanya menggunakan algoritma pengurutan "standar" yang, untuk alasan apa pun, pembuat kerangka cenderung masih menggunakan kembali jenis "generik" yang tidak seefisien ... mungkin mereka tidak fokus pada pengurutan int biasanya, karena ini kasus penggunaan yang lebih jarang?
rogerdpack

Jawaban:

38

Tidak seperti radix sort, quicksort bersifat universal, sedangkan radix sort hanya berguna untuk memperbaiki kunci integer panjang.

Anda juga harus mengerti, bahwa O (f (n)) benar-benar berarti dalam urutan K * f (n), di mana K adalah konstanta arbitrer. Untuk radix sort K ini cukup besar (setidaknya urutan jumlah bit dalam integer yang diurutkan), di sisi lain quicksort memiliki salah satu K terendah di antara semua algoritma pengurutan dan kompleksitas rata-rata n * log (n). Jadi dalam skenario kehidupan nyata quicksort akan sangat sering lebih cepat daripada jenis radix.

vartec
sumber
Catatan tentang kompleksitas yang dinyatakan: meskipun (LSD) Radix sort memiliki kompleksitas O (n * K), konstanta ini biasanya kecil, biasanya dipilih sedemikian rupa sehingga (2 ^ (W / K)) * C cocok ke L1, di mana C adalah ukuran dalam byte dari penghitung, W ukuran kunci yang diurutkan. Sebagian besar implementasi memilih K = [3,4] untuk kata-kata 32-bit pada x86. K juga dapat dibuat adaptif untuk mengeksploitasi koherensi temporal (near-sortness), karena setiap radix diurutkan secara individual.
awdz9nld
11
Catatan tentang universalitas: Radix sort sepenuhnya mampu beroperasi pada kunci floating-point, serta kunci integer panjang variabel
awdz9nld
20

Sebagian besar algoritma penyortiran adalah untuk tujuan umum. Diberikan fungsi perbandingan, mereka bekerja pada apa saja, dan algoritma seperti Quicksort dan Heapsort akan mengurutkan dengan O (1) memori tambahan.

Penyortiran radix lebih terspesialisasi. Anda memerlukan kunci khusus yang berada dalam urutan leksikografis. Anda perlu satu ember untuk setiap simbol yang mungkin ada di kunci, dan ember harus menyimpan banyak catatan. (Secara bergantian, Anda membutuhkan satu set besar ember yang akan menampung setiap nilai kunci yang mungkin.) Anda cenderung membutuhkan lebih banyak memori untuk melakukan radix sort, dan Anda akan menggunakannya secara acak. Tidak satu pun dari ini baik untuk komputer modern, karena Anda kemungkinan akan mendapatkan kesalahan halaman seperti Quicksort akan mendapatkan cache miss.

Akhirnya, orang-orang pada umumnya tidak menulis algoritma sorting mereka sendiri lagi. Sebagian besar bahasa memiliki fasilitas perpustakaan untuk disortir, dan hal yang benar untuk dilakukan adalah menggunakannya. Karena radix sort tidak dapat diterapkan secara universal, biasanya harus disesuaikan dengan penggunaan aktual, dan menggunakan banyak memori tambahan, sulit untuk memasukkannya ke fungsi pustaka atau templat.

David Thornley
sumber
Sebenarnya, quicksort membutuhkan O(n^2)memori dalam kasus terburuk karena npanggilan rekursif di partisi kiri dan kanan. Jika implementasi menggunakan optimisasi rekursi ekor, yang dapat diturunkan hanya O(n)karena panggilan ke partisi yang tepat tidak akan memerlukan ruang tambahan. ( en.wikipedia.org/wiki/Quicksort#Space_complexity )
Splinter of Chaos
Anda hanya perlu S(n) \in O(n)ruang untuk menyortir dengan radix, yaitu sama seperti untuk heap atau sortir cepat.
Velda
@SplinterofChaos mungkin wiki berubah? Sepertinya tidak disebutkan n^2untuk quicksort lagi, tapi O(log n)...
rogerdpack
Saya tidak berpikir itu "banyak" lebih banyak memori, mungkin 2 * n (OK itu lebih banyak tapi mungkin bukan tidak mungkin)? Dan bucket sangat kecil (dengan asumsi Anda membelah byte dan berulang) sehingga bisa masuk ke cache?
rogerdpack
5

Sangat jarang bahwa kunci yang Anda sortir sebenarnya adalah bilangan bulat dalam rentang yang dikenal dan jarang. Biasanya Anda memiliki bidang abjad, yang terlihat seperti mereka akan mendukung penyortiran non-komparatif, tetapi karena string dunia nyata tidak terdistribusi secara merata di seluruh alfabet, ini tidak bekerja sebagai baik sebagaimana mestinya dalam teori.

Di lain waktu, kriteria didefinisikan hanya secara operasional (diberikan dua catatan, Anda dapat memutuskan mana yang lebih dulu, tetapi Anda tidak dapat menilai seberapa jauh 'skala' dari catatan yang terisolasi itu). Jadi metode ini sering tidak berlaku, kurang berlaku dari yang Anda yakini, atau tidak lebih cepat dari O (n * log (n)).

Kilian Foth
sumber
Urutan Radix dapat menangani bilangan bulat (atau string) dalam rentang apa pun dengan secara rekursi mengurutkan mereka "byte pada suatu waktu" sehingga mereka tidak harus berada dalam rentang yang jarang FWIW ...
rogerdpack
4

Saya menggunakannya sepanjang waktu, sebenarnya lebih dari jenis berbasis perbandingan, tapi saya diakui orang aneh yang bekerja lebih banyak dengan angka daripada yang lain (saya hampir tidak pernah bekerja dengan string, dan mereka umumnya diinternir jika demikian pada titik mana radix pengurutan dapat berguna lagi untuk memfilter duplikat dan menghitung set persimpangan; Saya praktis tidak pernah melakukan perbandingan leksikografis).

Contoh dasar adalah titik penyortiran radix oleh dimensi yang diberikan sebagai bagian dari pencarian atau median split atau cara cepat untuk mendeteksi titik bertepatan, fragmen penyortiran kedalaman, atau penyortiran radix array indeks yang digunakan dalam banyak loop untuk memberikan akses yang lebih ramah terhadap cache pola (tidak bolak-balik dalam memori hanya untuk kembali lagi dan memuat kembali memori yang sama ke dalam garis cache). Ada aplikasi yang sangat luas setidaknya di domain saya (grafik komputer) hanya untuk mengurutkan pada tombol numerik 32-bit dan 64-bit berukuran tetap.

Satu hal yang ingin saya sampaikan dan katakan adalah bahwa radix sort dapat bekerja pada angka floating-point dan negatif, meskipun sulit untuk menulis versi FP yang se portable mungkin. Juga saat O (n * K), K hanya harus menjadi jumlah byte dari ukuran kunci (mis: sejuta bilangan bulat 32-bit biasanya akan mengambil lintasan berukuran 4 byte jika ada 2 ^ 8 entri dalam bucket ). Pola akses memori juga cenderung jauh lebih ramah-cache daripada quicksort meskipun itu membutuhkan array paralel dan array ember kecil biasanya (yang kedua biasanya dapat masuk dengan baik pada stack). QS mungkin melakukan 50 juta swap untuk mengurutkan array sejuta bilangan bulat dengan pola akses acak sporadis. Urutan radix dapat melakukan itu dalam 4 linier, cache-friendly melewati data.

Namun, kurangnya kesadaran untuk dapat melakukan ini dengan K kecil, pada bilangan negatif bersama dengan floating-point, mungkin sangat berkontribusi signifikan terhadap kurangnya popularitas jenis radix.

Adapun pendapat saya tentang mengapa orang tidak menggunakannya lebih sering, mungkin ada hubungannya dengan banyak domain yang umumnya tidak memiliki kebutuhan untuk mengurutkan angka atau menggunakannya sebagai kunci pencarian. Namun, hanya berdasarkan pengalaman pribadi saya, banyak mantan rekan kerja saya juga tidak menggunakannya dalam kasus-kasus di mana itu sangat cocok, dan sebagian karena mereka tidak sadar bahwa itu dapat dilakukan untuk bekerja pada FP dan negatif. Jadi selain itu hanya bekerja pada jenis numerik, sering dianggap lebih umum berlaku daripada yang sebenarnya. Saya tidak akan memiliki banyak gunanya jika saya pikir itu tidak bekerja pada angka floating-point dan bilangan bulat negatif.

Beberapa tolok ukur:

Sorting 10000000 elements 3 times...

mt_sort_int: {0.135 secs}
-- small result: [ 12 17 17 22 52 55 67 73 75 87 ]

mt_radix_sort: {0.228 secs}
-- small result: [ 12 17 17 22 52 55 67 73 75 87 ]

std::sort: {1.697 secs}
-- small result: [ 12 17 17 22 52 55 67 73 75 87 ]

qsort: {2.610 secs}
-- small result: [ 12 17 17 22 52 55 67 73 75 87 ]

Dan itu hanya dengan implementasi naif saya ( mt_sort_intjuga penyortiran radix tetapi dengan cabang kode yang lebih cepat mengingat dapat menganggap kuncinya adalah integer). Bayangkan seberapa cepat implementasi standar yang ditulis oleh para ahli mungkin.

Satu-satunya kasus di mana saya menemukan jenis radix lebih buruk daripada perbandingan berbasis C ++ yang sangat cepat std::sortadalah untuk sejumlah kecil elemen, katakanlah 32, pada titik mana saya percaya std::sortmulai menggunakan jenis yang lebih cocok untuk sejumlah kecil elemen seperti heapsort atau jenis penyisipan, meskipun pada saat itu implementasi saya hanya menggunakan std::sort.


sumber
1
Selalu senang mendengar pendapat orang-orang dengan pengalaman di daerah tersebut.
Frank Hileman
Mt_ muncul adalah implementasi multi-threaded: softwareengineering.stackexchange.com/a/362097/65606
rogerdpack
1

Satu lagi alasan: Penyortiran hari-hari ini biasanya diimplementasikan dengan rutin penyortiran yang disediakan oleh pengguna yang dilampirkan pada logika penyortiran yang disediakan oleh kompiler. Dengan radix sort, ini akan menjadi jauh lebih kompleks dan semakin buruk ketika rutin sortir bekerja pada beberapa kunci dengan panjang variabel. (Katakan, nama, dan tanggal lahir.)

Di dunia nyata saya benar-benar telah menerapkan jenis radix sekali. Ini adalah masa lalu ketika memori terbatas, saya tidak bisa membawa semua data saya ke memori sekaligus. Itu berarti bahwa jumlah akses ke data jauh lebih penting daripada O (n) vs O (n log n). Saya membuat satu lintasan melintasi data yang mengalokasikan setiap catatan ke nampan (dengan daftar yang berisi catatan di mana nampan, tidak benar-benar memindahkan apa pun.) Untuk setiap nampan kosong (kunci pengurutan saya adalah teks, akan ada banyak bins kosong) Saya memeriksa apakah saya benar-benar bisa membawa data ke dalam memori - jika ya, bawa dan gunakan quicksort. Jika tidak, buat file temp yang hanya berisi item dalam nampan dan panggil rutinnya secara rekursif. (Dalam praktiknya, beberapa tempat sampah akan meluap.) Ini menyebabkan dua pembacaan lengkap dan satu penulisan lengkap untuk penyimpanan jaringan dan sekitar 10% dari ini untuk penyimpanan lokal.

Saat ini masalah big-data seperti itu jauh lebih sulit untuk dihadapi, saya mungkin tidak akan pernah menulis hal seperti itu lagi. (Jika saya dihadapkan dengan data yang sama hari ini saya hanya akan menentukan OS 64-bit, tambahkan RAM jika Anda meronta-ronta di editor itu.)

Loren Pechtel
sumber
Menarik mengingat salah satu kelemahan yang disebutkan untuk jenis radix kadang-kadang disebutkan adalah "dibutuhkan lebih banyak ruang." Masih mencoba membungkus kepalaku di sekitar ini ...
rogerdpack
1
@rogerdpack Bukan karena pendekatan saya menggunakan lebih sedikit ruang, itu karena ia menggunakan lebih sedikit akses ke data. Saya sedang menyortir file yang sekitar satu gigabyte ketika berhadapan dengan batas kompiler (ini adalah mode yang dilindungi DOS, bukan Windows) yang sedikit di bawah 16mb dari total penggunaan memori termasuk kode dan batas struktur 64kb.
Loren Pechtel
-1

Jika semua parameter Anda adalah bilangan bulat dan jika Anda memiliki lebih dari 1024 parameter input, maka jenis radix selalu lebih cepat.

Mengapa?

Complexity of radix sort = max number of digits x number of input parameters.

Complexity of quick sort = log(number of input parameters) x   number of input parameters

Jadi radix sort lebih cepat ketika

log(n)> max num of digits

Bilangan bulat maks di Jawa adalah 2147483647. Panjangnya 10 digit

Jadi radix sort selalu lebih cepat kapan

log(n)> 10

Oleh karena itu jenis radix selalu lebih cepat ketika n>1024

developer747
sumber
Ada konstanta tersembunyi dalam detail implementasi, tetapi pada dasarnya Anda mengatakan "untuk input radix yang lebih besar lebih cepat" yang ... harusnya menjadi masalah! Hanya sulit untuk menemukan kasus penggunaan untuk itu tetapi ketika Anda bisa ...
rogerdpack