Tại sao quicksort tốt hơn sáp nhập?


354

Tôi đã được hỏi câu hỏi này trong một cuộc phỏng vấn. Cả hai đều là O (nlogn) và hầu hết mọi người đều sử dụng Quicksort thay vì Mergesort. Tại sao vậy?


91
Đây không phải là một câu hỏi phỏng vấn rất tốt. Dữ liệu trong thế giới thực không bị xáo trộn: nó thường chứa rất nhiều thứ tự mà một loại thông minh có thể sử dụng, và trong khi không thuật toán nào tự động làm điều này, thì việc hack một loại hợp nhất để thực hiện nó dễ dàng hơn so với quicksort. GNU libc's qsort, Python's list.sortArray.prototype.sortJavaScript của Firefox đều là những loại hợp nhất được cải tiến. (GNU STL sortsử dụng Introsort thay thế, nhưng điều đó có thể là do trong C ++, việc hoán đổi có khả năng thắng lớn khi sao chép.)
Jason Orendorff

3
@Jason Orendorff: Tại sao vậy "easier to hack a mergesort to do it than a quicksort"? Bất kỳ ví dụ cụ thể mà bạn có thể trích dẫn?
Lazer

16
@eSKay Một loại hợp nhất bắt đầu bằng cách nhóm dữ liệu ban đầu vào các tập hợp con được sắp xếp. Nếu mảng ban đầu chứa một số vùng đã được sắp xếp, bạn có thể tiết kiệm rất nhiều thời gian chỉ bằng cách phát hiện ra rằng chúng ở đó trước khi bạn bắt đầu. Và bạn có thể làm điều đó trong thời gian O (n). Để biết ví dụ cụ thể, xem mã nguồn của ba dự án tôi đã đề cập! Ví dụ tốt nhất có thể là Timsort của Python, được mô tả chi tiết tại đây: svn.python.org/view/python/trunk/Objects/ trộm và được triển khai trong svn.python.org/view/python/trunk/Objects/ tựa .
Jason Orendorff

4
@JasonOrendorff: Không chắc chắn tôi mua đối số của bạn rằng sáp nhập có thể dễ dàng sửa đổi hơn để tận dụng các phần đã được sắp xếp. Bước phân vùng của quicksort có thể được sửa đổi một cách tầm thường để sau đó kiểm tra xem cả hai phân vùng kết quả có được sắp xếp hay không và dừng đệ quy nếu có. Điều này có khả năng tăng gấp đôi số lượng so sánh, nhưng không làm thay đổi độ phức tạp thời gian O (n) của bước đó.
j_random_hacker

3
@j_random_hacker: đúng, đó là những gì tôi đang ngụ ý. Nhưng hãy xem xét: {10, 2, 3, 4, 5, 6, 7, 8, 1, 9} Mặc dù đã được sắp xếp gần như hoàn toàn, kiểm tra trước khi phân vùng sẽ không tìm thấy nó, cũng không phải sau đó. Và phân vùng sẽ làm hỏng nó trước khi các cuộc gọi tiếp theo sẽ kiểm tra nó. Trong khi đó, hợp nhất các loại sắp xếp kiểm tra các chuỗi đã được sắp xếp trong các bước phân chia trước khi di chuyển bất kỳ và các chuỗi thông minh sẽ tìm kiếm các bước chạy như thế này cụ thể trong bước phân chia (xem: Sắp xếp Tim)
Vịt Mooing

Câu trả lời:


275

Quicksort có thời gian chạy trường hợp xấu nhất O ( n 2 ) và thời gian chạy trường hợp trung bình O ( n log n ). Tuy nhiên, sẽ tốt hơn khi hợp nhất sắp xếp trong nhiều tình huống vì nhiều yếu tố ảnh hưởng đến thời gian chạy của thuật toán và khi kết hợp tất cả lại với nhau, quicksort sẽ thắng.

Cụ thể, thời gian chạy thường được trích dẫn của các thuật toán sắp xếp đề cập đến số lượng so sánh hoặc số lần hoán đổi cần thiết để thực hiện để sắp xếp dữ liệu. Đây thực sự là một thước đo tốt về hiệu suất, đặc biệt là vì nó độc lập với thiết kế phần cứng cơ bản. Tuy nhiên, những thứ khác - chẳng hạn như địa phương tham chiếu (nghĩa là chúng ta có đọc nhiều yếu tố có thể có trong bộ đệm không?) - cũng đóng một vai trò quan trọng trên phần cứng hiện tại. Quicksort đặc biệt đòi hỏi ít không gian bổ sung và thể hiện địa phương bộ đệm tốt và điều này làm cho nó nhanh hơn so với sắp xếp hợp nhất trong nhiều trường hợp.

Ngoài ra, thật dễ dàng để tránh thời gian chạy O ( n 2 ) trong trường hợp xấu nhất của quicksort gần như hoàn toàn bằng cách sử dụng một lựa chọn phù hợp của trục - chẳng hạn như chọn ngẫu nhiên (đây là một chiến lược tuyệt vời).

