Tại sao không có SortedList trong Java?


503

Trong Java có SortedSetSortedMap giao diện . Cả hai đều thuộc khung công tác Bộ sưu tập Java và cung cấp một cách được sắp xếp để truy cập các phần tử.

Tuy nhiên, theo cách hiểu của tôi thì không có SortedListtrong Java. Bạn có thể dùngjava.util.Collections.sort() để sắp xếp một danh sách.

Bất cứ ý tưởng tại sao nó được thiết kế như vậy?



6
Vậy kết quả mong đợi của bạn là gì khi chèn một phần tử vào giữa danh sách?
bestsss

4
@bestsss Hoàn toàn có thể có một lớp SortedList không thực hiện giao diện java.util.List. Đọc câu hỏi như một cuộc điều tra tại sao không có cấu trúc dữ liệu hỗ trợ chức năng được yêu cầu. Đừng để bị phân tâm bởi các chi tiết không đáng kể như đặt tên.
Alderath

@Alderath, cấu trúc thông thường cho cái này là một cây (đỏ / đen, avl hoặc btree) w / thêm các liên kết / tiếp theo để hỗ trợ đặt hàng. Tôi sử dụng cấu trúc tương tự đỏ / đen w / trước / liên kết tiếp theo. Đó là một cách sử dụng khá thích hợp, mặc dù. Cây có thể được duyệt qua cả thứ tự và thứ tự chèn, có O (logn) find / chứa nhưng get (int) là O (n). Với thực tế khả năng ứng dụng thích hợp của nó, tôi cho rằng nó được để lại cho các nhà phát triển để thực hiện nếu họ cần.
bestsss

3
Không trả lời câu hỏi "tại sao", nhưng cách giải quyết đơn giản nhất cho Treeset là sử dụng Bộ so sánh không bao giờ trả về 0, ví dụ int diff = this.score - that.score; return (diff == 0) ? 1 : diff; như đó là một vụ hack có mùi, tôi sẽ cung cấp điều này như một đối số của hàm tạo ẩn danh thay vì có bất kỳ so sánh nào.
Earcam

Câu trả lời:


682

Trình lặp danh sách đảm bảo đầu tiên và trước hết là bạn có được các phần tử của danh sách theo thứ tự nội bộ của danh sách (hay còn gọi là thứ tự chèn ). Cụ thể hơn, đó là theo thứ tự bạn đã chèn các yếu tố hoặc về cách bạn thao tác danh sách. Sắp xếp có thể được xem như là một thao tác của cấu trúc dữ liệu và có một số cách để sắp xếp danh sách.

Tôi sẽ sắp xếp các cách theo thứ tự hữu ích như cá nhân tôi thấy:

1. Xem xét sử dụng Sethoặc Bagbộ sưu tập thay thế

LƯU Ý: Tôi đặt tùy chọn này lên hàng đầu vì đây là điều bạn thường muốn làm.

Một tập hợp được sắp xếp tự động sắp xếp bộ sưu tập khi chèn , nghĩa là nó sắp xếp trong khi bạn thêm các phần tử vào bộ sưu tập. Điều đó cũng có nghĩa là bạn không cần phải tự sắp xếp nó.

Hơn nữa, nếu bạn chắc chắn rằng bạn không cần phải lo lắng về (hoặc có) các yếu tố trùng lặp thì bạn có thể sử dụng TreeSet<T>thay thế. Nó thực hiện SortedSetNavigableSetgiao diện và hoạt động như bạn có thể mong đợi từ một danh sách:

TreeSet<String> set = new TreeSet<String>();
set.add("lol");
set.add("cat");
// automatically sorts natural order when adding

for (String s : set) {
    System.out.println(s);
}
// Prints out "cat" and "lol"

Nếu bạn không muốn thứ tự tự nhiên, bạn có thể sử dụng tham số hàm tạo có một Comparator<T>.

Ngoài ra, bạn có thể sử dụng Multisets (còn được gọi là Túi ) , nghĩa là Setcho phép các yếu tố trùng lặp, thay vào đó và có các triển khai của bên thứ ba. Đáng chú ý nhất là từ các thư viện Guava có một TreeMultiset, hoạt động rất giống như TreeSet.

2. Sắp xếp danh sách của bạn với Collections.sort()

Như đã đề cập ở trên, sắp xếp Lists là một thao tác của cấu trúc dữ liệu. Vì vậy, đối với các tình huống mà bạn cần "một nguồn sự thật" sẽ được sắp xếp theo nhiều cách khác nhau thì sắp xếp nó theo cách thủ công là cách tốt nhất.

