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


308

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à và 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à 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ớ.O ( n 2 ) O ( n log n )O(nlogn)O(n2)O(nlogn)

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).


Cập nhật 1: một câu trả lời chính tắc nói rằng các hằng số liên quan đến của trường hợp trung bình nhỏ hơn các hằng số liên quan đến các thuật toán . Tuy nhiên, tôi vẫn chưa thấy một lời biện minh đúng đắn về điều này, với các tính toán chính xác thay vì chỉ những ý tưởng trực quan.O ( n log n )O(nlogn)O(nlogn)

Trong mọi trường hợp, có vẻ như sự khác biệt thực sự xảy ra, như một số câu trả lời cho thấy, ở cấp độ bộ nhớ, trong đó việc triển khai tận dụng cấu trúc bên trong của máy tính, ví dụ, sử dụng bộ nhớ cache nhanh hơn RAM. Các cuộc thảo luận đã được thú vị, nhưng tôi vẫn muốn xem chi tiết hơn liên quan đến quản lý bộ nhớ với, vì có vẻ như những câu trả lời đã làm với nó.


Cập nhật 2: Có một số trang web cung cấp so sánh các thuật toán sắp xếp, một số fancier hơn các thuật toán khác (đáng chú ý nhất là sorting-alerskyms.com ). Khác với việc trình bày một trợ giúp trực quan tốt đẹp, phương pháp này không trả lời câu hỏi của tôi.


2
Hợp nhất sắp xếp là trong trường hợp xấu nhất và sắp xếp một mảng các số nguyên trong đó có một giới hạn đã biết về kích thước của các số nguyên có thể được thực hiện trong thời gian với sắp xếp đếm. O ( n )O(nlogn)O(n)
Carl Mummert

13
sorting-alerskyms.com có một so sánh khá kỹ lưỡng về các thuật toán sắp xếp.
Joe

2
Cập nhật quảng cáo 1: Tôi phỏng đoán rằng bạn có thể có phân tích nghiêm ngặt hoặc giả định thực tế. Tôi chưa thấy cả hai. Ví dụ, hầu hết các phân tích chính thức chỉ tính so sánh.
Raphael

9
Câu hỏi này đã giành được gần đây cuộc thi trên programmers.SE !
Raphael

3
Câu hỏi thú vị. Tôi đã chạy một số thử nghiệm một thời gian trước với dữ liệu ngẫu nhiên và một triển khai ngây thơ của sắp xếp nhanh và sắp xếp hợp nhất. Cả hai thuật toán đều hoạt động khá tốt đối với các tập dữ liệu nhỏ (tối đa 100000 mục) nhưng sau đó sắp xếp hợp nhất hóa ra lại tốt hơn nhiều. Điều này dường như mâu thuẫn với giả định chung rằng sắp xếp nhanh là rất tốt và tôi vẫn chưa tìm thấy lời giải thích cho nó. Ý tưởng duy nhất tôi có thể đưa ra là thông thường thuật ngữ sắp xếp nhanh được sử dụng cho các thuật toán phức tạp hơn như sắp xếp giới thiệu và việc triển khai nhanh chóng của sắp xếp nhanh với trục ngẫu nhiên là không tốt.
Giorgio

Câu trả lời:


215

Câu trả lời ngắn

Đối số hiệu quả bộ đệm đã được giải thích chi tiết. Ngoài ra, có một lập luận nội tại, tại sao Quicksort lại nhanh. Nếu được thực hiện như với hai con trỏ băng qua đường khác, ví dụ ở đây , các vòng bên trong có một cơ thể rất nhỏ. Vì đây là mã được thực thi thường xuyên nhất, điều này sẽ được đền đáp.

Câu trả lời dài

Đầu tiên,

Các trường hợp trung bình không tồn tại!

Vì trường hợp tốt nhất và tồi tệ nhất thường là cực đoan hiếm khi xảy ra trong thực tế, phân tích trường hợp trung bình được thực hiện. Nhưng bất kỳ phân tích trường hợp trung bình giả định một số phân phối đầu vào ! Để sắp xếp, lựa chọn điển hình là mô hình hoán vị ngẫu nhiên (được giả định ngầm trên Wikipedia).

Tại sao -Notation?O