Trên thực tế, việc triển khai nhiều hiện đại của quicksort (đặc biệt libstdc ++ 's std::sort) đang thực sự introsort , mà lý thuyết trường hợp xấu nhất là O ( n log n ), giống như merge sort. Nó đạt được điều này bằng cách giới hạn độ sâu đệ quy và chuyển sang một thuật toán khác ( heapsort ) một khi nó vượt quá log n .


4
Bài viết trên Wikipedia nói rằng nó chuyển sang heapsort, không sáp nhập ... chỉ là FYI.
bảy

3
@Sev: Bài cũng như giấy gốc. Cảm ơn đã chỉ ra sai lầm. - Không phải là nó thực sự quan trọng, vì thời gian chạy tiệm cận của họ là như nhau.
Konrad Rudolph

110
Tại sao điều này được chọn là câu trả lời đúng? Tất cả nó giải thích là làm thế nào nhanh chóng sắp xếp các vấn đề được vá. Nó vẫn không cho biết tại sao sắp xếp nhanh được sử dụng nhiều hơn khác? Là câu trả lời "sắp xếp nhanh được sử dụng nhiều hơn so với khác bởi vì sau một độ sâu, bạn có thể chuyển sang heapsort"? .. tại sao không sử dụng heapsort ở nơi đầu tiên sau đó? .. chỉ cần cố gắng để hiểu ...
codeObserver

16
@ p1 Câu hỏi hay. Câu trả lời thực sự là trung bình, đối với dữ liệu trung bình, quicksort nhanh hơn so với sắp xếp hợp nhất (và sắp xếp heap, và mặc dù trường hợp xấu nhất của quicksort chậm hơn so với sắp xếp hợp nhất, trường hợp xấu nhất này có thể được giảm nhẹ rất dễ dàng (do đó câu trả lời của tôi).
Konrad Rudolph

4
Quicksort là tốt hơn về bộ nhớ là tốt.
Shashwat

287

Như nhiều người đã lưu ý, hiệu suất trường hợp trung bình cho quicksort nhanh hơn so với sáp nhập. Nhưng điều này chỉ đúng nếu bạn giả sử thời gian liên tục để truy cập bất kỳ phần bộ nhớ nào theo yêu cầu.

Trong RAM, giả định này thường không quá tệ (điều này không phải lúc nào cũng đúng vì bộ nhớ cache, nhưng nó không quá tệ). Tuy nhiên, nếu cấu trúc dữ liệu của bạn đủ lớn để sống trên đĩa, thì quicksort sẽ bị giết bởi thực tế là đĩa trung bình của bạn thực hiện công việc như 200 lần tìm kiếm ngẫu nhiên mỗi giây. Nhưng cùng một đĩa đó không gặp khó khăn khi đọc hoặc ghi megabyte mỗi giây dữ liệu theo tuần tự. Đó là chính xác những gì sáp nhập làm.

Do đó, nếu dữ liệu phải được sắp xếp trên đĩa, bạn thực sự, thực sự muốn sử dụng một số biến thể trên mergesort. (Nói chung, bạn liệt kê danh sách phụ, sau đó bắt đầu hợp nhất chúng lại với nhau trên ngưỡng kích thước.)

Hơn nữa, nếu bạn phải làm bất cứ điều gì với bộ dữ liệu có kích thước đó, hãy suy nghĩ kỹ về cách tránh tìm kiếm vào đĩa. Ví dụ, đây là lý do tại sao bạn nên bỏ chỉ mục trước khi thực hiện tải dữ liệu lớn trong cơ sở dữ liệu, sau đó xây dựng lại chỉ mục sau. Duy trì chỉ số trong quá trình tải có nghĩa là liên tục tìm cách vào đĩa. Ngược lại, nếu bạn bỏ các chỉ mục, thì cơ sở dữ liệu có thể xây dựng lại chỉ mục bằng cách sắp xếp thông tin cần xử lý trước tiên (tất nhiên là sử dụng một sự hợp nhất!) Và sau đó tải nó vào cơ sở hạ tầng BTREE cho chỉ mục. (BTREE được giữ tự nhiên theo thứ tự, vì vậy bạn có thể tải một tệp từ bộ dữ liệu được sắp xếp với vài lần tìm kiếm vào đĩa.)

Đã có một số trường hợp hiểu được cách tránh tìm kiếm đĩa đã cho phép tôi thực hiện các công việc xử lý dữ liệu mất hàng giờ thay vì vài ngày hoặc vài tuần.


1
Rất hay, không nghĩ về các giả định được thực hiện khi truy cập cấu trúc dữ liệu. Cái nhìn sâu sắc :)
chutsu

2
Bạn có thể giải thích ý của bạn bằng cách "tìm vào đĩa" có nghĩa là tìm kiếm một số giá trị duy nhất khi dữ liệu được lưu trữ trên đĩa không?
James Wierzba

8
@JamesWierzba Tôi lấy nó từ ngữ cảnh mà anh ta có nghĩa là "tìm kiếm một vị trí trên đĩa". "Tìm kiếm" trên một thiết bị đĩa quay có nghĩa là, nhấc đầu đọc và di chuyển nó đến một địa chỉ tuyệt đối mới, đây là một hoạt động chậm nổi tiếng. Khi bạn truy cập dữ liệu theo thứ tự được lưu trữ, phần cứng đĩa không phải tìm kiếm, nó chỉ cày theo tốc độ cao, đọc các mục theo tuần tự.
nclark

1
Một số có thể giải thích điều này nhiều hơn một chút? Đây là cách tôi đang nhìn thấy nó: Quicksort: Nếu chúng ta đang đi với trục ngẫu nhiên, ngăn xếp cuộc gọi có các đoạn của mảng được phân vùng một cách ngẫu nhiên. Điều này đòi hỏi truy cập ngẫu nhiên. Tuy nhiên, đối với mỗi cuộc gọi trong ngăn xếp, cả hai con trỏ trái và phải di chuyển tuần tự. Tôi giả sử những thứ này sẽ được giữ trong bộ đệm. Các giao dịch hoán đổi là hoạt động trở lại trên thông tin trong bộ đệm (và cuối cùng được ghi vào đĩa). (tiếp tục trong bình luận tiếp theo của tôi)
sam

1
Chỉ là một đóng góp tránh chi phí đọc / ghi đĩa tốn kém : Khi sắp xếp dữ liệu rất lớn cần truy cập đĩa, sẽ thuận lợi hơn khi chuyển hướng sắp xếp cho mỗi lần vượt qua. Đó là, ở cấp độ cao nhất của vòng lặp, một khi bạn đi từ 0hướng tới nvà lần tiếp theo bạn đi từ nhướng tới 0. Điều này mang lại lợi thế cho việc rút lui (sắp xếp) các khối dữ liệu đã có sẵn trong bộ nhớ (bộ đệm) và tấn công hai lần chỉ với một lần truy cập đĩa. Tôi nghĩ rằng hầu hết các DBMS đều sử dụng kỹ thuật tối ưu hóa này.
ssd

89

Trên thực tế, QuickSort là O (n 2 ). Thời gian chạy trường hợp trung bình của nó là O (nlog (n)), nhưng trường hợp xấu nhất của nó là O (n 2 ), xảy ra khi bạn chạy nó trong danh sách có chứa một vài mục duy nhất. Ngẫu nhiên mất O (n). Tất nhiên, điều này không thay đổi trường hợp xấu nhất của nó, nó chỉ ngăn người dùng độc hại khiến việc sắp xếp của bạn mất nhiều thời gian.

QuickSort phổ biến hơn vì nó:

  1. Là tại chỗ (MergeSort yêu cầu thêm bộ nhớ tuyến tính với số lượng phần tử được sắp xếp).
  2. Có một hằng số nhỏ ẩn.

4
Trên thực tế, có triển khai QuickSort là O (n * log (n)), không phải O (n ^ 2) trong trường hợp xấu nhất.
jfs

12
Nó cũng phụ thuộc vào kiến ​​trúc máy tính. Quicksort lợi ích từ bộ đệm, trong khi MergeSort thì không.
Cristian Ciupitu

4
@JF Sebastian: Đây có thể là những triển khai hướng nội nhất, không phải quicksort (introsort bắt đầu là quicksort và chuyển sang heapsort nếu nó sắp dừng là n * log (n)).
CesarB

44
Bạn có thể thực hiện sáp nhập tại chỗ.
Marcin

6
Sắp xếp hợp nhất có thể được thực hiện theo cách chỉ yêu cầu lưu trữ bổ sung O (1), nhưng hầu hết các triển khai đó bị ảnh hưởng rất nhiều về hiệu suất.
Rõ ràng hơn

29

"và hầu hết mọi người sử dụng Quicksort thay vì Mergesort. Tại sao vậy?"

Một lý do tâm lý chưa được đưa ra chỉ đơn giản là Quicksort được đặt tên khéo léo hơn. tức là tiếp thị tốt.

Đúng, Quicksort với chia tay ba có lẽ là một trong những thuật toán sắp xếp mục đích chung tốt nhất, nhưng không có gì vượt qua thực tế là sắp xếp "Quick" nghe mạnh hơn nhiều so với sắp xếp "Hợp nhất".


3
Không trả lời câu hỏi về cái nào tốt hơn. Tên của thuật toán là không liên quan trong việc xác định cái nào tốt hơn.
Nick Gallolas

18

Như những người khác đã lưu ý, trường hợp xấu nhất của Quicksort là O (n ^ 2), trong khi sáp nhập và heapsort ở lại O (nlogn). Tuy nhiên, trong trường hợp trung bình, cả ba đều là O (nlogn); vì vậy họ cho phần lớn các trường hợp có thể so sánh.

Điều làm cho Quicksort trung bình tốt hơn là vòng lặp bên trong ngụ ý so sánh một số giá trị với một giá trị duy nhất, trong khi trên hai thuật ngữ còn lại thì khác nhau cho mỗi so sánh. Nói cách khác, Quicksort thực hiện một nửa số lần đọc như hai thuật toán còn lại. Trên CPU hiện đại, hiệu năng bị chi phối rất nhiều bởi thời gian truy cập, vì vậy cuối cùng Quicksort cuối cùng là một lựa chọn đầu tiên tuyệt vời.


9

Tôi muốn thêm rằng ba algoritms được đề cập cho đến nay (mergesort, quicksort và heap sort) chỉ hợp nhất là ổn định. Đó là, thứ tự không thay đổi đối với những giá trị có cùng khóa. Trong một số trường hợp điều này là mong muốn.

Nhưng, sự thật mà nói, trong các tình huống thực tế, hầu hết mọi người chỉ cần hiệu suất trung bình tốt và quicksort là ... quick =)

Tất cả các thuật toán sắp xếp có những thăng trầm của họ. Xem bài viết Wikipedia để sắp xếp các thuật toán để có cái nhìn tổng quan tốt.


7

Từ mục Wikipedia trên Quicksort :