Bạn có thể sắp xếp danh sách của bạn với java.util.Collections.sort()phương pháp. Đây là một mẫu mã về cách:

List<String> strings = new ArrayList<String>()
strings.add("lol");
strings.add("cat");

Collections.sort(strings);
for (String s : strings) {
    System.out.println(s);
}
// Prints out "cat" and "lol"

Sử dụng bộ so sánh

Một lợi ích rõ ràng là bạn có thể sử dụng Comparatortrong sortphương pháp. Java cũng cung cấp một số triển khai cho Comparatorví dụ như Collatorhữu ích cho các chuỗi sắp xếp nhạy cảm cục bộ . Đây là một ví dụ:

Collator usCollator = Collator.getInstance(Locale.US);
usCollator.setStrength(Collator.PRIMARY); // ignores casing

Collections.sort(strings, usCollator);

Sắp xếp trong môi trường đồng thời

Tuy nhiên, xin lưu ý rằng việc sử dụng sortphương thức này không thân thiện trong các môi trường đồng thời, vì thể hiện của bộ sưu tập sẽ bị thao túng và thay vào đó bạn nên xem xét sử dụng các bộ sưu tập bất biến. Đây là thứ mà Guava cung cấp trong Orderinglớp và là một lớp lót đơn giản:

List<string> sorted = Ordering.natural().sortedCopy(strings);

3. Gói danh sách của bạn với java.util.PriorityQueue

Mặc dù không có danh sách được sắp xếp trong Java, tuy nhiên có một hàng đợi được sắp xếp có thể sẽ hoạt động tốt cho bạn. Đó là java.util.PriorityQueuelớp học.

Nico Haase liên kết trong các ý kiến ​​với một câu hỏi liên quan cũng trả lời điều này.

Trong bộ sưu tập được sắp xếp, rất có thể bạn không muốn thao túng cấu trúc dữ liệu nội bộ, đó là lý do PriorityQueue không triển khai giao diện Danh sách (vì điều đó sẽ cho phép bạn truy cập trực tiếp vào các phần tử của nó).

Hãy cẩn thận trên PriorityQueueiterator

Các PriorityQueuedụng cụ lớp Iterable<E>Collection<E>giao diện để nó có thể được lặp như bình thường. Tuy nhiên, iterator không được đảm bảo trả về các phần tử theo thứ tự được sắp xếp. Thay vào đó (như Alderath chỉ ra trong các bình luận) bạn cần poll()xếp hàng cho đến khi trống.

Lưu ý rằng bạn có thể chuyển đổi danh sách thành hàng đợi ưu tiên thông qua hàm tạo có bất kỳ bộ sưu tập nào :

List<String> strings = new ArrayList<String>()
strings.add("lol");
strings.add("cat");

PriorityQueue<String> sortedStrings = new PriorityQueue(strings);
while(!sortedStrings.isEmpty()) {
    System.out.println(sortedStrings.poll());
}
// Prints out "cat" and "lol"

4. Viết SortedListlớp của riêng bạn

LƯU Ý: Bạn không cần phải làm điều này.

Bạn có thể viết lớp Danh sách của riêng bạn sắp xếp mỗi khi bạn thêm một phần tử mới. Điều này có thể trở nên khá nặng nề tính toán tùy thuộc vào việc thực hiện của bạn và là vô nghĩa , trừ khi bạn muốn thực hiện nó như một bài tập, vì hai lý do chính:

  1. Nó phá vỡ hợp đồng mà List<E>giao diện có vì các addphương thức sẽ đảm bảo rằng phần tử sẽ nằm trong chỉ mục mà người dùng chỉ định.
  2. Tại sao phải phát minh lại bánh xe? Thay vào đó, bạn nên sử dụng TreeSet hoặc Multisets như được chỉ ra ở điểm đầu tiên ở trên.

Tuy nhiên, nếu bạn muốn thực hiện nó như một bài tập ở đây là một mẫu mã để bạn bắt đầu, nó sử dụng AbstractListlớp trừu tượng:

public class SortedList<E> extends AbstractList<E> {

    private ArrayList<E> internalList = new ArrayList<E>();

    // Note that add(E e) in AbstractList is calling this one
    @Override 
    public void add(int position, E e) {
        internalList.add(e);
        Collections.sort(internalList, null);
    }

    @Override
    public E get(int i) {
        return internalList.get(i);
    }

    @Override
    public int size() {
        return internalList.size();
    }

}

Lưu ý rằng nếu bạn chưa ghi đè các phương thức bạn cần, thì các cài đặt mặc định từ đó AbstractListsẽ ném UnsupportedOperationExceptions.