Việc loại bỏ các hằng số trong phân tích thuật toán được thực hiện vì một lý do chính: Nếu tôi quan tâm đến thời gian chạy chính xác , tôi cần chi phí (tương đối) cho tất cả các hoạt động cơ bản liên quan (thậm chí vẫn bỏ qua các vấn đề về bộ nhớ đệm, đường ống trong bộ xử lý hiện đại ...). Phân tích toán học có thể đếm tần suất mỗi lệnh được thực thi, nhưng thời gian chạy của các lệnh đơn phụ thuộc vào chi tiết bộ xử lý, ví dụ: phép nhân số nguyên 32 bit có mất nhiều thời gian như vậy không.

Có hai cách:

  1. Sửa một số kiểu máy.

    Điều này được thực hiện trong sê-ri sách của Don Knuth , Nghệ thuật lập trình máy tính cho một máy tính nhân tạo điển hình, được phát minh bởi tác giả. Trong tập 3, bạn tìm thấy kết quả trường hợp trung bình chính xác cho nhiều thuật toán sắp xếp, ví dụ:

    • Quicksort:11.667(n+1)ln(viết sai rồi)-1,74viết sai rồi-18,74
    • Sáp nhập:12.5nln(n)
    • Heapsort: 16nln(n)+0.01n
    • Insertionsort: [ nguồn ]2.25n2+7.75n3ln(n) Thời gian chạy của một số thuật toán sắp xếp

    Những kết quả này chỉ ra rằng Quicksort là nhanh nhất. Nhưng, nó chỉ được chứng minh trên máy nhân tạo của Knuth, nó không nhất thiết ngụ ý bất cứ điều gì để nói PC x86 của bạn. Cũng lưu ý rằng các thuật toán liên quan khác nhau cho các đầu vào nhỏ:
    Thời gian chạy của một số thuật toán sắp xếp cho đầu vào nhỏ
    [ nguồn ]

  2. Phân tích các hoạt động cơ bản trừu tượng .

    Để sắp xếp dựa trên so sánh, đây thường là giao dịch hoán đổiso sánh chính . Trong các cuốn sách của Robert Sedgewick, ví dụ như Thuật toán Thuật ngữ , phương pháp này được theo đuổi. Bạn tìm thấy ở đó

    • Quicksort: trung bình và hoán đổi12nln(n)13nln(n)
    • : so sánh , nhưng lên tới truy cập mảng (sáp nhập không dựa trên trao đổi, vì vậy chúng tôi không thể tính được điều đó).8,66 n ln ( n )1.44nln(n)8.66nln(n)
    • Insertionsort: so sánh và trung bình hoán đổi.114n214n2

    Như bạn thấy, điều này không dễ dàng cho phép so sánh các thuật toán như phân tích thời gian chạy chính xác, nhưng kết quả không phụ thuộc vào chi tiết máy.

Phân phối đầu vào khác

Như đã lưu ý ở trên, các trường hợp trung bình luôn liên quan đến một số phân phối đầu vào, vì vậy người ta có thể xem xét các trường hợp khác ngoài hoán vị ngẫu nhiên. Ví dụ, nghiên cứu đã được thực hiện cho Quicksort với các phần tử bằng nhau và có một bài viết hay về hàm sắp xếp tiêu chuẩn trong Java


8
Kết quả của loại 2. có thể được chuyển đổi thành kết quả của loại 1. bằng cách chèn các hằng số phụ thuộc vào máy. Do đó, tôi sẽ tranh luận 2. là một cách tiếp cận ưu việt.
Raphael

2
@Raphael +1. Tôi cho rằng bạn cho rằng phụ thuộc vào máy cũng phụ thuộc vào việc triển khai, phải không? Ý tôi là, máy nhanh + thực hiện kém có lẽ không hiệu quả lắm.
Janoma

2
@Janoma Tôi giả sử thuật toán phân tích được đưa ra ở dạng rất chi tiết (vì phân tích là chi tiết) và việc thực hiện càng nhiều bằng chữ càng tốt. Nhưng có, việc thực hiện cũng sẽ có yếu tố.
Raphael

3
Trên thực tế, phân tích loại 2 kém hơn trong thực tế. Các máy trong thế giới thực phức tạp đến mức các kết quả từ loại 2 không thể dịch thành loại 1. So sánh với loại 1: vẽ thời gian chạy thử nghiệm mất 5 phút làm việc.
Jules

4
@Jules: "vẽ thời gian chạy thử nghiệm" không phải là loại 1; nó không phải là một loại phân tích chính thức và nó không thể chuyển sang các máy khác. Đó là lý do tại sao chúng tôi làm phân tích chính thức, sau khi tất cả.
Raphael

78

Có nhiều điểm có thể được thực hiện liên quan đến câu hỏi này.

