Tại sao quicksort tốt hơn các thuật toán sắp xếp khác trong thực tế?


31

Đây là một câu trả lời của một câu hỏi trên cs.SE của Janoma . Tín dụng đầy đủ và chiến lợi phẩm cho anh ta hoặc cs.SE.

Trong một khóa học thuật toán tiêu chuẩn, chúng tôi được dạy rằng quicksort trung bình là O (n log n) và O (n²) trong trường hợp xấu nhất. Đồng thời, các thuật toán sắp xếp khác được nghiên cứu là O (n log n) trong trường hợp xấu nhất (như mergesortheapsort ), và thậm chí cả thời gian tuyến tính trong trường hợp tốt nhất (như bubbleort ) nhưng có thêm một số nhu cầu về bộ nhớ.

Sau khi lướt qua một số lần chạy nhiều hơn, việc nói rằng quicksort không nên hiệu quả như những lần khác.

Ngoài ra, hãy xem xét rằng các sinh viên học trong các khóa học lập trình cơ bản nói chung là không thực sự tốt vì nó có thể sử dụng quá nhiều bộ nhớ, v.v. Do đó (và mặc dù đây không phải là một đối số thực sự), điều này đưa ra ý tưởng rằng quicksort có thể không thực sự tốt vì nó là một thuật toán đệ quy.

Tại sao, sau đó, quicksort vượt trội hơn các thuật toán sắp xếp khác trong thực tế? Nó có liên quan đến cấu trúc dữ liệu trong thế giới thực không? Nó có liên quan đến cách thức hoạt động của bộ nhớ trong máy tính không? Tôi biết rằng một số ký ức nhanh hơn các ký ức khác, nhưng tôi không biết liệu đó có phải là lý do thực sự cho hiệu suất phản trực giác này (khi so sánh với các ước tính lý thuyết).


3
Danh tiếng Quicksort có từ thời mà cache không tồn tại.
Lập trình viên

9
"tại sao quicksort vượt trội hơn các thuật toán sắp xếp khác trong thực tế?" Chắc chắn đó là sự thật? Cho chúng tôi thấy việc triển khai thực sự mà bạn đang đề cập đến với tuyên bố này và cộng đồng sẽ cho bạn biết lý do tại sao việc triển khai cụ thể đó lại hành xử theo cách đó. Mọi thứ khác sẽ dẫn đến suy đoán hoang dã về các chương trình không tồn tại.
Doc Brown

1
@DocBrown: Nhiều triển khai Quicksort (hoặc biến thể của nó) được chọn trong nhiều thư viện, được cho là vì chúng hoạt động tốt nhất (tôi hy vọng vậy, đó là). Vì vậy, có thể có một cái gì đó về thuật toán làm cho Quicksort nhanh, độc lập với việc thực hiện .
Raphael

1
Ai đó phải nói điều này cho đầy đủ, vì vậy tôi sẽ: Quicksort không (thường) ổn định. Vì lý do này, bạn có thể không muốn sử dụng nó. Ngoài ra, vì lý do này, sắp xếp mặc định của bạn có thể không phải là Quicksort ngay cả khi đó là những gì bạn muốn.
RalphChapin

1
@Raphael: Thường thì cái được gọi là quick sort thực sự là một số biến thể như intro sort (được sử dụng, afaik, trong thư viện chuẩn C ++), không phải là sắp xếp nhanh thuần túy.
Giorgio

Câu trả lời:


21

Tôi không đồng ý rằng quicksort tốt hơn các thuật toán sắp xếp khác trong thực tế.

Đối với hầu hết các mục đích, Timsort - kết hợp giữa sắp xếp hợp nhất / chèn, khai thác thực tế là dữ liệu bạn sắp xếp thường bắt đầu gần như sắp xếp hoặc sắp xếp ngược lại.

Quicksort đơn giản nhất (không có trục ngẫu nhiên) xử lý trường hợp có khả năng phổ biến này là O (N ^ 2) (giảm xuống O (N lg N) với các trục ngẫu nhiên), trong khi TimSort có thể xử lý các trường hợp này trong O (N).