Quicksort cũng cạnh tranh với mergesort, một thuật toán sắp xếp đệ quy khác nhưng với lợi ích của thời gian chạy (nlogn) trong trường hợp xấu nhất. Mergesort là một loại ổn định, không giống như quicksort và heapsort, và có thể dễ dàng điều chỉnh để hoạt động trên các danh sách được liên kết và danh sách rất lớn được lưu trữ trên phương tiện truy cập chậm như lưu trữ đĩa hoặc lưu trữ gắn mạng. Mặc dù quicksort có thể được viết để hoạt động trên các danh sách được liên kết, nhưng nó thường sẽ chịu các lựa chọn trục kém mà không có quyền truy cập ngẫu nhiên. Nhược điểm chính của sáp nhập là, khi hoạt động trên mảng, nó đòi hỏi không gian phụ trợ Θ (n) trong trường hợp tốt nhất, trong khi biến thể của quicksort với phân vùng tại chỗ và đệ quy đuôi chỉ sử dụng không gian Θ (logn). (Lưu ý rằng khi vận hành trên các danh sách được liên kết, việc hợp nhất chỉ yêu cầu một lượng lưu trữ phụ trợ nhỏ, không đổi.)


7

Mu! Quicksort không tốt hơn, nó rất phù hợp cho một loại ứng dụng khác, hơn là sáp nhập.

Mergesort đáng để xem xét nếu tốc độ là điều cốt yếu, hiệu suất trong trường hợp xấu nhất không thể được chấp nhận và có thêm không gian. 1

Bạn nói rằng họ «Cả hai đều là O (nlogn) [Mạnh]». Cái này sai. «Quicksort sử dụng khoảng n ^ 2/2 so sánh trong trường hợp xấu nhất.» 1 .

Tuy nhiên, tài sản quan trọng nhất theo kinh nghiệm của tôi là việc thực hiện dễ dàng truy cập tuần tự mà bạn có thể sử dụng trong khi sắp xếp khi sử dụng ngôn ngữ lập trình với mô hình bắt buộc.

1 Sedgewick, Thuật toán


Sáp nhập có thể được thực hiện tại chỗ, do đó nó không cần thêm không gian. Ví dụ: với danh sách được liên kết kép: stackoverflow.com/questions/2938495/iêu
lanoxx

6

Quicksort là thuật toán sắp xếp nhanh nhất trong thực tế nhưng có một số trường hợp bệnh lý có thể khiến nó hoạt động kém như O (n2).

Heapsort được đảm bảo để chạy trong O (n * ln (n)) và chỉ yêu cầu lưu trữ bổ sung hữu hạn. Nhưng có nhiều trích dẫn của các bài kiểm tra trong thế giới thực cho thấy heapsort chậm hơn đáng kể so với quicksort trung bình.


5

Lời giải thích của Wikipedia là:

Thông thường, quicksort trong thực tế nhanh hơn đáng kể so với các thuật toán (nlogn) khác, bởi vì vòng lặp bên trong của nó có thể được thực hiện hiệu quả trên hầu hết các kiến ​​trúc, và trong hầu hết các dữ liệu trong thế giới thực, có thể đưa ra các lựa chọn thiết kế nhằm giảm thiểu xác suất yêu cầu thời gian bậc hai .

Sắp xếp nhanh chóng

Sáp nhập

Tôi nghĩ cũng có vấn đề với dung lượng lưu trữ cần thiết cho Mergesort (đó là Ω (n)) mà việc triển khai quicksort không có. Trong trường hợp xấu nhất, chúng có cùng thời lượng thuật toán, nhưng sáp nhập đòi hỏi nhiều dung lượng hơn.


Trường hợp xấu nhất của quicksort là O (n), sáp nhập O (n log n) - vì vậy có một sự khác biệt lớn ở đó.
paul23

1
trường hợp xấu nhất quicksort là O (n ^ 2) - không thể chỉnh sửa nhận xét trước đó của tôi và mắc lỗi đánh máy
paul23

@ paul23 ý kiến ​​có thể bị xóa. Ngoài ra, câu trả lời đã giải quyết vấn đề của bạn: "trong hầu hết các dữ liệu trong thế giới thực, có thể đưa ra các lựa chọn thiết kế nhằm giảm thiểu xác suất yêu cầu thời gian bậc hai"
Jim Balter

5

Tôi muốn thêm vào các câu trả lời tuyệt vời hiện có một số phép toán về cách QuickSort thực hiện khi chuyển hướng từ trường hợp tốt nhất và khả năng đó là gì, tôi hy vọng sẽ giúp mọi người hiểu rõ hơn một chút tại sao trường hợp O (n ^ 2) không có thật mối quan tâm trong việc triển khai QuickSort tinh vi hơn.

Ngoài các vấn đề truy cập ngẫu nhiên, có hai yếu tố chính có thể ảnh hưởng đến hiệu suất của QuickSort và cả hai đều liên quan đến cách trục xoay so với dữ liệu được sắp xếp.

1) Một số lượng nhỏ các khóa trong dữ liệu. Một bộ dữ liệu có cùng giá trị sẽ sắp xếp trong n ^ 2 lần trên QuickSort phân vùng 2 vanilla vì tất cả các giá trị ngoại trừ vị trí trục được đặt ở một bên mỗi lần. Các triển khai hiện đại giải quyết vấn đề này bằng các phương pháp như sử dụng sắp xếp 3 phân vùng. Các phương thức này thực hiện trên một tập dữ liệu của tất cả cùng một giá trị trong thời gian O (n). Vì vậy, sử dụng triển khai như vậy có nghĩa là đầu vào có số lượng khóa nhỏ thực sự cải thiện thời gian thực hiện và không còn là vấn đề đáng lo ngại.

2) Lựa chọn trục cực kỳ xấu có thể gây ra hiệu suất trường hợp xấu nhất. Trong trường hợp lý tưởng, trục sẽ luôn sao cho 50% dữ liệu nhỏ hơn và 50% dữ liệu lớn hơn, do đó đầu vào sẽ bị phá vỡ một nửa trong mỗi lần lặp. Điều này cho chúng ta n so sánh và hoán đổi lần log-2 (n) thu hồi thời gian O (n * logn).

Làm thế nào nhiều lựa chọn trục không lý tưởng ảnh hưởng đến thời gian thực hiện?

Hãy xem xét trường hợp trục được chọn liên tục sao cho 75% dữ liệu nằm ở một bên của trục. Nó vẫn là O (n * logn) nhưng bây giờ cơ sở của nhật ký đã thay đổi thành 1 / 0,75 hoặc 1,33. Mối quan hệ trong hiệu suất khi thay đổi cơ sở luôn là một hằng số được biểu thị bằng log (2) / log (newBase). Trong trường hợp này, hằng số đó là 2,4. Vì vậy, chất lượng của sự lựa chọn trục này mất hơn 2,4 lần so với lý tưởng.

Làm thế nào nhanh chóng điều này trở nên tồi tệ?

Không nhanh lắm cho đến khi lựa chọn trục bị (nhất quán) rất tệ:

  • 50% cho một bên: (trường hợp lý tưởng)
  • 75% cho một bên: dài gấp 2,4 lần
  • 90% ở một bên: dài gấp 6,6 lần
  • 95% ở một bên: dài 13,5 lần
  • 99% ở một bên: dài gấp 69 lần

Khi chúng tôi tiếp cận 100% ở một bên, phần nhật ký của thực thi sẽ tiếp cận n và toàn bộ thực thi tiếp cận theo phương pháp tiệm cận O (n ^ 2).

Trong triển khai QuickSort ngây thơ, các trường hợp như mảng được sắp xếp (đối với trục phần tử thứ 1) hoặc mảng được sắp xếp ngược (đối với trục phần tử cuối cùng) sẽ tạo ra thời gian thực hiện O (n ^ 2) trong trường hợp xấu nhất. Ngoài ra, việc triển khai với lựa chọn trục có thể dự đoán được có thể bị tấn công DoS bởi dữ liệu được thiết kế để tạo ra trường hợp thực thi tồi tệ nhất. Việc triển khai hiện đại tránh điều này bằng nhiều phương pháp, chẳng hạn như ngẫu nhiên hóa dữ liệu trước khi sắp xếp, chọn trung bình của 3 chỉ số được chọn ngẫu nhiên, v.v ... Với sự ngẫu nhiên này trong hỗn hợp, chúng tôi có 2 trường hợp:

  • Tập dữ liệu nhỏ. Trường hợp xấu nhất là có thể hợp lý nhưng O (n ^ 2) không phải là thảm họa vì n đủ nhỏ để n ^ 2 cũng nhỏ.
  • Tập dữ liệu lớn. Trường hợp xấu nhất là có thể trong lý thuyết nhưng không phải trong thực tế.