Quicksort thường nhanh

Mặc dù Quicksort có hành vi trường hợp xấu nhất , nhưng nó thường rất nhanh: giả sử lựa chọn trục ngẫu nhiên, có một cơ hội rất lớn chúng tôi chọn một số số tách đầu vào thành hai tập con có kích thước tương tự, đó chính xác là những gì chúng tôi muốn có .O(n2)

Cụ thể, ngay cả khi chúng tôi chọn một trục tạo ra sự phân chia 10% -90% cho mỗi 10 lần phân tách (đó là phân chia meh) và phân chia 1 phần tử - phần tử khác (đó là phần tách tệ nhất bạn có thể nhận được) , thời gian chạy của chúng tôi vẫn là O ( n log n ) (lưu ý rằng điều này sẽ làm nổ tung các hằng số đến một điểm mà sắp xếp Hợp nhất có thể nhanh hơn).n1O(nlogn)

Quicksort thường nhanh hơn hầu hết các loại

Quicksort thường nhanh hơn các loại chậm hơn (giả sử, sắp xếp chèn với thời gian chạy O ( n 2 ) của nó ), đơn giản là vì trong n lớn thời gian chạy của chúng phát nổ.O(nlogn)O(n2)n

Một lý do chính đáng tại sao Quicksort trong thực tế quá nhanh so với hầu hết các thuật toán khác như Heapsort, là bởi vì nó tương đối hiệu quả trong bộ nhớ cache. Thời gian chạy của nó thực sự là O ( nO(nlogn), trong đóBlà kích thước khối. Heapsort, mặt khác, không có bất kỳ sự tăng tốc nào như vậy: nó hoàn toàn không truy cập bộ nhớ cache một cách hiệu quả.O(nBlog(nB))B

Lý do cho hiệu quả bộ đệm này là vì nó quét tuyến tính đầu vào và phân vùng tuyến tính đầu vào. Điều này có nghĩa là chúng ta có thể tận dụng tối đa mọi tải bộ đệm mà chúng ta làm khi đọc mọi số chúng ta tải vào bộ đệm trước khi hoán đổi bộ đệm đó cho bộ đệm khác. Đặc biệt, thuật toán này không có bộ nhớ cache, mang lại hiệu năng bộ đệm tốt cho mọi cấp độ bộ đệm, đây là một chiến thắng khác.

Hiệu quả bộ nhớ cache có thể được cải thiện hơn nữa thành , trong đóMlà kích thước của bộ nhớ chính của chúng ta, nếu chúng ta sử dụngQuicksortk-way. Lưu ý rằng Mergesort cũng có hiệu suất bộ đệm tương tự như Quicksort và phiên bản k-way của nó trên thực tế có hiệu suất tốt hơn (thông qua các yếu tố hằng số thấp hơn) nếu bộ nhớ bị hạn chế nghiêm trọng. Điều này dẫn đến điểm tiếp theo: chúng ta sẽ cần so sánh Quicksort với Mergesort về các yếu tố khác.O(nBlogMB(nB))Mk

Quicksort thường nhanh hơn Mergesort

So sánh này là hoàn toàn về các yếu tố không đổi (nếu chúng ta xem xét trường hợp điển hình). Cụ thể, sự lựa chọn nằm giữa lựa chọn tối ưu của trục cho Quicksort so với bản sao của toàn bộ đầu vào cho Mergesort (hoặc độ phức tạp của thuật toán cần thiết để tránh sao chép này). Nó chỉ ra rằng trước đây là hiệu quả hơn: không có lý thuyết đằng sau điều này, nó chỉ xảy ra là nhanh hơn.

Lưu ý rằng Quicksort sẽ thực hiện nhiều cuộc gọi đệ quy hơn, nhưng phân bổ không gian ngăn xếp là rẻ (thực tế là gần như miễn phí, miễn là bạn không thổi chồng) và bạn sử dụng lại nó. Phân bổ một khối khổng lồ trên heap (hoặc ổ đĩa cứng của bạn, nếu thực sự lớn) là tốn kém khá hơn một chút, nhưng cả hai đều là O ( log n ) các chi phí mà nhạt so với O ( n ) công việc nêu trên.nO(logn)O(n)

Cuối cùng, lưu ý rằng Quicksort hơi nhạy cảm với đầu vào xảy ra theo đúng thứ tự, trong trường hợp đó, nó có thể bỏ qua một số giao dịch hoán đổi. Mergesort không có bất kỳ tối ưu hóa nào như vậy, điều này cũng khiến Quicksort nhanh hơn một chút so với Mergesort.

Sử dụng loại phù hợp với nhu cầu của bạn

Tóm lại: không có thuật toán sắp xếp nào luôn tối ưu. Chọn bất cứ ai phù hợp với nhu cầu của bạn. Nếu bạn cần một thuật toán nhanh nhất trong hầu hết các trường hợp và bạn không bận tâm, nó có thể sẽ hơi chậm trong các trường hợp hiếm và bạn không cần một loại ổn định, hãy sử dụng Quicksort. Nếu không, sử dụng thuật toán phù hợp với nhu cầu của bạn tốt hơn.


3
Nhận xét cuối cùng của bạn là đặc biệt có giá trị. Một đồng nghiệp của tôi hiện đang phân tích các triển khai Quicksort theo các bản phân phối đầu vào khác nhau. Một số trong số họ phá vỡ cho nhiều bản sao, ví dụ.
Raphael

4
Ôi(viết sai rồi2)

8
"[T] đây không phải là lý thuyết đằng sau điều này, nó chỉ xảy ra nhanh hơn." Tuyên bố đó rất không thỏa đáng theo quan điểm khoa học. Hãy tưởng tượng Newton nói, "Bướm bay lên, táo rơi xuống: không có lý thuyết nào đằng sau điều này, táo chỉ tình cờ rơi."
David Richerby

2
@Alex ten Brink, ý của bạn là gì, đặc biệt, thuật toán này có phải là bộ nhớ cache hay không ?
Hibou57

4
@David Richerby, Triệu Câu nói đó rất không thỏa đáng theo quan điểm khoa học của Quan điểm: anh ta có thể chỉ đang chứng kiến ​​một sự thật mà không giả vờ rằng chúng ta nên hài lòng với nó. Một số gia đình thuật toán bị thiếu chính thức hóa đầy đủ; hàm băm là một trường hợp ví dụ.
Hibou57

45

Trong một trong những hướng dẫn lập trình tại trường đại học của tôi, chúng tôi đã yêu cầu sinh viên so sánh hiệu suất của quicksort, mergesort, chèn sắp xếp so với list.sort tích hợp của Python (được gọi là Timsort ). Các kết quả thử nghiệm đã làm tôi ngạc nhiên sâu sắc vì list.sort tích hợp hoạt động tốt hơn nhiều so với các thuật toán sắp xếp khác, ngay cả với các trường hợp dễ dàng thực hiện quicksort, sự cố sáp nhập. Vì vậy, còn sớm để kết luận rằng việc triển khai quicksort thông thường là tốt nhất trong thực tế. Nhưng tôi chắc chắn rằng có nhiều triển khai quicksort tốt hơn, hoặc một số phiên bản lai của nó ngoài kia.

Đây là một bài viết blog hay của David R. MacIver giải thích Timsort là một hình thức sáp nhập thích ứng.


17
@Raphael Nói một cách ngắn gọn, Timsort là hợp nhất sắp xếp cho tiệm cận cộng với sắp xếp chèn cho các đầu vào ngắn cộng với một số phương pháp phỏng đoán để đối phó hiệu quả với dữ liệu có cụm được sắp xếp sẵn (thường xảy ra trong thực tế). Dai: ngoài thuật toán, list.sortlợi ích từ việc là một chức năng tích hợp được tối ưu hóa bởi các chuyên gia. Một so sánh công bằng hơn sẽ có tất cả các chức năng được viết bằng cùng một ngôn ngữ ở cùng một mức độ nỗ lực.
Gilles

1
@Dai: Ít nhất bạn có thể mô tả với loại đầu vào nào (tương ứng với phân phối của chúng) trong trường hợp nào (RAM thấp, thực hiện song song, ...) bạn đã thu được kết quả của mình.
Raphael

7
Chúng tôi đã thử nghiệm trên danh sách các số ngẫu nhiên và được sắp xếp một phần, sắp xếp hoàn toàn và sắp xếp ngược lại. Đó là một khóa học năm thứ nhất giới thiệu, vì vậy nó không phải là một nghiên cứu thực nghiệm sâu sắc. Nhưng thực tế là nó hiện được sử dụng chính thức để sắp xếp các mảng trong Java SE 7 và trên nền tảng Android có ý nghĩa gì đó.
Đại

3
Điều này cũng đã được thảo luận ở đây: cstheory.stackexchange.com/a/927/74
Jukka Suomela

34

Tôi nghĩ một trong những lý do chính khiến QuickSort nhanh như vậy so với các thuật toán sắp xếp khác là vì nó thân thiện với bộ đệm. Khi QS xử lý một phân đoạn của một mảng, nó truy cập các phần tử ở đầu và cuối của phân đoạn và di chuyển về phía trung tâm của phân khúc.

Vì vậy, khi bạn bắt đầu, bạn truy cập vào phần tử đầu tiên trong mảng và một phần bộ nhớ (Vị trí định vị) được tải vào bộ đệm. Và khi bạn cố gắng truy cập phần tử thứ hai, rất có thể nó đã có trong bộ đệm, vì vậy nó rất nhanh.

Các thuật toán khác như heapsort không hoạt động như thế này, chúng nhảy vào mảng rất nhiều, khiến chúng chậm hơn.


5
Đó là lời giải thích gây tranh cãi: sáp nhập cũng thân thiện với bộ đệm.
Dmytro Korduban

2
Tôi nghĩ rằng câu trả lời này về cơ bản là đúng, nhưng đây là một số chi tiết youtube.com/watch?v=aMnn0Jq0J-E
rgrig

3
có lẽ hằng số nhân cho độ phức tạp thời gian trường hợp trung bình của sắp xếp nhanh cũng tốt hơn (không phụ thuộc vào yếu tố bộ đệm bạn đã đề cập).
Kaveh

1
Điểm bạn đề cập không quan trọng lắm, so với các đặc tính tốt khác của sắp xếp nhanh.
MMS

1
@Kaveh: "hằng số nhân cho độ phức tạp thời gian trường hợp trung bình của sắp xếp nhanh cũng tốt hơn" Bạn có dữ liệu nào về điều này không?
Giorgio

29

Những người khác đã nói rằng thời gian chạy trung bình không triệu chứng của Quicksort là tốt hơn (không đổi) so với các thuật toán sắp xếp khác (trong một số cài đặt nhất định).

Ôi(viết sai rồiđăng nhậpviết sai rồi)

Lưu ý rằng có nhiều biến thể của Quicksort (xem ví dụ luận án của Sedgewick). Chúng thực hiện khác nhau trên các bản phân phối đầu vào khác nhau (thống nhất, gần như được sắp xếp, gần như được sắp xếp ngược, nhiều bản sao, ...) và các thuật toán khác có thể tốt hơn đối với một số người.

k10


20

Ôi(viết sai rồilgviết sai rồi)

ps: chính xác hơn, tốt hơn các thuật toán khác phụ thuộc vào nhiệm vụ. Đối với một số tác vụ, có thể tốt hơn để sử dụng các thuật toán sắp xếp khác.

Xem thêm:


3
@Janoma đây là vấn đề về ngôn ngữ và trình biên dịch bạn sử dụng. Hầu như tất cả các ngôn ngữ chức năng (ML, Lisp, Haskell) có thể thực hiện tối ưu hóa ngăn chặn ngăn xếp phát triển và trình biên dịch thông minh hơn cho các ngôn ngữ bắt buộc có thể làm điều tương tự (GCC, G ++ và tôi tin rằng MSVC đều làm điều này). Ngoại lệ đáng chú ý là Java, sẽ không bao giờ thực hiện tối ưu hóa này, do đó, thật hợp lý khi viết lại đệ quy của bạn dưới dạng lặp.
Rafe Kettler

4
@JD, bạn không thể sử dụng tối ưu hóa cuộc gọi đuôi với quicksort (ít nhất là không hoàn toàn), vì nó tự gọi hai lần. Bạn có thể tối ưu hóa cuộc gọi thứ hai, nhưng không phải cuộc gọi đầu tiên.
Svick

1
@Janoma, bạn không thực sự cần thực hiện đệ quy. Ví dụ, nếu bạn nhìn vào việc thực hiện hàm qsort trong C, thì nó không sử dụng các cuộc gọi đệ quy, và do đó việc thực hiện trở nên nhanh hơn nhiều.
Kaveh

1
Heapsort cũng được đặt ra, tại sao QS thường nhanh hơn?
Kevin

6
23240

16

Θ(viết sai rồi2)Θ(viết sai rồiđăng nhậpviết sai rồi)

Lý do thứ hai là nó thực hiện in-placesắp xếp và hoạt động rất tốt với môi trường bộ nhớ ảo.

CẬP NHẬT :: (Sau bình luận của Janoma và Svick)

Để minh họa điều này tốt hơn, tôi xin đưa ra một ví dụ bằng cách sử dụng Sắp xếp hợp nhất (vì sắp xếp Hợp nhất là thuật toán sắp xếp được áp dụng rộng rãi tiếp theo sau khi sắp xếp nhanh, tôi nghĩ) và cho bạn biết các hằng số bổ sung đến từ đâu (theo hiểu biết tốt nhất của tôi và tại sao tôi nghĩ Sắp xếp nhanh là tốt hơn):

Hãy xem xét các seqence sau:

12,30,21,8,6,9,1,7. The merge sort algorithm works as follows:

(a) 12,30,21,8    6,9,1,7  //divide stage
(b) 12,30   21,8   6,9   1,7   //divide stage
(c) 12   30   21   8   6   9   1   7   //Final divide stage
(d) 12,30   8,21   6,9   1,7   //Merge Stage
(e) 8,12,21,30   .....     // Analyze this stage

Nếu bạn quan tâm đầy đủ xem giai đoạn cuối đang diễn ra như thế nào, thì 12 đầu tiên được so sánh với 8 và 8 nhỏ hơn để nó đi trước. Bây giờ 12 là LẠI so với 21 và 12 tiếp theo và cứ tiếp tục như vậy. Nếu bạn thực hiện hợp nhất cuối cùng, tức là 4 phần tử với 4 phần tử khác, nó sẽ phát sinh rất nhiều so sánh EXTRA dưới dạng các hằng số KHÔNG phát sinh trong Sắp xếp nhanh. Đây là lý do tại sao sắp xếp nhanh chóng được ưa thích.


1
Nhưng điều gì làm cho hằng số quá nhỏ?
Svick

1
@svick Vì chúng được sắp xếp in-placetức là không cần thêm bộ nhớ.
0x0

Θ(viết sai rồilgviết sai rồi)

15

Kinh nghiệm của tôi khi làm việc với dữ liệu trong thế giới thực là quicksort là một lựa chọn tồi . Quicksort hoạt động tốt với dữ liệu ngẫu nhiên, nhưng dữ liệu trong thế giới thực thường không phải là ngẫu nhiên.

Trở lại năm 2008, tôi đã theo dõi một lỗi phần mềm treo cho đến việc sử dụng quicksort. Một lúc sau tôi đã viết những hàm ý đơn giản về sắp xếp chèn, quicksort, heap sort và merge sort và thử nghiệm những thứ này. Sắp xếp hợp nhất của tôi vượt trội hơn tất cả những người khác trong khi làm việc trên các tập dữ liệu lớn.

Kể từ đó, hợp nhất sắp xếp là thuật toán sắp xếp của tôi lựa chọn. Đó là thanh lịch. Nó là đơn giản để thực hiện. Nó là một loại ổn định. Nó không suy biến thành hành vi bậc hai như quicksort. Tôi chuyển sang sắp xếp chèn để sắp xếp các mảng nhỏ.

Trong nhiều trường hợp, tôi đã tự nghĩ rằng một triển khai nhất định hoạt động tốt đến mức đáng ngạc nhiên đối với quicksort chỉ để tìm ra rằng nó thực sự không phải là quicksort. Đôi khi việc triển khai chuyển đổi giữa quicksort và một thuật toán khác và đôi khi nó không sử dụng quicksort chút nào. Ví dụ, các hàm qsort () của GLibc thực sự sử dụng sắp xếp hợp nhất. Chỉ khi phân bổ không gian làm việc không thành công, nó mới quay trở lại quicksort tại chỗ mà một nhận xét mã gọi là "thuật toán chậm hơn" .

Chỉnh sửa: Các ngôn ngữ lập trình như Java, Python và Perl cũng sử dụng sắp xếp hợp nhất hoặc chính xác hơn là một đạo hàm, như Timsort hoặc sắp xếp hợp nhất cho các tập lớn và sắp xếp chèn cho các tập nhỏ. (Java cũng sử dụng quicksort hai trục nhanh hơn so với quicksort đơn giản.)


Tôi đã thấy một cái gì đó tương tự như vậy bởi vì chúng tôi đã liên tục nối thêm / dùng đến để chèn vào một loạt dữ liệu đã được sắp xếp. Bạn có thể giải quyết vấn đề này một cách trung bình bằng cách sử dụng quicksort ngẫu nhiên (và bị bất ngờ bởi một loại chậm cực kỳ hiếm và ngẫu nhiên), hoặc bạn có thể chịu đựng một loại luôn chậm hơn mà không bao giờ mất một thời gian đáng ngạc nhiên để kết thúc. Đôi khi bạn yêu cầu sự ổn định sắp xếp là tốt. Java đã chuyển từ sử dụng sắp xếp hợp nhất sang một biến thể quicksort.
Rob

@Rob Điều này không chính xác. Java vẫn sử dụng một biến thể của sáp nhập (Timsort) cho đến ngày nay. Nó cũng sử dụng một biến thể của quicksort, (quicksort hai trục).
Erwan Legrand

14

1 - Sắp xếp nhanh chóng tại chỗ (không cần thêm ghi nhớ, ngoài số lượng không đổi.)

2 - Sắp xếp nhanh dễ thực hiện hơn các thuật toán sắp xếp hiệu quả khác.

3 - Sắp xếp nhanh có các yếu tố hằng số nhỏ hơn trong thời gian chạy so với các thuật toán sắp xếp hiệu quả khác.

Cập nhật: Để sắp xếp hợp nhất, bạn cần thực hiện một số "hợp nhất", cần thêm (các) mảng để lưu trữ dữ liệu trước khi hợp nhất; nhưng sắp xếp nhanh chóng, bạn không. Đó là lý do tại sao sắp xếp nhanh chóng tại chỗ. Ngoài ra còn có một số so sánh bổ sung được thực hiện để hợp nhất làm tăng các yếu tố không đổi trong sắp xếp hợp nhất.


3
Bạn đã thấy các triển khai Quicksort tại chỗ, lặp đi lặp lại chưa? Chúng có nhiều thứ nhưng không "dễ".
Raphael

2
Số 2 hoàn toàn không trả lời câu hỏi của tôi, và số 1 và 3 cần sự biện minh đúng đắn, theo ý kiến ​​của tôi.
Janoma

@Raphael: Họ dễ. Dễ dàng hơn nhiều để thực hiện sắp xếp nhanh tại chỗ bằng cách sử dụng một mảng, thay vì con trỏ. Và nó không cần phải được lặp đi lặp lại.
MMS

Các mảng để hợp nhất không phải là xấu. Khi bạn đã di chuyển một mục từ đống nguồn sang đống đích, nó không còn cần phải ở đó nữa. Nếu bạn đang sử dụng mảng động, sẽ có phí bộ nhớ không đổi khi hợp nhất.
Oskar Skog

@ 1 Sáp nhập cũng có thể được thực hiện. @ 2 Định nghĩa hiệu quả là gì? Tôi thích sắp xếp hợp nhất vì theo ý kiến ​​của tôi rất đơn giản và hiệu quả. @ 3 Không liên quan khi bạn sắp xếp một lượng lớn dữ liệu và yêu cầu thuật toán được triển khai hiệu quả.
Oskar Skog

11

Trong những điều kiện là một thuật toán sắp xếp cụ thể thực sự là thuật toán nhanh nhất?

Θ(đăng nhập(viết sai rồi)2)Θ(viết sai rồiđăng nhập(viết sai rồi)2)

Θ(viết sai rồik)Θ(viết sai rồim)k= =2#viết sai rồibạnmber_ođụ_PoSSTôibtôie_vmộttôibạneSm= =#mmộtxTôimbạnm_tôieviết sai rồigth_ođụ_keyS

3) Cấu trúc dữ liệu cơ bản có bao gồm các yếu tố được liên kết không? Có -> luôn luôn sử dụng sắp xếp hợp nhất. Có cả hai cách dễ dàng để thực hiện kích thước cố định hoặc từ dưới lên thích ứng (còn gọi là tự nhiên) hợp nhất các loại cấu trúc dữ liệu khác nhau cho các cấu trúc dữ liệu được liên kết và vì chúng không bao giờ yêu cầu sao chép toàn bộ dữ liệu trong mỗi bước và chúng cũng không bao giờ yêu cầu thu hồi nhanh hơn bất kỳ loại sắp xếp so sánh chung nào khác, thậm chí nhanh hơn so với sắp xếp nhanh.