Theo các điểm chuẩn trong C # so sánh quicksort tích hợp với TimSort, Timsort nhanh hơn đáng kể trong các trường hợp được sắp xếp chủ yếu và nhanh hơn một chút trong trường hợp dữ liệu ngẫu nhiên và TimSort trở nên tốt hơn nếu chức năng so sánh đặc biệt chậm. Tôi đã không lặp lại các điểm chuẩn này và sẽ không ngạc nhiên nếu quicksort đánh bại TimSort một chút cho một số kết hợp dữ liệu ngẫu nhiên hoặc nếu có gì đó kỳ quặc trong loại dựng sẵn của C # (dựa trên quicksort) đang làm chậm nó. Tuy nhiên, TimSort có những lợi thế khác biệt khi dữ liệu có thể được sắp xếp một phần và gần bằng với quicksort về tốc độ khi dữ liệu không được sắp xếp một phần.

TimSort cũng có một phần thưởng bổ sung là một loại ổn định, không giống như quicksort. Nhược điểm duy nhất của TimSort sử dụng bộ nhớ O (N) so với O (lg N) trong triển khai (nhanh) thông thường.


18

Sắp xếp nhanh được coi là nhanh hơn vì hệ số nhỏ hơn bất kỳ thuật toán đã biết nào khác. Không có lý do hoặc bằng chứng cho điều đó, chỉ là không có thuật toán với hệ số nhỏ hơn đã được tìm thấy. Đúng là các thuật toán khác cũng có thời gian O ( n log n ), nhưng trong thế giới thực, hệ số này cũng quan trọng.

Lưu ý rằng đối với sắp xếp chèn dữ liệu nhỏ (loại được coi là O ( n 2 )) nhanh hơn vì bản chất của các hàm toán học. Điều này phụ thuộc vào các hệ số cụ thể khác nhau giữa các máy. (Cuối cùng, chỉ có lắp ráp thực sự đang chạy.) Vì vậy, đôi khi một sự kết hợp của sắp xếp nhanh và sắp xếp chèn là nhanh nhất trong thực tế tôi nghĩ.


7
+ Phải. Giáo viên cần phải nhận thức rõ hơn (và tôi là một giáo viên) về thực tế là các yếu tố không đổi có thể thay đổi theo thứ tự cường độ. Vì vậy, kỹ năng điều chỉnh hiệu suất thực sự quan trọng, bất kể big-O. Vấn đề là, họ tiếp tục dạy gprof , chỉ vì họ phải vượt qua điểm đạn trong chương trình giảng dạy, đó là cách tiếp cận sai 180 độ.
Mike Dunlavey

2
Không có lý do hay pro [o] f cho điều đó: chắc chắn là có. Nếu bạn đào đủ sâu, bạn sẽ tìm thấy một lý do.
Gilles 'SO- ngừng trở nên xấu xa'

2
@B Seven: để đơn giản hóa rất nhiều điểm số cho thuật toán sắp xếp O (n log n), có (n log n) các vòng lặp sắp xếp để sắp xếp n mục. Hệ số là mỗi chu kỳ của vòng lặp mất bao lâu. Khi n thực sự lớn (ít nhất là hàng nghìn), hệ số không quan trọng bằng O () ngay cả khi hệ số này rất lớn. Nhưng khi n nhỏ, hệ số quan trọng - và có thể là điều quan trọng nhất nếu bạn chỉ sắp xếp 10 mục.
Matt Gallagher

4
@MikeDunlavey - một ví dụ điển hình là việc xây dựng các kim tự tháp là O (n) trong khi sắp xếp ảnh của bạn về chúng là O (n ln n) nhưng nhanh hơn!
Martin Beckett

2
Có các thuật toán O (n log n) được đảm bảo như heapsort và mergesort, do đó, trong trường hợp xấu nhất không có triệu chứng, Quicksort thậm chí không nhanh bằng mức tốt nhất. Nhưng trong hiệu suất thế giới thực, một số biến thể quicksort làm rất tốt. Tuy nhiên, nói "hệ số nhỏ hơn" cũng giống như nói "nó nhanh hơn vì nó nhanh hơn". Tại sao các yếu tố không đổi rất nhỏ? Một lý do chính là vì quicksort rất tốt về mặt địa phương - nó sử dụng bộ nhớ cache rất tốt. Mergesort cũng có địa phương tốt, nhưng rất khó để thực hiện tại chỗ.
Steve314

16

