Tại sao Collections.sort sử dụng Mergesort nhưng Arrays.sort thì không?


96

Tôi đang sử dụng JDK-8 (x64). Đối với Arrays.sort(nguyên thủy), tôi đã tìm thấy thông tin sau trong tài liệu Java:

Thuật toán sắp xếp là Kép xoay vòng Quicksort của Vladimir Yaroslavskiy, Jon Bentley và Joshua Bloch. '

Đối với Collections.sort(các đối tượng), tôi tìm thấy "Sắp xếp thời gian" này:

Thực hiện Đây là một ổn định, thích nghi, lặp đi lặp lại mergesort ... thi này bãi danh sách cụ thể vào một mảng, sắp xếp mảng , và lặp trên danh sách đặt lại mỗi phần tử từ vị trí tương ứng trong mảng.

Nếu Collections.sortsử dụng một mảng, tại sao nó không chỉ gọi Arrays.sorthoặc sử dụng QuickSort trục kép ? Tại sao sử dụng Mergesort ?


8
Đó là javadoc dành cho các mảng nguyên thủy - các mảng Đối tượng được sắp xếp bằng cách sử dụng justgsort.
assylias

2
mergesort cho u nlogn luôn trong khi quicksort có thể thỉnh thoảng Hãy cho nlogn2 geneally mảng kích thước không phải là lớn nhưng bộ sưu tập dễ dàng đi tối đa hàng triệu mục để tham gia một nguy cơ nlogn2 không đáng PS nlogn2 i nghĩa sqaure của n
Kumar Saurabh

O (n ^ 2) cho quicksort là trường hợp xấu nhất. Trong thực tế, nó nhanh hơn
James Wierzba

nhưng u cant bỏ qua những caese trong khi thực hiện một api
Kumar Saurabh

2
Liên kết này rất liên quan.
qartal

Câu trả lời:


99

API đảm bảo sắp xếp ổn địnhQuicksort không cung cấp. Tuy nhiên, khi sắp xếp các giá trị nguyên thủy theo thứ tự tự nhiên của chúng, bạn sẽ không nhận thấy sự khác biệt vì các giá trị nguyên thủy không có danh tính. Do đó, Quicksort có thể được sử dụng cho các mảng nguyên thủy và sẽ được sử dụng khi nó được coi là hiệu quả hơn¹.

Đối với các đối tượng, bạn có thể nhận thấy, khi các đối tượng có danh tính khác nhau được coi là bình đẳng theo cách equalstriển khai của chúng hoặc được cung cấp Comparatorthay đổi thứ tự của chúng. Do đó, Quicksort không phải là một lựa chọn. Vì vậy, một biến thể của MergeSort được sử dụng, các phiên bản Java hiện tại sử dụng TimSort . Điều này áp dụng cho cả hai Arrays.sortCollections.sort, mặc dù với Java 8, Listbản thân nó có thể ghi đè các thuật toán sắp xếp.


¹ Lợi thế về hiệu quả của Quicksort là cần ít bộ nhớ hơn khi thực hiện tại chỗ. Nhưng nó có hiệu suất trong trường hợp xấu nhất đáng kể và không thể khai thác các lần chạy dữ liệu được sắp xếp trước trong một mảng, điều mà TimSort thực hiện.

Do đó, các thuật toán sắp xếp đã được làm lại từ phiên bản này sang phiên bản khác, trong khi vẫn ở trong lớp được đặt tên nhầm DualPivotQuicksort. Ngoài ra, tài liệu không bắt kịp, điều này cho thấy rằng nói chung là một ý tưởng tồi khi đặt tên một thuật toán được sử dụng nội bộ trong một đặc tả, khi không cần thiết.

Tình hình hiện tại (bao gồm cả Java 8 đến Java 11) như sau:

  • Nói chung, các phương pháp sắp xếp cho các mảng nguyên thủy sẽ chỉ sử dụng Quicksort trong một số trường hợp nhất định. Đối với các mảng lớn hơn, họ sẽ cố gắng xác định các lần chạy dữ liệu được sắp xếp trước, giống như TimSort , và sẽ hợp nhất chúng khi số lần chạy không vượt quá một ngưỡng nhất định. Nếu không, chúng sẽ quay trở lại Quicksort , nhưng với một triển khai sẽ trở lại Sắp xếp chèn cho các phạm vi nhỏ, điều này không chỉ ảnh hưởng đến các mảng nhỏ mà còn cả đệ quy của sắp xếp nhanh.
  • sort(char[],…)sort(short[],…)thêm một trường hợp đặc biệt khác, để sử dụng Sắp xếp đếm cho các mảng có độ dài vượt quá ngưỡng nhất định
  • Tương tự như vậy, sort(byte[],…)sẽ sử dụng Sắp xếp đếm , nhưng với ngưỡng nhỏ hơn nhiều, tạo ra sự tương phản lớn nhất với tài liệu, vì sort(byte[],…)không bao giờ sử dụng Quicksort. Nó chỉ sử dụng sắp xếp Chèn cho các mảng nhỏ và sắp xếp đếm ngược lại.

1
Rất tiếc, điều thú vị là Collections.sort Javadoc tuyên bố: "Sắp xếp này được đảm bảo là ổn định", nhưng vì nó ủy quyền cho List.sort, có thể bị ghi đè bởi các triển khai danh sách, sắp xếp ổn định thực sự không thể được kiểm định bởi Collections.sort cho tất cả danh sách triển khai. Hay tôi bỏ lỡ điều gì đó? Và List.sort không yêu cầu thuật toán sắp xếp phải ổn định.
Puce