Làm thế nào chúng ta có thể thấy hiệu suất khủng khiếp?

Các cơ hội đang biến mất nhỏ . Hãy xem xét một loại 5.000 giá trị:

Việc triển khai giả thuyết của chúng tôi sẽ chọn một trục bằng cách sử dụng trung bình 3 chỉ số được chọn ngẫu nhiên. Chúng tôi sẽ coi các pivots nằm trong phạm vi 25% -75% là "tốt" và các pivots nằm trong phạm vi 0% -25% hoặc 75% -100% là "xấu". Nếu bạn nhìn vào phân phối xác suất bằng cách sử dụng trung bình của 3 chỉ số ngẫu nhiên, mỗi lần đệ quy có 11/16 cơ hội kết thúc với một trục tốt. Chúng ta hãy đưa ra 2 giả định bảo thủ (và sai) để đơn giản hóa toán học:

  1. Pivots tốt luôn chính xác ở mức phân chia 25% / 75% và hoạt động ở trường hợp lý tưởng 2,4 *. Chúng tôi không bao giờ có được một sự phân chia lý tưởng hoặc bất kỳ sự phân chia nào tốt hơn 25/75.

  2. Pivots xấu luôn là trường hợp xấu nhất và về cơ bản không đóng góp gì cho giải pháp.

Việc triển khai QuickSort của chúng tôi sẽ dừng ở n = 10 và chuyển sang sắp xếp chèn, vì vậy chúng tôi yêu cầu 22 phân vùng trục 25% / 75% để phá vỡ 5.000 giá trị đầu vào cho đến nay. (10 * 1.333333 ^ 22> 5000) Hoặc, chúng tôi yêu cầu 4990 pivots trường hợp xấu nhất. Hãy nhớ rằng nếu chúng ta tích lũy được 22 pivots tốt tại bất kỳ thời điểm nào thì việc sắp xếp sẽ hoàn thành, vì vậy trường hợp xấu nhất hoặc bất cứ điều gì gần nó đòi hỏi cực kỳ xui xẻo. Nếu chúng tôi mất 88 lần thu hồi để thực sự đạt được 22 pivots tốt cần thiết để sắp xếp xuống n = 10, thì đó sẽ là trường hợp lý tưởng 4 * 2.4 * hoặc khoảng 10 lần thời gian thực hiện trường hợp lý tưởng. Làm thế nào có khả năng là chúng ta sẽ không đạt được 22 pivots tốt cần thiết sau 88 lần thu hồi?

Phân phối xác suất nhị thức có thể trả lời điều đó, và câu trả lời là khoảng 10 ^ -18. (n là 88, k là 21, p là 0,6875) sử dụng của bạn là khoảng một ngàn lần nhiều khả năng bị sét đánh trong 1 giây cần thiết để bấm [Sắp xếp] hơn là họ sẽ thấy rằng 5.000 mục loại chạy bất kỳ tồi tệ hơn hơn 10 * trường hợp lý tưởng. Cơ hội này trở nên nhỏ hơn khi tập dữ liệu trở nên lớn hơn. Dưới đây là một số kích thước mảng và cơ hội tương ứng của chúng để chạy dài hơn 10 * lý tưởng:

  • Mảng gồm 640 mục: 10 ^ -13 (yêu cầu 15 điểm xoay vòng tốt trong số 60 lần thử)
  • Mảng 5.000 mặt hàng: 10 ^ -18 (yêu cầu 22 pivots tốt trong số 88 lần thử)
  • Mảng gồm 40.000 mặt hàng: 10 ^ -23 (yêu cầu 29 pivots tốt trong số 116)

Hãy nhớ rằng đây là với 2 giả định bảo thủ tồi tệ hơn thực tế. Vì vậy, hiệu suất thực tế là tốt hơn và sự cân bằng của xác suất còn lại gần với lý tưởng hơn là không.

Cuối cùng, như những người khác đã đề cập, ngay cả những trường hợp không có khả năng vô lý này cũng có thể được loại bỏ bằng cách chuyển sang loại heap nếu ngăn đệ quy đi quá sâu. Vì vậy, TLDR là, để triển khai QuickSort tốt, trường hợp xấu nhất không thực sự tồn tại vì nó đã được thiết kế và thực thi hoàn thành trong thời gian O (n * logn).


1
"Những câu trả lời tuyệt vời hiện có" - đó là những câu trả lời nào? Tôi không thể xác định vị trí của họ.
Jim Balter

Có bất kỳ biến thể nào của Sắp xếp nhanh thông báo cho chức năng so sánh về các phân vùng, theo cách cho phép nó khai thác các tình huống trong đó một phần đáng kể của khóa sẽ giống nhau cho tất cả các mục trong phân vùng không?
supercat

4

Tại sao Quicksort tốt?

  • QuickSort mất N ^ 2 trong trường hợp xấu nhất và trường hợp trung bình NlogN. Trường hợp xấu nhất xảy ra khi dữ liệu được sắp xếp. Điều này có thể được giảm thiểu bằng cách xáo trộn ngẫu nhiên trước khi bắt đầu sắp xếp.
  • QuickSort không chiếm thêm bộ nhớ được lấy bằng cách sắp xếp hợp nhất.
  • Nếu tập dữ liệu lớn và có các mục giống hệt nhau, độ phức tạp của Quicksort sẽ giảm bằng cách sử dụng phân vùng 3 chiều. Nhiều hơn không có mặt hàng giống hệt tốt hơn sắp xếp. Nếu tất cả các mục là giống hệt nhau, nó sắp xếp theo thời gian tuyến tính. [Đây là triển khai mặc định trong hầu hết các thư viện]

Quicksort luôn tốt hơn Mergesort?

Không hẳn vậy.

  • Mergesort ổn định nhưng Quicksort thì không. Vì vậy, nếu bạn cần sự ổn định trong đầu ra, bạn sẽ sử dụng Mergesort. Sự ổn định là cần thiết trong nhiều ứng dụng thực tế.
  • Bộ nhớ ngày nay rẻ. Vì vậy, nếu bộ nhớ thêm được sử dụng bởi Mergesort không quan trọng đối với ứng dụng của bạn, thì không có hại khi sử dụng Mergesort.

Lưu ý: Trong java, hàm Arrays.sort () sử dụng Quicksort cho các kiểu dữ liệu nguyên thủy và Mergesort cho các kiểu dữ liệu đối tượng. Bởi vì các đối tượng tiêu thụ chi phí bộ nhớ, do đó, thêm một chút chi phí cho Mergesort có thể không phải là bất kỳ vấn đề nào đối với quan điểm hiệu suất.

Tham khảo : Xem video QuickSort của Tuần 3, Khóa học thuật toán Princeton tại Coursera


"Điều này có thể được giảm thiểu bằng cách xáo trộn ngẫu nhiên trước khi bắt đầu phân loại." - er, không, điều đó sẽ tốn kém. Thay vào đó, sử dụng pivots ngẫu nhiên.
Jim Balter

4

Quicksort KHÔNG tốt hơn sáp nhập. Với O (n ^ 2) (trường hợp xấu nhất hiếm khi xảy ra), quicksort có khả năng chậm hơn nhiều so với O (nlogn) của loại hợp nhất. Quicksort có ít chi phí hoạt động hơn, vì vậy với máy tính nhỏ và chậm, tốt hơn. Nhưng máy tính ngày nay nhanh đến mức chi phí bổ sung của sáp nhập là không đáng kể và nguy cơ xảy ra sự cố nhanh rất chậm vượt xa chi phí không đáng kể của sáp nhập trong hầu hết các trường hợp.

Ngoài ra, một sự hợp nhất để lại các mục với các khóa giống hệt nhau theo thứ tự ban đầu của chúng, một thuộc tính hữu ích.