Quicksort không vượt trội hơn tất cả các thuật toán sắp xếp khác. Ví dụ, sắp xếp heap từ dưới lên ( Wegener 2002 ) vượt trội so với quicksort cho lượng dữ liệu hợp lý và cũng là một thuật toán tại chỗ. Nó cũng dễ thực hiện (ít nhất, không khó hơn một số biến thể quicksort được tối ưu hóa).

Nó chỉ không quá nổi tiếng và bạn không tìm thấy nó trong nhiều sách giáo khoa, điều đó có thể giải thích tại sao nó không phổ biến như quicksort.


+1: Tôi đã chạy một số thử nghiệm và thực sự sắp xếp hợp nhất chắc chắn tốt hơn sắp xếp nhanh cho các mảng lớn (> 100000 phần tử). Sắp xếp heap hơi tệ hơn so với sắp xếp hợp nhất (nhưng sắp xếp hợp nhất cần nhiều bộ nhớ hơn). Tôi nghĩ rằng những gì mọi người gọi là sắp xếp nhanh thường là một biến thể được gọi là sắp xếp giới thiệu: sắp xếp nhanh chóng rơi trở lại sắp xếp đống khi độ sâu đệ quy vượt quá một giới hạn nhất định.
Giorgio

@Giorgio: quicksort có thể được sửa đổi theo một số cách để cải thiện nó, xem ví dụ ở đây: alss4.cs.princeton.edu/23quicksort Bạn đã thử cải tiến đó chưa?
Doc Brown

Thật thú vị, bạn có thể lưu một tài liệu tham khảo đến một cuốn sách \ trang web để đọc thêm về nó không? (tốt nhất là một cuốn sách)
Ramzi Kahil

@Martin: ý bạn là về heapsort từ dưới lên? Vâng, tôi đã đưa ra một tài liệu tham khảo ở trên. Nếu bạn muốn có một tài nguyên miễn phí, wikipedia của Đức có một bài viết về nó ( de.wikipedia.org/wiki/BottomUp-Heapsort ). Ngay cả khi bạn không nói tiếng Đức, tôi đoán bạn vẫn có thể đọc ví dụ về C99.
Doc Brown

7

Bạn không nên chỉ tập trung vào trường hợp xấu nhất và chỉ về độ phức tạp thời gian. Đó là về trung bình nhiều hơn tồi tệ nhất, và đó là về thời gian không gian.

Sắp xếp nhanh chóng:

  • độ phức tạp thời gian trung bình là ( n log n );
  • có thể được thực hiện với độ phức tạp không gian của Θ (log n );

Cũng có tài khoản rằng ký hiệu O lớn không tính đến bất kỳ hằng số nào, nhưng trong thực tế, nó sẽ tạo ra sự khác biệt nếu thuật toán nhanh hơn vài lần. ( N log n ) có nghĩa là, thuật toán đó thực thi trong K  n  log ( n ), trong đó K là hằng số. Quicksort là thuật toán sắp xếp so sánh với K thấp nhất .


1
@Gilles: nó có K thấp, vì đó là một thuật toán đơn giản.
vartec

5
WTF? Điều này không có ý nghĩa gì. Sự đơn giản của một thuật toán không liên quan đến tốc độ chạy của nó. Lựa chọn sắp xếp đơn giản hơn quicksort, điều đó không làm cho nó nhanh hơn.
Gilles 'SO- ngừng trở nên xấu xa'

1
@Gilles: sắp xếp lựa chọn là O (n ^ 2) cho mọi trường hợp (tệ nhất, trung bình và tốt nhất). Vì vậy, nó không quan trọng như thế nào đơn giản. Quicksort là O (n log n) cho trường hợp trung bình và trong số tất cả các thuật toán với O (n log n) avg nó là đơn giản nhất.
vartec

1
@Gilles: những thứ khác như nhau, đơn giản thực hiện viện trợ. Giả sử bạn đang so sánh hai thuật toán mà mỗi lần lặp (K n log n) của các vòng lặp bên trong tương ứng của chúng: thuật toán cần thực hiện ít công cụ hơn trên mỗi vòng lặp có lợi thế về hiệu suất.
sắp tới

1
@ Sắpstorm: Phrased như thế tuyên bố của bạn là một tautology, nhưng nó không liên quan đến "đơn giản". Ví dụ, có các biến thể Quicksort phức tạp hơn (phân biệt trường hợp!) Dẫn đến thời gian chạy nhỏ hơn (cả về lý thuyết và thực hành).
Raphael