Θ(viết sai rồi)

5) Kích thước của dữ liệu cơ bản có thể bị ràng buộc ở kích thước nhỏ đến trung bình không? ví dụ: n <10.000 ... 100.000.000 (tùy thuộc vào kiến ​​trúc cơ bản và cấu trúc dữ liệu)? Có -> sử dụng sắp xếp bitonic hoặc sáp nhập chẵn lẻ Batcher. Đi 1)

Θ(viết sai rồi)Θ(viết sai rồi2)Θ(viết sai rồiđăng nhập(viết sai rồi)2)trường hợp xấu nhất thời gian chạy được biết đến, hoặc có thể thử sắp xếp lược. Tôi không chắc loại vỏ hoặc loại lược sẽ hoạt động tốt trong thực tế.

Θ(đăng nhập(viết sai rồi))Θ(viết sai rồi)Θ(viết sai rồi)Θ(đăng nhập(viết sai rồi))Θ(viết sai rồi2)Θ(viết sai rồi)Θ(viết sai rồi)Θ(đăng nhập(viết sai rồi))Θ(viết sai rồiđăng nhập(viết sai rồi))

Θ(viết sai rồiđăng nhập(viết sai rồi))

Gợi ý thực hiện cho quicksort:

Θ(viết sai rồi)Θ(đăng nhập(viết sai rồi))Θ(viết sai rồiđăng nhậpk(k-1))