12
+1. Imo, điều này mang tính xây dựng hơn câu trả lời được bình chọn hàng đầu. Nó đi kèm với hai nhược điểm nhỏ mặc dù. PriorityQueue không hỗ trợ truy cập ngẫu nhiên. Bạn không thể nhìn trộm (Element Index). Vì vậy, bạn không thể làm ví dụ Integer maxVal = prioQueue.peek(prioQueue.size() - 1);. Thứ hai, nếu bạn có ý định sử dụng PriorityQueue đơn giản như một danh sách được sắp xếp, nó sẽ nghe có vẻ ít trực quan hơn PriorityQueuetrong mã so với trước đây SortedList, nếu cấu trúc dữ liệu như vậy tồn tại.
Alderath

12
Và, sau khi xem xét câu hỏi khác mà ai đó liên kết trong các bình luận, một nhược điểm lớn khác là iterator của PriorityQueue không được đảm bảo trả về các phần tử theo bất kỳ thứ tự cụ thể nào. Vì vậy, trừ khi tôi đang xem xét một cái gì đó, cách duy nhất để in tất cả các đối tượng trong PriorityQueue theo thứ tự là liên tục thăm dò () hàng đợi cho đến khi nó trống. Đối với tôi, điều này cảm thấy đường biên giới trở lại. Để in các đối tượng trong PriorityQueue hai lần, trước tiên bạn phải sao chép PriorityQueue và sau đó thăm dò () tất cả các đối tượng từ PriorityQueue ban đầu và sau đó pol () l tất cả các đối tượng từ bản sao.
Alderath

1
Hmm ... có vẻ như bạn đúng Alderath. Bạn không thể sử dụng trình lặp của PriorityQueue để lấy các phần tử theo thứ tự mong đợi. Hình như tôi phải chỉnh sửa câu trả lời của mình.
Spoike

7
Hàng đợi ưu tiên chỉ là một đống, bạn chỉ có thể truy cập vào đầu, imo nó không thuộc về câu trả lời của câu hỏi.
bestsss

1
Cũng đáng chú ý là Collections.sort()thậm chí cho phép bạn xác định chức năng so sánh được sử dụng để sắp xếp, bằng cách sử dụng một Comparatorđối tượng.
Mike 'Pomax' Kamermans

71

Bởi vì khái niệm Danh sách không tương thích với khái niệm bộ sưu tập được sắp xếp tự động. Điểm của Danh sách là sau khi gọi list.add(7, elem), một cuộc gọi list.get(7)sẽ trở lại elem. Với danh sách được sắp xếp tự động, phần tử có thể kết thúc ở vị trí tùy ý.


26

Vì tất cả các danh sách đã được "sắp xếp" theo thứ tự các mục đã được thêm vào (thứ tự FIFO), bạn có thể "sử dụng" chúng với một thứ tự khác, bao gồm cả thứ tự tự nhiên của các yếu tố, sử dụng java.util.Collections.sort().

BIÊN TẬP:

Liệt kê các cấu trúc dữ liệu dựa trên điều thú vị là thứ tự trong đó các mục được chèn vào.

Bộ không có thông tin đó.

Nếu bạn muốn đặt hàng bằng cách thêm thời gian, sử dụng List. Nếu bạn muốn đặt hàng theo tiêu chí khác, sử dụng SortedSet.


22
Đặt không cho phép trùng lặp
bestsss

10
Tôi không nghĩ rằng đây là một câu trả lời rất tốt. Chắc chắn, các danh sách trong API java có một thứ tự cụ thể được xác định bởi thời điểm / cách các mục được chèn. Tuy nhiên, sự tồn tại của các danh sách được sắp xếp theo cách phụ thuộc vào phương pháp / thời gian chèn không ngăn cản việc có các cấu trúc dữ liệu khác theo thứ tự được xác định theo cách khác (ví dụ: Bộ so sánh). Về cơ bản, OP đang hỏi tại sao không có cấu trúc dữ liệu tương đương với Sắp xếp với ngoại lệ rằng cấu trúc dữ liệu sẽ cho phép nhiều lần xuất hiện của các phần tử bằng nhau.
Alderath

5
Vì vậy, câu hỏi tiếp theo của tôi sẽ là: " Tại sao không có cấu trúc dữ liệu hoạt động như Sắp xếp, nhưng có thể chứa nhiều phần tử bằng nhau? " (Và vui lòng không trả lời " vì Bộ chỉ có thể chứa một phần tử ")
Alderath

@Alderath: Xem stackoverflow.com/questions/416266/sort-collection-in-java . Tóm lại: Sử dụng GuM's TreeMultiset
Janus Troelsen