5

Quicksort thường là một lựa chọn tốt vì nó khá nhanh và hợp lý nhanh chóng và dễ thực hiện.

Nếu bạn nghiêm túc về việc sắp xếp một lượng lớn dữ liệu rất nhanh thì có lẽ bạn sẽ tốt hơn với một số biến thể trên MergeSort. Điều này có thể được thực hiện để tận dụng bộ nhớ ngoài, có thể sử dụng nhiều luồng hoặc thậm chí các quy trình nhưng chúng không tầm thường đối với mã.


1

Hiệu suất thực tế của các thuật toán phụ thuộc vào nền tảng, cũng như ngôn ngữ, trình biên dịch, lập trình viên chú ý đến chi tiết triển khai, nỗ lực tối ưu hóa cụ thể, et cetera. Vì vậy, "lợi thế yếu tố không đổi" của quicksort không được xác định rõ ràng - đó là một đánh giá chủ quan dựa trên các công cụ hiện có và ước tính sơ bộ về "nỗ lực thực hiện tương đương" của bất kỳ ai thực sự nghiên cứu hiệu suất so sánh .. .

Điều đó nói rằng, tôi tin rằng quicksort hoạt động tốt (đối với đầu vào ngẫu nhiên) bởi vì nó đơn giản và vì cấu trúc đệ quy của nó tương đối thân thiện với bộ đệm. Mặt khác, vì trường hợp xấu nhất của nó rất dễ kích hoạt, nên bất kỳ việc sử dụng quicksort thực tế nào cũng sẽ cần phức tạp hơn mô tả trong sách giáo khoa của nó sẽ chỉ ra: do đó, các phiên bản sửa đổi như introsort.

Theo thời gian, khi nền tảng thống trị thay đổi, các thuật toán khác nhau có thể đạt được hoặc mất lợi thế tương đối (không xác định) của chúng. Sự khôn ngoan thông thường về hiệu suất tương đối có thể tụt hậu so với sự thay đổi này, vì vậy nếu bạn thực sự không chắc chắn thuật toán nào là tốt nhất cho ứng dụng của mình, bạn nên thực hiện cả hai và kiểm tra chúng.


Tôi đoán "hằng số nhỏ hơn" mà những người khác liên quan đến nó là một trong phân tích chính thức, đó là về số lượng so sánh hoặc hoán đổi. Điều này được xác định rất rõ nhưng không rõ làm thế nào điều này chuyển thành thời gian chạy. Một đồng nghiệp hiện đang thực hiện một số nghiên cứu về điều đó, thực sự.
Raphael

Ấn tượng của tôi là đó là về hiệu suất tổng quát, nhưng tôi cũng không tin vào điều đó. Mặc dù vậy, bạn đã đúng: nếu so sánh của bạn đặc biệt đắt đỏ, bạn có thể tra cứu số lượng so sánh dự kiến ​​...
sắp diễn ra vào

1
Vì lý do bạn nêu, nói về hiệu suất tổng thể (theo thời gian) không có nghĩa trong trường hợp chung vì có quá nhiều yếu tố chi tiết. Lý do chỉ tính các thao tác chọn không phải là chúng đắt tiền, mà là chúng xảy ra "thường xuyên nhất "Theo nghĩa ký hiệu Landau (Big-Oh), vì vậy việc đếm những thứ đó mang lại cho bạn sự không triệu chứng thô. Ngay khi bạn xem xét các hằng số và / hoặc thời gian chạy, chiến lược này ít thú vị hơn nhiều.
Raphael

Việc triển khai QuickSort tốt sẽ biên dịch sao cho các giá trị trục của bạn vẫn còn trong một thanh ghi CPU miễn là cần thiết. Điều này thường đủ để đánh bại một loại nhanh hơn về mặt lý thuyết với thời gian Big-O tương đương.
Dan Lyons

Các thuật toán sắp xếp khác nhau có các đặc điểm khác nhau liên quan đến số lượng so sánh và số lượng trao đổi chúng làm. Và @DanLyons lưu ý rằng một loại điển hình trong thư viện thực hiện so sánh thông qua các hàm do người dùng cung cấp và việc giữ các giá trị trong các thanh ghi qua nhiều lệnh gọi hàm là khá khó khăn.
Pointy
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.