2
Câu thứ hai của bạn nói rằng "... sự hợp nhất có khả năng chậm hơn nhiều so với ... sự hợp nhất". Tài liệu tham khảo đầu tiên có lẽ là quicksort.
Jonathan Leffler

Hợp nhất sắp xếp chỉ ổn định nếu thuật toán hợp nhất ổn định; Điều này không được đảm bảo.
Rõ ràng hơn

@Clearer Nó được đảm bảo nếu <=được sử dụng để so sánh chứ không phải <, và không có lý do gì để không.
Jim Balter

@JimBalter Tôi có thể dễ dàng đưa ra một thuật toán hợp nhất không ổn định (ví dụ quicksort, sẽ phục vụ vai trò đó). Lý do tại sao sắp xếp nhanh nhanh hơn sắp xếp hợp nhất trong nhiều trường hợp không phải vì giảm chi phí mà là do cách quicksort truy cập dữ liệu, thân thiện với bộ đệm hơn rất nhiều so với hợp nhất tiêu chuẩn.
Rõ ràng

@Clearer quicksort không phải là một loại hợp nhất ... tuyên bố ngày 21 tháng 12 năm 14 của bạn mà tôi đã phản hồi nghiêm túc về sắp xếp hợp nhất và liệu nó có ổn định không. quicksort và nhanh hơn hoàn toàn không liên quan đến bình luận của bạn hoặc phản hồi của tôi. Kết thúc cuộc thảo luận cho tôi ... hết lần này đến lần khác.
Jim Balter

3

Câu trả lời sẽ hơi nghiêng về phía quicksort wrt với những thay đổi được mang theo DualPOLLQuickSort cho các giá trị nguyên thủy. Nó được sử dụng trong JAVA 7 để sắp xếp trong java.util.Arrays

It is proved that for the Dual-Pivot Quicksort the average number of
comparisons is 2*n*ln(n), the average number of swaps is 0.8*n*ln(n),
whereas classical Quicksort algorithm has 2*n*ln(n) and 1*n*ln(n)
respectively. Full mathematical proof see in attached proof.txt
and proof_add.txt files. Theoretical results are also confirmed
by experimental counting of the operations.

Bạn có thể tìm thấy sự cấy ghép JAVA7 tại đây - http://grepcode.com/file/reposective.grepcode.com/java/root/jdk/openjdk/7-b147/java/util/Arrays.java

Đọc thêm tuyệt vời trên DualPOLLQuickSort - http://permalink.gmane.org/gmane.comp.java.openjdk.core-libs.devel/2628


3

Trong sắp xếp hợp nhất, thuật toán chung là:

  1. Sắp xếp mảng con bên trái
  2. Sắp xếp mảng con bên phải
  3. Hợp nhất 2 mảng con được sắp xếp

Ở cấp độ cao nhất, việc hợp nhất 2 mảng con được sắp xếp liên quan đến việc xử lý N phần tử.

Một mức dưới mức đó, mỗi lần lặp của bước 3 liên quan đến việc xử lý các yếu tố N / 2, nhưng bạn phải lặp lại quy trình này hai lần. Vì vậy, bạn vẫn đang xử lý các phần tử 2 * N / 2 == N.

Dưới một cấp độ, bạn đang hợp nhất các yếu tố 4 * N / 4 == N, v.v. Mỗi độ sâu trong ngăn xếp đệ quy liên quan đến việc hợp nhất cùng một số phần tử, trên tất cả các lệnh gọi cho độ sâu đó.

Hãy xem xét thuật toán sắp xếp nhanh thay thế:

  1. Chọn một điểm trục
  2. Đặt điểm trục ở vị trí chính xác trong mảng, với tất cả các phần tử nhỏ hơn ở bên trái và các phần tử lớn hơn ở bên phải
  3. Sắp xếp phân khúc bên trái
  4. Sắp xếp phân khúc bên phải

Ở cấp độ cao nhất, bạn đang xử lý một mảng có kích thước N. Sau đó, bạn chọn một điểm trục, đặt nó vào vị trí chính xác và sau đó có thể bỏ qua nó hoàn toàn cho phần còn lại của thuật toán.

Dưới một mức, bạn đang xử lý 2 mảng con có kích thước kết hợp là N-1 (nghĩa là trừ điểm trục trước đó). Bạn chọn một điểm trục cho mỗi mảng phụ, có tới 2 điểm trục bổ sung.

Dưới một mức, bạn đang xử lý 4 mảng con với kích thước kết hợp N-3, với cùng lý do như trên.

Rồi N-7 ... Rồi N-15 ... Rồi N-32 ...

Độ sâu của ngăn xếp đệ quy của bạn vẫn giữ nguyên (logN). Với sắp xếp hợp nhất, bạn luôn luôn xử lý hợp nhất phần tử N, qua từng cấp của ngăn xếp đệ quy. Tuy nhiên, với cách sắp xếp nhanh, số lượng phần tử mà bạn đang xử lý giảm dần khi bạn đi xuống ngăn xếp. Ví dụ: nếu bạn nhìn vào độ sâu giữa chừng của ngăn đệ quy, số phần tử bạn xử lý là N - 2 ^ ((logN) / 2)) == N - sqrt (N).

Disclaimer: Trên merge-sort, vì bạn chia mảng thành 2 phần chính xác bằng nhau mỗi lần, độ sâu đệ quy chính xác là logN. Về sắp xếp nhanh, vì điểm trục của bạn không chắc là chính xác ở giữa mảng, độ sâu của ngăn xếp đệ quy của bạn có thể lớn hơn logN một chút. Tôi đã không thực hiện phép toán để xem vai trò của yếu tố này và yếu tố được mô tả ở trên, thực sự đóng vai trò như thế nào trong độ phức tạp của thuật toán.


Rằng các trụ cột không phải là một phần của các loại ở cấp độ tiếp theo không phải là lý do tại sao QS có hiệu suất cao hơn. Xem các câu trả lời khác để có cái nhìn sâu sắc bổ sung.
Jim Balter

@JimBalter Bạn đang đề cập đến "câu trả lời nào khác"? Câu trả lời hàng đầu chỉ nói rằng QS "yêu cầu ít không gian bổ sung và thể hiện địa phương bộ đệm tốt" nhưng không đưa ra lời giải thích nào về lý do đó, cũng như không cung cấp bất kỳ trích dẫn nào. Câu trả lời thứ 2 chỉ đơn giản nói rằng hợp nhất sắp xếp tốt hơn cho các tập dữ liệu lớn hơn
RvPr

Bạn đang di chuyển các cột mục tiêu, từ lý do tại sao QS hoạt động hiệu quả hơn để giải thích các sự kiện cơ bản về cách thức hoạt động của nó. Câu trả lời cho các câu hỏi khác làm điều đó: stackoverflow.com/questions/9444714/ cấp ... Tôi hy vọng điều đó là đủ cho bạn; Tôi sẽ không trả lời thêm.
Jim Balter

3

Không giống như Hợp nhất Sắp xếp nhanh Sắp xếp không sử dụng một không gian bổ trợ. Trong khi Hợp nhất Sắp xếp sử dụng một không gian bổ trợ O (n). Nhưng Hợp nhất Sắp xếp có độ phức tạp trường hợp xấu nhất của O (nlogn) trong khi độ phức tạp trường hợp xấu nhất của Sắp xếp nhanh là O (n ^ 2) xảy ra khi mảng đã được sắp xếp.


Không, trường hợp xấu nhất của QuickSort không xảy ra khi mảng đã được sắp xếp, trừ khi bạn sử dụng mục đầu tiên hoặc cuối cùng làm trục, nhưng không ai làm điều đó.
Jim Balter

2

Quicksort có độ phức tạp trung bình tốt hơn nhưng trong một số ứng dụng thì đó là sự lựa chọn sai. Quicksort dễ bị tấn công từ chối dịch vụ. Nếu kẻ tấn công có thể chọn đầu vào được sắp xếp, anh ta có thể dễ dàng xây dựng một tập hợp có độ phức tạp thời gian trong trường hợp xấu nhất là o (n ^ 2).