11
@Puce: điều đó đơn giản có nghĩa là trách nhiệm đối với bảo lãnh đó hiện nằm trong tay những người thực hiện List.sortphương pháp ghi đè . Collections.sortkhông bao giờ có thể đảm bảo hoạt động chính xác cho mọi Listtriển khai vì nó không thể đảm bảo, ví dụ: Listkhông thay đổi nội dung của nó một cách nhanh chóng. Tất cả tóm lại là sự đảm bảo Collections.sortchỉ áp dụng cho các Listtriển khai đúng (và đúng Comparatorhoặc equalstriển khai).
Holger

1
@Puce: Nhưng bạn nói đúng, Javadoc không rõ ràng như nhau về ràng buộc này trong cả hai phương pháp Nhưng ít nhất tài liệu mới nhất nói rằng Collections.sortsẽ ủy quyền cho List.sort.
Holger

@Puce: có rất nhiều ví dụ về điều này, trong đó các thuộc tính quan trọng không phải là một phần của kiểu mà chỉ được đề cập trong tài liệu (và do đó không được trình biên dịch kiểm tra). Hệ thống kiểu của Java đơn giản là quá yếu để thể hiện bất kỳ thuộc tính thú vị nào. (Nó không khác nhiều so với ngôn ngữ được nhập động về mặt này, ở đó, các thuộc tính cũng được xác định trong tài liệu và tùy thuộc vào lập trình viên để đảm bảo chúng không bị vi phạm.) Thực ra nó còn đi xa hơn: bạn có để ý mà Collections.sortthậm chí không đề cập trong chữ ký kiểu của nó rằng đầu ra được sắp xếp?
Jörg W Mittag,

1
Trong một ngôn ngữ có hệ thống kiểu biểu cảm hơn, kiểu trả về Collections.sortsẽ giống như "tập hợp có cùng kiểu và độ dài như đầu vào với các thuộc tính 1) mọi phần tử có trong đầu vào cũng có trong đầu ra, 2 ) đối với mọi cặp phần tử từ đầu ra, chỉ số bên trái không lớn hơn phần tử bên phải, 3) đối với mọi cặp phần tử bằng nhau từ đầu ra, chỉ số của bên trái trong đầu vào nhỏ hơn chỉ số của bên phải "hoặc đại loại là cái đó.
Jörg W Mittag,

20

Tôi không biết về tài liệu, nhưng việc triển khai java.util.Collections#sorttrong Java 8 (HotSpot) diễn ra như thế này:

@SuppressWarnings({"unchecked", "rawtypes"})
public static <T> void sort(List<T> list, Comparator<? super T> c) {
    list.sort(c);
}

List#sortcó triển khai này:

@SuppressWarnings({"unchecked", "rawtypes"})
default void sort(Comparator<? super E> c) {
    Object[] a = this.toArray();
    Arrays.sort(a, (Comparator) c);
    ListIterator<E> i = this.listIterator();
    for (Object e : a) {
        i.next();
        i.set((E) e);
    }
}

Vì vậy, cuối cùng, Collections#sortsử dụng Arrays#sort(của các yếu tố đối tượng) đằng sau hậu trường. Việc triển khai này sử dụng sắp xếp hợp nhất hoặc sắp xếp thời gian.


16

Theo Javadoc, chỉ các mảng nguyên thủy mới được sắp xếp bằng Quicksort. Các mảng đối tượng cũng được sắp xếp bằng Mergesort.

Vì vậy, Collections.sort dường như sử dụng cùng một thuật toán sắp xếp như Arrays.sort cho Đối tượng.

Một câu hỏi khác sẽ là tại sao một thuật toán sắp xếp khác được sử dụng cho các mảng nguyên thủy hơn là cho các mảng Đối tượng?


2

Như đã nêu trên nhiều câu trả lời.

Quicksort được Arrays.sort sử dụng để sắp xếp các tập hợp nguyên thủy vì không cần tính ổn định (bạn sẽ không biết hoặc không quan tâm nếu hai int giống nhau được hoán đổi trong loại)

MergeSort hay cụ thể hơn là Timsort được Arrays.sort sử dụng để sắp xếp các tập hợp các đối tượng. Cần có sự ổn định. Quicksort không cung cấp sự ổn định, Timsort thì có.

Collections.sort ủy quyền cho Arrays.sort, đó là lý do tại sao bạn thấy javadoc tham chiếu đến MergeSort.


1

Sắp xếp nhanh có hai nhược điểm lớn khi nói đến sắp xếp hợp nhất:

  • Nó không ổn định trong khi nó không phải là nguyên thủy.
  • Nó không đảm bảo hiệu suất n log n.

Tính ổn định không phải là vấn đề đối với các kiểu nguyên thủy, vì không có khái niệm đồng nhất khác biệt với bình đẳng (giá trị).

Tính ổn định là một vấn đề lớn khi phân loại các đối tượng tùy ý. Đó là một lợi ích phụ tuyệt vời mà Merge Sort đảm bảo hiệu suất n log n (thời gian) bất kể đầu vào là gì. Đó là lý do tại sao sắp xếp hợp nhất được chọn để cung cấp một sắp xếp ổn định (Merge Sort) để sắp xếp các tham chiếu đối tượng.


1
Ý bạn là "Không ổn định" là gì?
Arun Gowda
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.