4
Danh sách KHÔNG được "sắp xếp", ngay cả khi bạn đặt "sắp xếp" trong dấu ngoặc kép. Họ được ra lệnh.
Koray Tugay

21

Đặt và Bản đồ là cấu trúc dữ liệu phi tuyến tính. Danh sách là cấu trúc dữ liệu tuyến tính.

nhập mô tả hình ảnh ở đây


Cấu trúc SortedSetSortedMapgiao diện dữ liệu cây thực hiện TreeSetTreeMaptương ứng sử dụng thuật toán triển khai cây Đỏ-Đen đã sử dụng . Vì vậy, nó đảm bảo rằng không có mục trùng lặp (hoặc khóa trong trường hợp Map).

  • List đã duy trì một bộ sưu tập theo thứ tự và cấu trúc dữ liệu dựa trên chỉ mục, cây không có cấu trúc dữ liệu dựa trên chỉ mục.
  • Tree theo định nghĩa không thể chứa các bản sao.
  • Trong Listchúng ta có thể có các bản sao, vì vậy không có TreeList(tức là không SortedList).
  • Danh sách duy trì các yếu tố theo thứ tự chèn. Vì vậy, nếu chúng ta muốn sắp xếp danh sách chúng ta phải sử dụng java.util.Collections.sort(). Nó sắp xếp danh sách được chỉ định theo thứ tự tăng dần, theo thứ tự tự nhiên của các yếu tố của nó.

14

JavaFX SortedList

Mặc dù phải mất một thời gian, Java 8 vẫn được sắp xếp List. http://docs.oracle.com/javase/8/javafx/api/javafx/collections/transatures/SorticList.html

Như bạn có thể thấy trong javadocs, nó là một phần của các bộ sưu tập JavaFX , nhằm cung cấp một khung nhìn được sắp xếp trên một ObservableList.

Cập nhật: Lưu ý rằng với Java 11, bộ công cụ JavaFX đã di chuyển ra ngoài JDK và hiện là một thư viện riêng. JavaFX 11 có sẵn dưới dạng SDK có thể tải xuống hoặc từ MavenCentral. Xem https://openjfx.io


2
Thật không may, SortedList này không hoạt động như một danh sách thông thường - ví dụ: nó không có hàm tạo mặc định (bạn phải xây dựng nó bằng ObservableList, bất kể điều đó có nghĩa là gì ...)
Erel Segal-Halevi

Danh sách SortedList từ JavaFX rõ ràng là dành cho việc sử dụng các Thành phần GUI và vì chi phí KHÔNG phù hợp cho việc chỉ có Danh sách các đối tượng được sắp xếp. Ngoài ra, điều đó có nghĩa là tham chiếu toàn bộ Mô-đun FX ngay cả khi GUI không được sử dụng trong dự án.
COBRA.cH

1
@ COBRA.cH Vâng, đó là sự thật. Danh sách được sắp xếp hiệu quả hơn có thể là một trình bao bọc mỏng xung quanh TreeMap, trong đó một số nguyên được sử dụng để đếm các bản sao của khóa. Bạn cũng có thể sử dụng một TreeSet với một bộ so sánh không bao giờ trả về 0.
swpalmer 24/03/18

Tại sao các phiếu giảm? Câu hỏi bắt đầu với một giả định rằng không có danh sách được sắp xếp trong các thư viện JDK. Câu trả lời này sửa chữa giả định đó. Cho dù bạn thích hay không thích việc thực hiện danh sách được sắp xếp cụ thể này không phải là lý do để bỏ phiếu. Câu trả lời này không đề xuất lớp học, chỉ đơn thuần chỉ ra sự tồn tại của nó.
Basil Bourque

10

Đối với bất kỳ người mới nào, kể từ tháng 4 năm 2015, Android hiện có lớp SortedList trong thư viện hỗ trợ, được thiết kế riêng để làm việc RecyclerView. Đây là bài viết trên blog về nó.


1
Đáng chú ý là tại thời điểm nhận xét này, Androids SortedList thiếu hỗ trợ cho chức năng onItemMoved () với RecyclerView. Viết SortedList của riêng tôi mà kém hiệu quả hơn là những gì tôi phải làm để khắc phục những hạn chế.
Matthew

4

Một điểm khác là độ phức tạp thời gian của các hoạt động chèn. Đối với một danh sách chèn, người ta mong đợi một độ phức tạp của O (1). Nhưng điều này không thể được đảm bảo với một danh sách được sắp xếp.

Và điểm quan trọng nhất là danh sách không thừa nhận gì về các yếu tố của chúng. Ví dụ: bạn có thể lập danh sách những điều không thực hiện equalshoặc compare.