2) Tồn tại các biến thể lặp từ dưới lên, lặp lại của quicksort, nhưng AFAIK, chúng có cùng ranh giới không gian và thời gian không có triệu chứng giống như các giới hạn từ trên xuống, khó thực hiện hơn (ví dụ như quản lý hàng đợi một cách rõ ràng). Kinh nghiệm của tôi là cho bất kỳ mục đích thực tế, những điều đó không bao giờ đáng xem xét.

Gợi ý thực hiện cho sáp nhập:

1) sáp nhập từ trên xuống luôn nhanh hơn so với sáp nhập từ trên xuống, vì nó không yêu cầu các cuộc gọi đệ quy.

2) sự hợp nhất rất ngây thơ có thể được tăng tốc bằng cách sử dụng bộ đệm đôi và chuyển đổi bộ đệm thay vì sao chép dữ liệu trở lại từ mảng tạm thời sau mỗi bước.

3) Đối với nhiều dữ liệu trong thế giới thực, sáp nhập thích ứng nhanh hơn nhiều so với sáp nhập kích thước cố định.

Θ(k)Θ(đăng nhập(k))Θ(1)Θ(viết sai rồi)

Từ những gì tôi đã viết, rõ ràng quicksort thường không phải là thuật toán nhanh nhất, ngoại trừ khi tất cả các điều kiện sau đây được áp dụng:

