Tại sao Radix Sort không được sử dụng thường xuyên hơn?


31

Nó ổn định và có độ phức tạp thời gian là O (n). Nó phải nhanh hơn các thuật toán như Quicksort và Mergesort, nhưng tôi hầu như không bao giờ thấy nó được sử dụng.


2
Xem tại đây: vi.wikipedia.org/wiki/Radix_sort#Effic Hiệu quả là O (kn) và nó có thể không tốt hơn O (n * log (n)).
Thất vọngWithFormsDesigner

2
Radix sort thường được sử dụng trong các hệ thống thời gian thực mềm như game. Liệu một thuật toán có tốt hơn một thuật toán hay không, như thường lệ, phụ thuộc vào tất cả các tham số của vấn đề, không chỉ là độ phức tạp bị ràng buộc
awdz9nld

@FrustratedWithFormsDesigner Có lẽ wiki đã thay đổi? Tôi không thấy tài liệu tham khảo về `n log (n) nữa, FWIW ...
rogerdpack

Boost có một (biến thể tại chỗ) của nó: boost.org/doc/libs/1_62_0/libs/sort/doc/html/sort/sort_hpp.html nhưng vâng, tôi nghĩ mọi người chỉ không biết nó tồn tại ... hoặc là tất cả họ chỉ sử dụng thuật toán sắp xếp "tiêu chuẩn", vì lý do nào, người tạo khung có xu hướng vẫn sử dụng lại các loại "chung" không hiệu quả ... có thể họ không tập trung vào sắp xếp ints thông thường, vì nó là trường hợp sử dụng hiếm hơn?
rogerdpack

Câu trả lời:


38

Không giống như sắp xếp cơ số, quicksort là phổ quát, trong khi sắp xếp cơ số chỉ hữu ích cho việc sửa các khóa số nguyên có độ dài.

Ngoài ra, bạn phải hiểu rằng O (f (n)) thực sự có nghĩa là theo thứ tự K * f (n), trong đó K là một hằng số tùy ý. Đối với sắp xếp cơ số, K này xảy ra khá lớn (ít nhất là thứ tự số bit trong số nguyên được sắp xếp), mặt khác quicksort có một trong những K thấp nhất trong số tất cả các thuật toán sắp xếp và độ phức tạp trung bình của n * log (n). Do đó, trong kịch bản đời thực, quicksort sẽ rất nhanh hơn so với radix sort.


Lưu ý về độ phức tạp đã nêu: mặc dù (LSD) Loại Radix có độ phức tạp là O (n * K), hằng số này thường nhỏ, thường được chọn sao cho (2 ^ (W / K)) * C phù hợp với L1, trong đó C là kích thước tính theo byte của bộ đếm, W kích thước của khóa được sắp xếp. Hầu hết các triển khai đều chọn K = [3,4] cho các từ 32 bit trên x86. K cũng có thể được thực hiện để thích ứng để khai thác sự kết hợp thời gian (gần sắp xếp), vì mỗi cơ số được sắp xếp riêng lẻ.
awdz9nld

11
Lưu ý về tính phổ quát: Radix sort hoàn toàn có khả năng hoạt động trên các phím dấu phẩy động, cũng như các khóa nguyên có độ dài thay đổi
awdz9nld 18/07/14

20

Hầu hết các thuật toán sắp xếp là mục đích chung. Với một chức năng so sánh, chúng hoạt động trên mọi thứ và các thuật toán như Quicksort và Heapsort sẽ sắp xếp với bộ nhớ phụ O (1).

Phân loại Radix là chuyên ngành hơn. Bạn cần một khóa cụ thể theo thứ tự từ điển. Bạn cần một thùng cho mỗi ký hiệu có thể có trong khóa và các thùng cần chứa nhiều hồ sơ. (Cách khác, bạn cần một mảng lớn sẽ chứa mọi giá trị khóa có thể.) Bạn có thể sẽ cần nhiều bộ nhớ hơn để thực hiện sắp xếp cơ số và bạn sẽ sử dụng nó một cách ngẫu nhiên. Cả hai điều này đều không tốt cho các máy tính hiện đại, vì bạn có thể gặp lỗi trang như Quicksort sẽ bị lỗi bộ nhớ cache.

Cuối cùng, mọi người nói chung không viết các thuật toán sắp xếp của riêng họ nữa. Hầu hết các ngôn ngữ đều có các phương tiện thư viện để sắp xếp, và điều đúng đắn thường làm là sử dụng chúng. Vì loại radix không được áp dụng phổ biến, thường phải được điều chỉnh theo mục đích sử dụng thực tế và sử dụng nhiều bộ nhớ bổ sung, thật khó để đưa nó vào chức năng thư viện hoặc mẫu.


Trên thực tế, quicksort yêu cầu O(n^2)bộ nhớ trong trường hợp xấu nhất do ncác cuộc gọi đệ quy trên các phân vùng bên trái và bên phải. Nếu việc triển khai sử dụng tối ưu hóa đệ quy đuôi, điều đó có thể được hạ xuống chỉ O(n)vì các lệnh gọi đến phân vùng bên phải sẽ không cần thêm dung lượng. ( en.wikipedia.org/wiki/Quicksort#Space_complexity )
Splinter of Chaos

Bạn chỉ cần S(n) \in O(n)không gian để sắp xếp với cơ số, tức là tương tự như cho heap hoặc sắp xếp nhanh.
Velda

@SplinterofChaos có wiki đã thay đổi? Nó dường như không đề cập đến n^2quicksort nữa, nhưng O(log n)...
rogerdpack

Tôi không nghĩ đó là "nhiều" bộ nhớ hơn, có thể là 2 * n (OK đó là nhiều hơn nhưng có thể không phải là không thể)? Và các thùng quá nhỏ (giả sử bạn đang phân tách byte và đệ quy) để nó có thể phù hợp với bộ đệm?
rogerdpack

5

Thật hiếm khi các khóa bạn sắp xếp thực sự là các số nguyên trong một phạm vi thưa thớt, đã biết. Thông thường bạn có các trường chữ cái, trông giống như chúng sẽ hỗ trợ sắp xếp không so sánh, nhưng vì các chuỗi trong thế giới thực không được phân bổ đều trên bảng chữ cái, nên về mặt lý thuyết, nó không hoạt động tốt như vậy.

Những lần khác, tiêu chí chỉ được xác định một cách vận hành (được đưa ra hai bản ghi, bạn có thể quyết định cái nào đến trước, nhưng bạn không thể đánh giá mức độ "xa" của một bản ghi bị cô lập). Vì vậy, phương pháp này thường không được áp dụng, ít áp dụng hơn bạn có thể tin hoặc không nhanh hơn O (n * log (n)).


Radix sort có thể xử lý các số nguyên (hoặc chuỗi) trong bất kỳ phạm vi nào bằng cách sắp xếp đệ quy chúng "một byte tại một thời điểm" để chúng không phải ở trong phạm vi thưa thớt FWIW ...
rogerdpack

4

Tôi sử dụng nó mọi lúc, thực tế nhiều hơn các loại dựa trên so sánh, nhưng tôi thừa nhận là một kẻ kỳ quặc hoạt động với số lượng nhiều hơn bất kỳ thứ gì khác (tôi hầu như không bao giờ làm việc với các chuỗi, và chúng thường được thực hiện nếu tại thời điểm đó radix sắp xếp có thể hữu ích một lần nữa để lọc ra các bản sao và tính toán các giao điểm thiết lập; tôi thực tế không bao giờ thực hiện so sánh từ điển).

Một ví dụ cơ bản là các điểm sắp xếp cơ số theo một thứ nguyên nhất định như là một phần của phân chia tìm kiếm hoặc phân chia trung bình hoặc một cách nhanh chóng để phát hiện các điểm trùng khớp, phân đoạn độ sâu hoặc sắp xếp một chuỗi các chỉ mục được sử dụng trong nhiều vòng để cung cấp quyền truy cập thân thiện hơn vào bộ đệm các mẫu (không quay đi quay lại trong bộ nhớ chỉ để quay lại lần nữa và tải lại cùng bộ nhớ vào một dòng bộ đệm). Có một ứng dụng rất rộng ít nhất là trong miền của tôi (đồ họa máy tính) chỉ để sắp xếp trên các phím số 32 bit và 64 bit có kích thước cố định.

Một điều tôi muốn nói và nói rằng loại radix có thể hoạt động trên các số và dấu phẩy động, mặc dù rất khó để viết một phiên bản FP dễ mang theo nhất có thể. Ngoài ra, trong khi đó là O (n * K), K chỉ phải là số byte có kích thước khóa (ví dụ: một triệu số nguyên 32 bit thường sẽ có các đường truyền có kích thước 4 byte nếu có 2 ^ 8 mục trong nhóm ). Mẫu truy cập bộ nhớ cũng có xu hướng thân thiện với bộ đệm hơn rất nhiều so với quicksorts mặc dù nó cần một mảng song song và một mảng xô nhỏ (thứ hai thường có thể vừa vặn trên ngăn xếp). QS có thể thực hiện 50 triệu giao dịch hoán đổi để sắp xếp một mảng gồm một triệu số nguyên với các mẫu truy cập ngẫu nhiên lẻ tẻ. Sắp xếp cơ số có thể làm điều đó trong 4 tuyến tính, thân thiện với bộ nhớ cache vượt qua dữ liệu.

Tuy nhiên, việc thiếu nhận thức về việc có thể làm điều này với một K nhỏ, trên các số âm cùng với dấu phẩy động, rất có thể đóng góp đáng kể vào việc thiếu sự phổ biến của các loại cơ số.

Theo ý kiến ​​của tôi về lý do tại sao mọi người không sử dụng nó thường xuyên hơn, có thể phải làm với nhiều tên miền thường không có nhu cầu sắp xếp số hoặc sử dụng chúng làm khóa tìm kiếm. Tuy nhiên, chỉ dựa trên kinh nghiệm cá nhân của tôi, nhiều đồng nghiệp cũ của tôi cũng đã không sử dụng nó trong trường hợp nó hoàn toàn phù hợp, và một phần vì họ không biết rằng nó có thể được tạo ra để làm việc trên FP và phủ định. Vì vậy, ngoài việc nó chỉ hoạt động trên các loại số, nó thường được cho là thậm chí ít được áp dụng hơn so với thực tế. Tôi cũng sẽ không sử dụng nó nhiều như vậy nếu tôi nghĩ nó không hoạt động trên các số có dấu phẩy động và số nguyên âm.

Một số điểm chuẩn:

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 ]

Và đó chỉ là với cách triển khai ngây thơ của tôi ( mt_sort_intcũng là sắp xếp cơ số nhưng với một nhánh mã nhanh hơn được cho rằng nó có thể giả sử khóa là một số nguyên). Hãy tưởng tượng một triển khai tiêu chuẩn được viết bởi các chuyên gia có thể nhanh như thế nào.

Trường hợp duy nhất mà tôi thấy loại radix có giá thấp hơn so với dựa trên so sánh rất nhanh của C ++ std::sortlà cho một số phần tử rất nhỏ, ví dụ 32, tại thời điểm đó tôi tin rằng std::sortbắt đầu sử dụng các loại phù hợp hơn cho số lượng phần tử nhỏ nhất như heapsorts hoặc chèn các loại, mặc dù tại thời điểm đó việc thực hiện của tôi chỉ sử dụng std::sort.


1
Luôn luôn tốt đẹp để nghe ý kiến ​​của những người có kinh nghiệm trong khu vực.
Frank Hileman

Xuất hiện mt_ là các triển khai đa luồng: softwareengineering.stackexchange.com/a/362097/65606
rogerdpack

1

Thêm một lý do: Ngày nay việc sắp xếp thường được thực hiện với thói quen sắp xếp do người dùng cung cấp kèm theo logic sắp xếp do trình biên dịch cung cấp. Với một loại cơ số, điều này sẽ phức tạp hơn đáng kể và thậm chí còn tệ hơn khi thói quen sắp xếp hành động theo nhiều khóa có độ dài thay đổi. (Nói, tên và ngày sinh.)

Trong thế giới thực, tôi đã thực sự thực hiện một loại cơ số một lần. Đó là vào thời xưa khi bộ nhớ bị hạn chế, tôi không thể mang tất cả dữ liệu của mình vào bộ nhớ cùng một lúc. Điều đó có nghĩa là số lượng truy cập vào dữ liệu quan trọng hơn nhiều so với O (n) so với O (n log n). Tôi đã thực hiện một lần chuyển dữ liệu phân bổ từng bản ghi vào một thùng (theo danh sách các bản ghi trong đó có thùng, không thực sự di chuyển bất cứ thứ gì.) Đối với mỗi thùng không trống (khóa sắp xếp của tôi là văn bản, sẽ có rất nhiều thùng rỗng) Tôi đã kiểm tra xem tôi có thực sự có thể mang dữ liệu vào bộ nhớ hay không - nếu có, hãy mang dữ liệu vào và sử dụng quicksort. Nếu không, xây dựng tệp tạm thời chỉ chứa các mục trong thùng và gọi đệ quy thường quy. (Trong thực tế, một số thùng sẽ tràn ra.) Điều này gây ra hai lần đọc hoàn chỉnh và một lần ghi hoàn chỉnh vào bộ lưu trữ mạng và khoảng 10% trong số này cho bộ nhớ cục bộ.

Ngày nay, những vấn đề dữ liệu lớn như vậy rất khó chạy, tôi có thể sẽ không bao giờ viết bất cứ điều gì như vậy nữa. (Nếu tôi phải đối mặt với cùng một dữ liệu hiện nay, tôi chỉ cần chỉ định HĐH 64 bit, thêm RAM nếu bạn gặp sự cố trong trình chỉnh sửa đó.)


Hấp dẫn khi xem xét một trong những nhược điểm được đề cập đến loại radix đôi khi được đề cập là "nó cần nhiều không gian hơn". Vẫn cố quấn lấy đầu tôi ...
rogerdpack

1
@rogerdpack Không phải là cách tiếp cận của tôi sử dụng ít không gian hơn, mà là nó sử dụng ít quyền truy cập vào dữ liệu hơn. Tôi đã sắp xếp một tệp có dung lượng khoảng gigabyte trong khi xử lý giới hạn trình biên dịch (đây là chế độ được bảo vệ bởi DOS, không phải Windows) với tổng số dưới 16mb sử dụng bộ nhớ bao gồm mã và giới hạn cấu trúc là 64kb.
Loren Pechtel

-1

Nếu tất cả các tham số của bạn đều là số nguyên và nếu bạn có hơn 1024 tham số đầu vào, thì sắp xếp cơ số luôn nhanh hơn.

Tại sao?

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

Vì vậy, sắp xếp radix nhanh hơn khi

log(n)> max num of digits

Số nguyên tối đa trong Java là 2147483647. Dài 10 chữ số

Vì vậy, sắp xếp radix luôn nhanh hơn khi

log(n)> 10

Do đó, sắp xếp radix luôn nhanh hơn khi n>1024


Có các hằng số ẩn trong chi tiết triển khai, nhưng về cơ bản, bạn đang nói "đối với loại cơ số đầu vào lớn hơn thì nhanh hơn" mà ... nên là như vậy! Thật khó để tìm thấy các trường hợp sử dụng cho nó nhưng khi bạn có thể ...
rogerdpack
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.