it-swarm-vi.tech

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

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?

344

Quicksort có O (viết sai rồi2) thời gian chạy trường hợp xấu nhất và O (viết sai rồiđăng nhậpviết sai rồi) thời gian chạy trung bình. Tuy nhiên, nó vượt trội hơn khi hợp nhất sắp xếp theo nhiều kịch bản vì nhiều yếu tố ảnh hưởng đến thời gian chạy 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 của tài liệu tham khảo (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, nó rất dễ dàng để tránh thời gian chạy nhanh nhất trong trường hợp xấu nhất của O (viết sai rồi2) 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).

Trong thực tế, nhiều triển khai hiện đại của quicksort (cụ thể là libstdc ++ xông s std::sort) thực sự là introsort , trong đó trường hợp xấu nhất về mặt lý thuyết là O (viết sai rồiđăng nhậpviết sai rồi), giống như sắp xếp hợp nhất. 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á logviết sai rồi.

258
Konrad Rudolph

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ị 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 có danh sách phụ quicksort, 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 các 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 kiếm 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ì hàng ngày hoặc hàng tuần.

275
user11318

Trên thực tế, QuickSort là O (n2). Nó trường hợp trung bình thời gian chạy là O (nlog (n)), nhưng trường hợp xấu nhất là O (n2), xảy ra khi bạn chạy nó trong danh sách 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.
88
Dark Shikari

"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 có vẻ mạnh hơn nhiều so với sắp xếp "Hợp nhất".

29
Ash

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 kết thúc là một lựa chọn đầu tiên tuyệt vời.

15
Javier

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.

8
Antti Rasinen

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
gnobal

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.

Sáp nhập đá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à không gian thêm có sẵn . 1

Bạn nói rằng họ "Cả hai đều là O(nlogn) []]. 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

7
Roman Glass

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 thử nghiệm trong thế giới thực cho thấy heapsort chậm hơn đáng kể so với quicksort trung bình.

6
Niyaz

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 .

Quicksort

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.

5
Mat Mannion

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 thi trên một tập dữ liệu có cùng giá trị trong O(n) time. 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ựa chọn trục không lý tưởng ảnh hưởng đến thời gian thực hiện bao nhiêu?

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

Điều này nhanh đến mức nào?

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 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ơ hội là 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 sử dụng trung bình gồm 3 chỉ mục đượ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 luôn chính xác ở mức 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ỳ đ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ó yêu cầu 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 gấp 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 câu hỏi đó và câu trả lời là khoảng 10 ^ -18. (n là 88, k là 21, p là 0,6875) Người dùng của bạn có khả năng bị sét đánh cao gấp hàng nghìn lần trong 1 giây khi nhấp vào [SORT] so với khi họ thấy 5.000 lượt chạy vật phẩm bất kỳ trường hợp lý tưởng nào tệ hơn hơn 10 *. 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 trục xoay 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 hiện hoàn thành trong thời gian O (n * logn).

4
Lance Wisely

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.

4
xpda

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 có luôn tốt hơn Mergesort không?

Không thực sự.

  • 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

4
Sanjeev Kumar Dangi

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 thời gian trường hợp xấu nhất là 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.

3
Shantam Mittal

Câu trả lời sẽ hơi nghiêng về quicksort w.r.t đối với các 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 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ự bắt chước của 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
appbootup

Trong hợp nhất sắp xếp, 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 cấp độ, 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 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.

3
RvPr

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
Simon Johnson

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ỉ là O (n log n) trên trung bình . 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 có hiệu suất hoặc khả năng đáp ứng trong 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.

2
DJ Capelis

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 so với 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).

2
Wade Anderson

Đâ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 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 theo các so sánh (tuy nhiên nó về mặt hoán đổi và có thể 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

2
virco

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.

2
Aldian Fazrihady

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 sắp xếp các mặt hàng. 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ớ mặt 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ớ)

1
minorlogic

Đ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ó bằng một khi n là vô hạn. Có vẻ 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. 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 cấp 1000000 chữ số trong Java theo lớp Ngẫu nhiên và phải mất 2610ms bằng cách sáp nhập, 1370ms bằng quicksort.

1
Peter

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ư mergesort là lựa chọn phổ biến cho các danh sách.

Điều tôi thắc mắc là tại sao rất hiếm khi thấy radix hoặc sắp xếp xô. 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 như số nguyên: 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
Anders Eurenius

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.

0
pankaj

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 O(1) dấu cách và O(1) thời gian, 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 bất kỳ không gian thêm. Tuy nhiên, 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
Saad