1) có nhiều hơn một vài "giá trị" có thể

2) cấu trúc dữ liệu cơ bản không được liên kết

3) chúng tôi không cần một trật tự ổn định

4) dữ liệu đủ lớn để thời gian chạy tiệm cận không tối ưu phụ của máy sắp xếp bitonic hoặc Batcher lẻ thậm chí hợp nhất đá vào

5) dữ liệu gần như không được sắp xếp và không bao gồm các phần lớn hơn đã được sắp xếp

6) chúng ta có thể truy cập chuỗi dữ liệu đồng thời từ nhiều nơi

Θ(đăng nhập(viết sai rồi))Θ(viết sai rồi)

ps: Ai đó cần giúp tôi định dạng văn bản.


(5): Việc triển khai sắp xếp của Apple kiểm tra một lần chạy theo thứ tự tăng dần hoặc giảm dần ở đầu và cuối mảng trước. Điều này rất nhanh nếu không có nhiều yếu tố như vậy và có thể xử lý các yếu tố này rất hiệu quả nếu có nhiều hơn n / ln n trong số chúng. Ghép nối hai mảng được sắp xếp và sắp xếp kết quả và bạn có được một sự hợp nhất
gnasher729

8

Hầu hết các phương pháp sắp xếp phải di chuyển dữ liệu xung quanh theo các bước ngắn (ví dụ: sắp xếp hợp nhất thực hiện thay đổi cục bộ, sau đó hợp nhất phần dữ liệu nhỏ này, sau đó hợp nhất dữ liệu lớn hơn. ..). Do đó, bạn cần nhiều chuyển động dữ liệu nếu dữ liệu ở xa đích của nó.

mộtb


5
Đối số của bạn về quicksort vs merge sort không giữ nước. Quicksort bắt đầu với một bước di chuyển lớn, sau đó thực hiện các bước di chuyển nhỏ hơn và nhỏ hơn (khoảng một nửa lớn ở mỗi bước). Hợp nhất sắp xếp bắt đầu với một di chuyển nhỏ, sau đó thực hiện các di chuyển lớn hơn và lớn hơn (khoảng gấp đôi ở mỗi bước). Điều này không chỉ ra một cách hiệu quả hơn so với cái khác.
Gilles
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.