Độ phức tạp trường hợp trung bình của Mergesort và độ phức tạp của trường hợp xấu nhất là như nhau, và như vậy không gặp phải vấn đề tương tự. Thuộc tính sắp xếp hợp nhất này cũng làm cho nó trở thành lựa chọn ưu việt cho các hệ thống thời gian thực - chính xác là vì không có trường hợp bệnh lý nào khiến nó chạy chậm hơn nhiều.

Tôi là một fan hâm mộ lớn hơn của Mergesort so với tôi của Quicksort, vì những lý do này.


2
Làm thế nào để Quicksort có độ phức tạp trung bình tốt hơn? Cả hai đều là O (nlgn). Tôi sẽ lập luận rằng một kẻ tấn công sẽ không cung cấp đầu vào cho bất kỳ thuật toán sắp xếp nào ... nhưng vì lợi ích của việc không giả định bảo mật bằng cách tối nghĩa, hãy giả sử rằng anh ta có thể. Mặc dù thời gian chạy của n ^ 2 kém hơn nlgn, nhưng máy chủ web sẽ bị sập dựa trên một cuộc tấn công không đủ. Trên thực tế, đối số DOS gần như không có giá trị, bởi vì bất kỳ máy chủ web nào cũng dễ bị tấn công DDOS và nhiều khả năng kẻ tấn công sẽ sử dụng mạng máy chủ phân tán, tất cả đều tràn ngập TCP TCP.
CaTalyst.X

"Quicksort có độ phức tạp trung bình tốt hơn" - không, không.
Jim Balter

2

Điều đó thật khó nói. Điều tồi tệ nhất của MergeSort là n (log2n) -n + 1, điều này là chính xác nếu n bằng 2 ^ k (tôi đã chứng minh điều này). Và đối với mọi n, nó nằm giữa (n lg n - n + 1) và (n lg n + n + O (lg n)). Nhưng đối với quickSort, tốt nhất của nó là nlog2n (cũng n bằng 2 ^ k). Nếu bạn chia Mergesort cho quickSort, nó sẽ bằng một khi n là vô hạn. Nếu như trường hợp xấu nhất của MergeSort tốt hơn trường hợp QuickSort tốt nhất, tại sao chúng ta sử dụng quicksort? Nhưng hãy nhớ rằng, MergeSort không có, nó yêu cầu 2n không gian memeroy. Và MergeSort cũng cần phải thực hiện nhiều bản sao mảng, mà chúng ta cũng cần phải thực hiện nhiều bản sao mảng. không bao gồm trong phân tích thuật toán. Trong một từ, MergeSort thực sự khó hiểu hơn quicksort trong trị liệu, nhưng trong thực tế, bạn cần xem xét không gian ghi nhớ, chi phí sao chép mảng, sáp nhập chậm hơn so với sắp xếp nhanh. Tôi đã từng thực hiện thử nghiệm trong đó tôi đã được cung cấp 1000000 chữ số trong java bởi lớp Random,và phải mất 2610ms bằng cách sáp nhập, 1370ms bằng quicksort.


2

Sắp xếp nhanh là trường hợp xấu nhất O (n ^ 2), tuy nhiên, trường hợp trung bình luôn thực hiện sắp xếp hợp nhất. Mỗi thuật toán là O (nlogn), nhưng bạn cần nhớ rằng khi nói về Big O, chúng ta bỏ đi các yếu tố phức tạp thấp hơn. Sắp xếp nhanh có những cải tiến đáng kể so với sắp xếp hợp nhất khi nói đến các yếu tố không đổi.

Sắp xếp hợp nhất cũng yêu cầu bộ nhớ O (2n), trong khi sắp xếp nhanh có thể được thực hiện tại chỗ (chỉ yêu cầu O (n)). Đây là một lý do khác mà sắp xếp nhanh thường được ưa thích hơn sắp xếp hợp nhất.

Thông tin thêm:

Trường hợp xấu nhất của sắp xếp nhanh xảy ra khi trục được chọn kém. Hãy xem xét ví dụ sau:

[5, 4, 3, 2, 1]

Nếu trục được chọn là số nhỏ nhất hoặc lớn nhất trong nhóm thì sắp xếp nhanh sẽ chạy trong O (n ^ 2). Xác suất chọn phần tử nằm trong 25% lớn nhất hoặc nhỏ nhất trong danh sách là 0,5. Điều đó mang lại cho thuật toán 0,5 cơ hội trở thành một trục tốt. Nếu chúng tôi sử dụng thuật toán chọn trục điển hình (giả sử chọn một yếu tố ngẫu nhiên), chúng tôi có 0,5 cơ hội chọn trục tốt cho mỗi lựa chọn trục. Đối với các bộ sưu tập có kích thước lớn, xác suất luôn chọn trục quay kém là 0,5 * n. Dựa trên xác suất này, sắp xếp nhanh là hiệu quả cho trường hợp trung bình (và điển hình).


O (2n) == O (n). Phát biểu đúng là Mergesort cần bộ nhớ bổ sung O (n) (cụ thể hơn, nó cần n / 2 bộ nhớ phụ). Và điều này không đúng với danh sách liên kết.
Jim Balter

@JimBalter Thưa ông, bạn có phiền khi chia sẻ những ý tưởng tuyệt vời và đáng giá của bạn với chúng tôi về sự hoàn hảo của họ như là một câu trả lời cho câu hỏi không? Cảm ơn trước.
snr

2

Đây là một câu hỏi khá cũ, nhưng vì tôi đã giải quyết cả hai gần đây nên đây là 2c của tôi:

Hợp nhất nhu cầu sắp xếp trung bình ~ N log N so sánh. Đối với các mảng đã được sắp xếp (gần như) đã sắp xếp xuống còn 1/2 N log N, vì trong khi hợp nhất, chúng tôi (hầu như) luôn chọn "trái" phần 1/2 N lần và sau đó chỉ cần sao chép đúng 1/2 phần tử. Ngoài ra, tôi có thể suy đoán rằng đầu vào đã được sắp xếp làm cho bộ dự đoán nhánh của bộ xử lý tỏa sáng nhưng đoán gần như tất cả các nhánh chính xác, do đó ngăn chặn các đường ống dẫn.

Sắp xếp nhanh trung bình yêu cầu so sánh ~ 1,38 N log N. Nó không được hưởng lợi nhiều từ mảng đã được sắp xếp về mặt so sánh (tuy nhiên, về mặt hoán đổi và có thể là về mặt dự đoán nhánh trong CPU).

Điểm chuẩn của tôi trên bộ xử lý khá hiện đại cho thấy như sau:

Khi chức năng so sánh là một hàm gọi lại (như trong triển khai libc qsort ()) thì tốc độ chậm hơn so với sáp nhập 15% trên đầu vào ngẫu nhiên và 30% cho mảng đã được sắp xếp cho số nguyên 64 bit.

Mặt khác, nếu so sánh không phải là một cuộc gọi lại, kinh nghiệm của tôi là quicksort vượt trội hơn so với sáp nhập tới 25%.

Tuy nhiên, nếu mảng (lớn) của bạn có rất ít giá trị duy nhất, sắp xếp hợp nhất bắt đầu đạt được trên quicksort trong mọi trường hợp.