bạn có thể có danh sách w / O (logn) insert / xóa / find / chứa nhưng không có w / get (int).
bestsss

3
Điểm cuối cùng không thực sự là một lời giải thích tốt. Bạn có thể thực hiện một SortedSetsố điều không thực hiện Comparable. Xem hàm tạo này của Setet .
Alderath

1
@Alderath - có lẽ từ ngữ của tôi quá yếu. Tuy nhiên, quan sát cho rằng các yếu tố của Bộ và khóa của Cây phải tương đương ít nhất là cho sự bình đẳng, trong khi các yếu tố danh sách không cần. Cho dù mối quan hệ thứ tự / bình đẳng cho Bộ và Cây được thực hiện trong Bộ so sánh hay ở nơi khác là không quan trọng - nhưng bạn cần một mối quan hệ.
Ingo

Danh sách không đảm bảo chèn O (1) , chúng đảm bảo quyền truy cập O (1) . bigochcoateet.com
Matt Quigley

1
@Betlista bạn nói đúng! Tôi không thể cập nhật nhận xét của mình, nhưng Listgiao diện Java không đảm bảo bất kỳ thông số kỹ thuật hiệu suất nào trên các phương thức của nó.
Matt Quigley

3

Hãy nghĩ về nó như thế này: Listgiao diện có các phương thức như add(int index, E element), set(int index, E element). Hợp đồng là một khi bạn đã thêm một yếu tố ở vị trí X, bạn sẽ tìm thấy nó ở đó trừ khi bạn thêm hoặc xóa các yếu tố trước nó.

Nếu bất kỳ triển khai danh sách nào sẽ lưu trữ các phần tử theo một số thứ tự khác ngoài dựa trên chỉ mục, các phương thức danh sách trên sẽ không có ý nghĩa.


2

Dòng đầu tiên trong API danh sách cho biết đây là một bộ sưu tập có thứ tự (còn được gọi là chuỗi). Nếu bạn sắp xếp danh sách, bạn không thể duy trì thứ tự, do đó không có TreeList trong Java.
Như API cho biết Danh sách Java được lấy cảm hứng từ Trình tự và xem các thuộc tính trình tự http://en.wikipedia.org/wiki/Sequence_(mathatures )

Điều đó không có nghĩa là bạn không thể sắp xếp danh sách, nhưng Java theo đúng định nghĩa của anh ấy và không cung cấp các phiên bản danh sách được sắp xếp theo mặc định.



2

Trong trường hợp bạn đang tìm cách sắp xếp các yếu tố, nhưng cũng có thể truy cập chúng theo chỉ mục một cách hiệu quả, bạn có thể thực hiện như sau:

  1. Sử dụng danh sách truy cập ngẫu nhiên để lưu trữ (ví dụ ArrayList)
  2. Hãy chắc chắn rằng nó luôn được sắp xếp

Sau đó, để thêm hoặc xóa một phần tử bạn có thể sử dụng Collections.binarySearchđể lấy chỉ mục chèn / xóa. Vì danh sách của bạn thực hiện truy cập ngẫu nhiên, bạn có thể sửa đổi danh sách một cách hiệu quả với chỉ mục được xác định.

Thí dụ:

/**
 * @deprecated
 *      Only for demonstration purposes. Implementation is incomplete and does not 
 *      handle invalid arguments.
 */
@Deprecated
public class SortingList<E extends Comparable<E>> {
    private ArrayList<E> delegate;

    public SortingList() {
        delegate = new ArrayList<>();
    }

    public void add(E e) {
        int insertionIndex = Collections.binarySearch(delegate, e);

        // < 0 if element is not in the list, see Collections.binarySearch
        if (insertionIndex < 0) {
            insertionIndex = -(insertionIndex + 1);
        }
        else {
            // Insertion index is index of existing element, to add new element 
            // behind it increase index
            insertionIndex++;
        }

        delegate.add(insertionIndex, e);
    }

    public void remove(E e) {
        int index = Collections.binarySearch(delegate, e);
        delegate.remove(index);
    }

    public E get(int index) {
        return delegate.get(index);
    }
}

1

Xem xét sử dụng chỉ mục cây-bản đồ . Đó là một TreeSet của JDK nâng cao cung cấp quyền truy cập vào phần tử theo chỉ mục và tìm chỉ mục của một phần tử mà không lặp lại hoặc ẩn danh sách bên dưới sao lưu cây. Thuật toán dựa trên việc cập nhật trọng số của việc thay đổi các nút mỗi khi có thay đổi.

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.