Vì vậy, có thể điểm mấu chốt là: nếu so sánh là tốn kém (ví dụ: hàm gọi lại, so sánh các chuỗi, so sánh nhiều phần của cấu trúc chủ yếu là "nếu" để tạo ra sự khác biệt thứ hai - thứ ba - thì khả năng là bạn sẽ tốt hơn với sắp xếp hợp nhất. Đối với các nhiệm vụ đơn giản, quicksort sẽ nhanh hơn.

Điều đó nói rằng tất cả những gì đã nói trước đây là đúng: - Quicksort có thể là N ^ 2, nhưng Sedgewick tuyên bố rằng việc triển khai ngẫu nhiên tốt có nhiều khả năng máy tính thực hiện bị sét đánh hơn là đi N ^ 2 - Mergesort cần thêm không gian


Có qsort nhịp hợp nhất ngay cả đối với đầu vào được sắp xếp nếu so sánh là rẻ?
Eonil

2

Khi tôi thử nghiệm cả hai thuật toán sắp xếp, bằng cách đếm số lượng cuộc gọi đệ quy, quicksort luôn có các cuộc gọi đệ quy ít hơn so với sáp nhập. Đó là bởi vì quicksort có pivots và pivots không được bao gồm trong các cuộc gọi đệ quy tiếp theo. Bằng cách đó quicksort có thể đạt được trường hợp cơ sở đệ quy nhanh hơn so với sáp nhập.


Pivots không liên quan gì đến lý do tại sao QS có ít cuộc gọi đệ quy hơn ... đó là vì một nửa đệ quy của QS là đệ quy đuôi, có thể được loại bỏ.
Jim Balter

2

Đây là một câu hỏi phổ biến được hỏi trong các cuộc phỏng vấn rằng mặc dù hiệu suất tốt nhất của loại hợp nhất, quicksort được coi là tốt hơn so với sắp xếp hợp nhất, đặc biệt là cho một đầu vào lớn. Có một số lý do nhất định do quicksort tốt hơn:

1- Không gian phụ trợ: Sắp xếp nhanh là thuật toán sắp xếp tại chỗ. Sắp xếp tại chỗ có nghĩa là không cần thêm không gian lưu trữ để thực hiện sắp xếp. Mặt khác, sắp xếp hợp nhất yêu cầu một mảng tạm thời để hợp nhất các mảng được sắp xếp và do đó nó không đúng chỗ.

2- Trường hợp xấu nhất: Trường hợp xấu nhất của quicksort O(n^2)có thể tránh được bằng cách sử dụng quicksort ngẫu nhiên. Có thể dễ dàng tránh được với xác suất cao bằng cách chọn đúng trục. Có được một hành vi trường hợp trung bình bằng cách chọn phần tử trục chính làm cho nó cải thiện hiệu suất và trở nên hiệu quả như sắp xếp Hợp nhất.

3- Địa phương tham chiếu: Quicksort đặc biệt thể hiện vị trí bộ đệm tốt và điều này làm cho nó nhanh hơn so với sắp xếp hợp nhất trong nhiều trường hợp như trong môi trường bộ nhớ ảo.

4- Đệ quy đuôi: QuickSort là đệ quy đuôi trong khi sắp xếp Hợp nhất thì không. Hàm đệ quy đuôi là một hàm trong đó cuộc gọi đệ quy là điều cuối cùng được thực hiện bởi hàm. Các hàm đệ quy đuôi được coi là tốt hơn các hàm đệ quy không đuôi vì đệ quy đuôi có thể được tối ưu hóa bởi trình biên dịch.


1

Mặc dù cả hai đều thuộc cùng một lớp phức tạp, điều đó không có nghĩa là cả hai đều có cùng thời gian chạy. Quicksort thường nhanh hơn sáp nhập, chỉ vì việc mã hóa việc thực hiện chặt chẽ dễ dàng hơn và các hoạt động mà nó có thể diễn ra nhanh hơn. Đó là bởi vì quicksort thường nhanh hơn mà mọi người sử dụng nó thay vì sáp nhập.

Tuy nhiên! Cá nhân tôi thường sẽ sử dụng sáp nhập hoặc một biến thể quicksort chuyển sang sáp nhập khi quicksort hoạt động kém. Nhớ lại. Quicksort chỉ trung bình O (n log n) . Trường hợp xấu nhất là O (n ^ 2)! Sáp nhập luôn là O (n log n). Trong trường hợp bắt buộc phải thực hiện hoặc đáp ứng thời gian thực và dữ liệu đầu vào của bạn đến từ một nguồn độc hại, bạn không nên sử dụng quicksort đơn giản.


1

Tất cả mọi thứ đều bình đẳng, tôi hy vọng hầu hết mọi người sẽ sử dụng bất cứ thứ gì có sẵn một cách thuận tiện nhất và điều đó có xu hướng là qsort (3). Khác với quicksort được biết là rất nhanh trên mảng, giống như sáp nhập là lựa chọn phổ biến cho các danh sách.

Điều tôi băn khoăn là tại sao rất hiếm thấy radix hoặc xô sắp xếp. Chúng là O (n), ít nhất là trên các danh sách được liên kết và tất cả những gì nó cần là một số phương pháp chuyển đổi khóa thành số thứ tự. (chuỗi và phao chỉ hoạt động tốt.)

Tôi đang nghĩ lý do liên quan đến cách dạy khoa học máy tính. Tôi thậm chí đã phải chứng minh với giảng viên của mình về phân tích Thuật toán rằng thực sự có thể sắp xếp nhanh hơn O (n log (n)). (Anh ta có bằng chứng rằng bạn không thể so sánh sắp xếp nhanh hơn O (n log (n)), điều này là đúng.)

Trong các tin tức khác, phao có thể được sắp xếp dưới dạng số nguyên, nhưng bạn phải chuyển các số âm xung quanh sau đó.

Chỉnh sửa: Trên thực tế, đây là một cách thậm chí còn luẩn quẩn hơn để sắp xếp số nguyên-float: http://www.stereopsis.com/radix.html . Lưu ý rằng thủ thuật lật bit có thể được sử dụng bất kể thuật toán sắp xếp nào bạn thực sự sử dụng ...


1
Tôi đã thấy chia sẻ của tôi về các loại cơ số. Nhưng nó khá khó sử dụng vì nếu phân tích chính xác, thời gian chạy của nó không phải là O (n) vì nó phụ thuộc nhiều hơn số lượng phần tử đầu vào. Nói chung, rất khó để đưa ra loại dự đoán mạnh mẽ mà sắp xếp cơ số cần phải hiệu quả về đầu vào.
Konrad Rudolph

Đó O (n), trong đó n là tổng kích thước đầu vào, nghĩa là bao gồm kích thước của các phần tử. Đúng là bạn có thể thực hiện nó để bạn phải đệm nhiều số không, nhưng thật vô nghĩa khi sử dụng một triển khai kém để so sánh. (Điều đó nói rằng, việc thực hiện có thể khó khăn, ymmv.)
Anders Eurenius

Lưu ý rằng nếu bạn đang sử dụng GNU libc, qsortlà một loại hợp nhất.
Jason Orendorff

Er, chính xác hơn, đó là một loại hợp nhất trừ khi bộ nhớ tạm thời cần thiết không thể được phân bổ. cvs.savannah.gnu.org/viewvc/libc/stdlib/ory
Jason Orendorff

1

Bổ sung nhỏ để sắp xếp nhanh chóng và hợp nhất các loại.

Ngoài ra nó có thể phụ thuộc vào loại vật phẩm sắp xếp. Nếu truy cập vào các mục, trao đổi và so sánh không phải là các thao tác đơn giản, như so sánh các số nguyên trong bộ nhớ phẳng, thì sắp xếp hợp nhất có thể là thuật toán thích hợp hơn.

Ví dụ: chúng tôi sắp xếp các mục bằng giao thức mạng trên máy chủ từ xa.

Ngoài ra, trong các thùng chứa tùy chỉnh như "danh sách được liên kết", không có lợi ích của việc sắp xếp nhanh.
1. Hợp nhất sắp xếp trên danh sách được liên kết, không cần thêm bộ nhớ. 2. Truy cập vào các phần tử trong sắp xếp nhanh không phải là tuần tự (trong bộ nhớ)


0

Sắp xếp nhanh là một thuật toán sắp xếp tại chỗ, vì vậy nó phù hợp hơn cho các mảng. Mặt khác, sắp xếp hợp nhất yêu cầu lưu trữ thêm O (N) và phù hợp hơn cho các danh sách được liên kết.

Không giống như mảng, trong danh sách thích, chúng ta có thể chèn các mục ở giữa với không gian O (1) và thời gian O (1), do đó, hoạt động hợp nhất trong sắp xếp hợp nhất có thể được thực hiện mà không cần thêm không gian. Tuy nhiên, việc phân bổ và phân bổ lại không gian bổ sung cho các mảng có ảnh hưởng xấu đến thời gian chạy sắp xếp hợp nhất. Hợp nhất sắp xếp cũng ưu tiên danh sách được liên kết vì dữ liệu được truy cập tuần tự, không có nhiều truy cập bộ nhớ ngẫu nhiên.

Mặt khác, sắp xếp nhanh đòi hỏi nhiều quyền truy cập bộ nhớ ngẫu nhiên và với một mảng, chúng ta có thể truy cập trực tiếp vào bộ nhớ mà không có bất kỳ chuyển động nào theo yêu cầu của danh sách được liên kết. Ngoài ra sắp xếp nhanh khi được sử dụng cho các mảng có một địa phương tham chiếu tốt vì các mảng được lưu trữ liên tục trong bộ nhớ.

Mặc dù cả hai thuật toán sắp xếp độ phức tạp trung bình là O (NlogN), thông thường mọi người cho các tác vụ thông thường sử dụng một mảng để lưu trữ và vì lý do đó, sắp xếp nhanh chóng nên là thuật toán được lựa chọn.

EDIT: Tôi vừa phát hiện ra rằng hợp nhất trường hợp tệ nhất / tốt nhất / avg luôn luôn là nlogn, nhưng sắp xếp nhanh có thể thay đổi từ n2 (trường hợp xấu nhất khi các phần tử đã được sắp xếp) thành nlogn (trường hợp avg / tốt nhất khi trục luôn chia mảng thành hai một nửa).


0

Xem xét thời gian và không gian phức tạp cả. Đối với sắp xếp Hợp nhất: Độ phức tạp thời gian: O (nlogn), Độ phức tạp không gian: O (nlogn)

Để sắp xếp nhanh: Độ phức tạp thời gian: O (n ^ 2), Độ phức tạp không gian: O (n)

Bây giờ, cả hai đều giành chiến thắng trong một scenerio mỗi. Nhưng, bằng cách sử dụng một trục ngẫu nhiên, bạn hầu như luôn có thể giảm độ phức tạp Thời gian của Sắp xếp nhanh thành O (nlogn).

Do đó, sắp xếp nhanh được ưa thích trong nhiều ứng dụng thay vì sắp xếp Hợp nhất.


-1

Trong vùng đất c / c ++, khi không sử dụng container stl, tôi có xu hướng sử dụng quicksort, bởi vì nó được tích hợp vào thời gian chạy, trong khi sáp nhập thì không.

Vì vậy, tôi tin rằng trong nhiều trường hợp, nó chỉ đơn giản là con đường ít kháng cự nhất.

Ngoài ra, hiệu suất có thể cao hơn nhiều với sắp xếp nhanh, trong trường hợp toàn bộ tập dữ liệu không phù hợp với tập làm việc.


3
Trên thực tế, nếu đó là hàm thư viện qsort () mà bạn đang nói đến, thì nó có thể hoặc không được triển khai dưới dạng quicksort.
Thomas Padron-McCarthy

3
Konrad, xin lỗi để có một chút hậu môn về điều này, nhưng bạn tìm thấy sự đảm bảo đó ở đâu? Tôi không thể tìm thấy nó trong tiêu chuẩn ISO C hoặc trong tiêu chuẩn C ++.
Thomas Padron-McCarthy

2
GNU libc qsortlà một loại hợp nhất trừ khi số lượng phần tử thực sự khổng lồ hoặc bộ nhớ tạm thời không thể được phân bổ. cvs.savannah.gnu.org/viewvc/libc/stdlib/ triệt
Jason Orendorff

-3

Một trong những lý do là triết học hơn. Quicksort là triết lý hàng đầu. Với n phần tử để sắp xếp, có n! khả năng. Với 2 phân vùng m & nm loại trừ lẫn nhau, số lượng khả năng sẽ giảm theo nhiều bậc. m! * (nm)! nhỏ hơn bởi một số đơn đặt hàng so với n! một mình. tưởng tượng 5! vs 3! * 2!. 5! có khả năng gấp 10 lần so với 2 phân vùng 2 & 3 mỗi phân vùng. và ngoại suy thành 1 triệu giai thừa so với 900K! * 100K! Vì vậy, thay vì lo lắng về việc thiết lập bất kỳ trật tự nào trong một phạm vi hoặc phân vùng, chỉ cần thiết lập trật tự ở cấp độ rộng hơn trong các phân vùng và giảm các khả năng trong một phân vùng. Bất kỳ thứ tự nào được thiết lập trước đó trong một phạm vi sẽ bị xáo trộn sau nếu các phân vùng không loại trừ lẫn nhau.

Bất kỳ cách tiếp cận từ dưới lên nào như sắp xếp hợp nhất hoặc sắp xếp đống đều giống như cách tiếp cận của công nhân hoặc nhân viên nơi người ta bắt đầu so sánh ở cấp độ hiển vi sớm. Nhưng thứ tự này chắc chắn sẽ bị mất ngay sau khi một yếu tố ở giữa chúng được tìm thấy sau này. Những cách tiếp cận này rất ổn định và cực kỳ dễ đoán nhưng thực hiện một số lượng công việc phụ nhất định.

Sắp xếp nhanh giống như phương pháp quản lý trong đó ban đầu người ta không quan tâm đến bất kỳ đơn hàng nào, chỉ về việc đáp ứng một tiêu chí rộng rãi mà không liên quan đến đơn hàng. Sau đó, các phân vùng được thu hẹp cho đến khi bạn có được một bộ sắp xếp. Thách thức thực sự trong Quicksort là tìm ra một phân vùng hoặc tiêu chí trong bóng tối khi bạn không biết gì về các yếu tố cần sắp xếp. Đó là lý do tại sao chúng ta cần phải bỏ ra một số nỗ lực để tìm giá trị trung bình hoặc chọn 1 một cách ngẫu nhiên hoặc một cách tiếp cận "Quản lý" tùy ý. Để tìm một trung vị hoàn hảo có thể mất một lượng nỗ lực đáng kể và dẫn đến một cách tiếp cận từ dưới lên ngu ngốc một lần nữa. Vì vậy, Quicksort nói chỉ cần chọn một trục ngẫu nhiên và hy vọng rằng nó sẽ ở đâu đó ở giữa hoặc thực hiện một số công việc để tìm trung vị 3, 5 hoặc một cái gì đó nữa để tìm trung bình tốt hơn nhưng không có kế hoạch hoàn hảo & đừng ' T lãng phí bất cứ lúc nào trong đơn đặt hàng ban đầu. Điều đó dường như làm tốt nếu bạn may mắn hoặc đôi khi xuống cấp đến n ^ 2 khi bạn không nhận được trung vị nhưng chỉ cần có cơ hội. Bất kỳ cách dữ liệu là ngẫu nhiên. đúng. Vì vậy, tôi đồng ý nhiều hơn với cách tiếp cận logic từ trên xuống> nhanh chóng và hóa ra cơ hội cần có về lựa chọn trục và so sánh mà nó tiết kiệm trước đó dường như hoạt động tốt hơn nhiều lần so với bất kỳ cách tiếp cận ổn định tỉ mỉ và kỹ lưỡng nào -> hợp nhất sắp xếp. Nhưng so sánh rằng nó tiết kiệm sớm hơn dường như hoạt động tốt hơn nhiều lần so với bất kỳ đáy ổn định tỉ mỉ và kỹ lưỡng nào -> cách tiếp cận như sắp xếp hợp nhất. Nhưng so sánh rằng nó tiết kiệm sớm hơn dường như hoạt động tốt hơn nhiều lần so với bất kỳ đáy ổn định tỉ mỉ và kỹ lưỡng nào -> cách tiếp cận như sắp xếp hợp nhất. Nhưng


quicksort lợi ích từ sự ngẫu nhiên của lựa chọn trục. Trục ngẫu nhiên sẽ tự nhiên có xu hướng về phân vùng 50:50 và không có khả năng nhất quán đối với một trong những thái cực. Hệ số không đổi của nlogn khá thấp cho đến khi phân vùng trung bình là 60-40 hoặc thậm chí đến 70-30.
Dưa đông

Điều này là hoàn toàn vô nghĩa. quicksort được sử dụng vì hiệu suất của nó, chứ không phải "triết lý" ... và các tuyên bố về "trật tự bị ràng buộc bị mất" chỉ đơn giản là sai.
Jim Balter
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.