Hashset vs Plantset


496

Tôi luôn luôn yêu cây, sự tốt đẹp O(n*log(n))và sự gọn gàng của chúng. Tuy nhiên, mọi kỹ sư phần mềm tôi từng biết đều hỏi tôi tại sao tôi sẽ sử dụng a TreeSet. Từ nền tảng CS, tôi không nghĩ nó quan trọng với tất cả những gì bạn sử dụng và tôi không quan tâm đến việc làm rối tung các hàm và hàm băm (trong trường hợp Java).

Trong trường hợp nào tôi nên sử dụng HashSethơn a TreeSet?

Câu trả lời:


860

Hashset nhanh hơn nhiều so với Treeset (thời gian không đổi so với thời gian đăng nhập đối với hầu hết các hoạt động như thêm, xóa và chứa) nhưng không cung cấp đảm bảo đặt hàng như Treeset.

Hashset

  • lớp cung cấp hiệu suất thời gian không đổi cho các hoạt động cơ bản (thêm, xóa, chứa và kích thước).
  • nó không đảm bảo rằng thứ tự của các phần tử sẽ không đổi theo thời gian
  • hiệu suất lặp phụ thuộc vào công suất ban đầu và hệ số tải của Hashset.
    • Nó khá an toàn để chấp nhận hệ số tải mặc định nhưng bạn có thể muốn chỉ định công suất ban đầu có kích thước gấp đôi kích thước mà bạn mong đợi bộ sẽ phát triển.

Cây Set

  • đảm bảo chi phí thời gian đăng nhập (n) cho các hoạt động cơ bản (thêm, xóa và chứa)
  • đảm bảo rằng các phần tử của tập hợp sẽ được sắp xếp (tăng dần, tự nhiên hoặc phần tử do bạn chỉ định thông qua hàm tạo của nó) (thực hiện SortedSet)
  • không cung cấp bất kỳ tham số điều chỉnh nào cho hiệu suất lặp
  • Mời một vài phương pháp tiện dụng để đối phó với tập lệnh như first(), last(), headSet(), và tailSet()vv

Điểm quan trọng:

  • Cả hai đều đảm bảo bộ sưu tập các yếu tố không trùng lặp
  • Nói chung, nhanh hơn để thêm các phần tử vào Hashset và sau đó chuyển đổi bộ sưu tập thành một Setet để có một giao dịch được sắp xếp không trùng lặp.
  • Không ai trong số các triển khai này được đồng bộ hóa. Đó là nếu nhiều luồng truy cập một tập hợp đồng thời và ít nhất một trong các luồng sửa đổi tập hợp, thì nó phải được đồng bộ hóa bên ngoài.
  • LinkedHashSet trong một số ý nghĩa trung gian giữa HashSetTreeSet. Được triển khai dưới dạng bảng băm với danh sách được liên kết chạy qua nó, tuy nhiên, nó cung cấp phép lặp theo thứ tự chèn không giống như giao dịch được sắp xếp được đảm bảo bởi Treeset .

Vì vậy, sự lựa chọn sử dụng phụ thuộc hoàn toàn vào nhu cầu của bạn nhưng tôi cảm thấy rằng ngay cả khi bạn cần một bộ sưu tập theo thứ tự thì bạn vẫn nên thích Hashset để tạo Set và sau đó chuyển đổi nó thành TreeSet.

  • ví dụ SortedSet<String> s = new TreeSet<String>(hashSet);

38
Chỉ có tôi mới tìm thấy lời khẳng định "Hashset nhanh hơn nhiều so với TreeSet (thời gian không đổi so với thời gian đăng nhập ...)" hoàn toàn sai? Đầu tiên, đây là về độ phức tạp thời gian, không phải thời gian tuyệt đối và O (1) có thể trong quá nhiều trường hợp chậm hơn O (f (N)). Thứ hai rằng O (logN) là "gần như" O (1). Tôi sẽ không ngạc nhiên nếu trong nhiều trường hợp phổ biến, TreeSet vượt trội hơn Hashset.
lvella

22
Tôi chỉ muốn bình luận thứ hai của Ivella. độ phức tạp thời gian KHÔNG giống như thời gian chạy và O (1) không phải lúc nào cũng tốt hơn O (2 ^ n). Một ví dụ sai lầm minh họa điểm: xem xét một bộ băm bằng thuật toán băm lấy 1 nghìn tỷ hướng dẫn máy để thực thi (O (1)) so với bất kỳ triển khai phổ biến nào về sắp xếp bong bóng (O (N ^ 2) avg / tệ nhất) cho 10 phần tử . Sắp xếp bong bóng sẽ giành chiến thắng mọi lúc. Vấn đề là các thuật toán lớp dạy mọi người phải suy nghĩ về xấp xỉ sử dụng thời gian phức tạp nhưng trong thế giới thực các yếu tố liên tục vấn đề thường xuyên.
Peter Oehlert

17
Có lẽ đó chỉ là tôi, nhưng không phải là lời khuyên trước tiên nên thêm mọi thứ vào một bộ băm, và sau đó biến nó thành một cái cây kinh khủng? 1) Việc chèn vào hàm băm chỉ nhanh nếu bạn biết trước kích thước của tập dữ liệu của mình, nếu không, bạn phải trả lại băm O (n), có thể nhiều lần. và 2) Dù sao bạn cũng phải trả tiền cho việc chèn TreeSet khi chuyển đổi tập hợp. (với sự báo thù, bởi vì việc lặp lại thông qua hàm băm không hiệu quả khủng khiếp)
TinkerTank

5
Lời khuyên này dựa trên thực tế là đối với một bộ, bạn phải kiểm tra xem liệu một mục có bị trùng lặp hay không trước khi thêm nó; do đó, bạn sẽ tiết kiệm thời gian loại bỏ các bản sao nếu bạn đang sử dụng hàm băm trên cây. Tuy nhiên, xem xét giá phải trả cho việc tạo bộ thứ hai cho các mục không trùng lặp, tỷ lệ phần trăm trùng lặp sẽ thực sự tuyệt vời để vượt qua mức giá này và làm cho nó tiết kiệm thời gian hơn. Và tất nhiên, điều này là dành cho các bộ vừa và lớn vì đối với một bộ nhỏ, cây có thể nhanh hơn một bộ băm.
SylvainL

5
@PeterOehlert: vui lòng cung cấp điểm chuẩn cho điều đó. Tôi hiểu quan điểm của bạn, nhưng sự khác biệt giữa cả hai bộ hầu như không quan trọng với kích thước bộ sưu tập nhỏ. Và ngay khi tập hợp phát triển đến một điểm, trong đó việc thực hiện có vấn đề, log (n) đang trở thành một vấn đề. Nói chung là các hàm băm (thậm chí là phức tạp) cường độ của trật tự nhanh hơn một số lỗi bộ nhớ cache (mà bạn có trên các cây khổng lồ cho hầu hết mọi cấp độ truy cập) để tìm / truy cập / thêm / sửa đổi lá. Ít nhất đó là kinh nghiệm của tôi với hai bộ này trong Java.
Bouncner

38

Một lợi thế chưa được đề cập đến TreeSetlà nó có "địa phương" lớn hơn, đó là cách viết tắt (1) nếu hai mục nhập gần nhau theo thứ tự, TreeSetđặt chúng gần nhau trong cấu trúc dữ liệu và do đó trong bộ nhớ; và (2) vị trí này tận dụng nguyên tắc địa phương, trong đó nói rằng dữ liệu tương tự thường được truy cập bởi một ứng dụng có tần suất tương tự.

Điều này trái ngược với a HashSet, trải đều các mục trên toàn bộ nhớ, bất kể khóa của chúng là gì.

Khi chi phí đọc độ trễ từ ổ cứng gấp hàng nghìn lần chi phí đọc từ bộ nhớ cache hoặc RAM và khi dữ liệu thực sự được truy cập với địa phương, TreeSetcó thể là lựa chọn tốt hơn nhiều.


3
Bạn có thể chứng minh rằng nếu hai mục nhập gần nhau theo thứ tự, một TreeSet đặt chúng gần nhau trong cấu trúc dữ liệu và do đó trong bộ nhớ ?
David Soroko

6
Khá không liên quan đến Java. Các thành phần của tập hợp dù sao cũng là Đối tượng và chỉ ra một nơi khác, vì vậy bạn không tiết kiệm được gì nhiều.
Andrew Gallasch

Bên cạnh các ý kiến ​​khác được đưa ra về việc thiếu tính cục bộ trong Java nói chung, việc triển khai TreeSet/ TreeMapkhông phải là địa phương được OpenJDK tối ưu hóa. Mặc dù có thể sử dụng cây b bậc 4 để thể hiện cây đỏ-đen và do đó cải thiện hiệu năng cục bộ và bộ đệm, đó không phải là cách triển khai. Thay vào đó, mỗi nút lưu trữ một con trỏ tới khóa riêng của nó, giá trị riêng của nó, nút mẹ và các nút con trái và phải của nó, hiển nhiên trong mã nguồn JDK 8 cho TreeMap.Entry .
kbolino

25

HashSetlà O (1) để truy cập các phần tử, vì vậy nó chắc chắn có vấn đề. Nhưng việc duy trì trật tự của các đối tượng trong tập hợp là không thể.

TreeSetlà hữu ích nếu duy trì một đơn đặt hàng (Về mặt giá trị và không phải thứ tự chèn) quan trọng với bạn. Nhưng, như bạn đã lưu ý, bạn đang giao dịch để có thời gian chậm hơn để truy cập vào một yếu tố: O (log n) cho các hoạt động cơ bản.

Từ javadocs choTreeSet :

Thực hiện này cung cấp bảo đảm chi phí log (n) thời gian cho các thao tác cơ bản ( add, removecontains).


22

1.Hashset cho phép đối tượng null.

2.TreeSet sẽ không cho phép đối tượng null. Nếu bạn cố gắng thêm giá trị null, nó sẽ ném NullPulumException.

3.Hashset nhanh hơn nhiều so với Treeset.

ví dụ

 TreeSet<String> ts = new TreeSet<String>();
 ts.add(null); // throws NullPointerException

 HashSet<String> hs = new HashSet<String>();
 hs.add(null); // runs fine

3
ts.add (null) nó sẽ hoạt động tốt trong trường hợp của TreeSet nếu null được thêm làm Đối tượng đầu tiên trong Treeset. Và bất kỳ đối tượng nào được thêm vào sau đó sẽ cung cấp cho NullPulumException trong phương thức so sánh của Trình so sánh.
Shoaib Chikate

2
Bạn thực sự thực sự không nên thêm nullvào thiết lập của bạn một trong hai cách.
lông mịn

TreeSet<String> badassTreeSet = new TreeSet<String>(new Comparator<String>() { public int compare(String string1, String string2) { if (string1 == null) { return (string2 == null) ? 0 : -1; } else if (string2 == null) { return 1; } else { return string1.compareTo(string2); } } }); badassTreeSet.add("tree"); badassTreeSet.add("asdf"); badassTreeSet.add(null); badassTreeSet.add(null); badassTreeSet.add("set"); badassTreeSet.add("tree"); System.out.println(badassTreeSet);
Dávid Horváth

21

Dựa trên câu trả lời trực quan đáng yêu trên Maps của @shevchyk, đây là ý kiến ​​của tôi:

╔══════════════╦═════════════════════╦═══════════════════╦═════════════════════╗
   Property          HashSet             TreeSet           LinkedHashSet   
╠══════════════╬═════════════════════╬═══════════════════╬═════════════════════╣
                no guarantee order  sorted according                       
   Order       will remain constant to the natural        insertion-order  
                    over time          ordering                            
╠══════════════╬═════════════════════╬═══════════════════╬═════════════════════╣
 Add/remove           O(1)              O(log(n))             O(1)         
╠══════════════╬═════════════════════╬═══════════════════╬═════════════════════╣
                                      NavigableSet                         
  Interfaces           Set                Set                  Set         
                                       SortedSet                           
╠══════════════╬═════════════════════╬═══════════════════╬═════════════════════╣
                                       not allowed                         
  Null values        allowed        1st element only        allowed        
                                        in Java 7                          
╠══════════════╬═════════════════════╩═══════════════════╩═════════════════════╣
                 Fail-fast behavior of an iterator cannot be guaranteed      
   Fail-fast   impossible to make any hard guarantees in the presence of     
   behavior              unsynchronized concurrent modification              
╠══════════════╬═══════════════════════════════════════════════════════════════╣
      Is                                                                     
 synchronized               implementation is not synchronized               
╚══════════════╩═══════════════════════════════════════════════════════════════╝

13

Lý do tại sao hầu hết sử dụng HashSetlà các hoạt động (trung bình) O (1) thay vì O (log n). Nếu bộ chứa các mục tiêu chuẩn, bạn sẽ không "làm rối tung các hàm băm" như điều đó đã được thực hiện cho bạn. Nếu tập hợp chứa các lớp tùy chỉnh, bạn phải triển khai hashCodeđể sử dụng HashSet(mặc dù Java hiệu quả hiển thị như thế nào), nhưng nếu bạn sử dụng, TreeSetbạn phải tạo nó Comparablehoặc cung cấp a Comparator. Đây có thể là một vấn đề nếu lớp không có một thứ tự cụ thể.

Đôi khi tôi đã sử dụng TreeSet(hoặc thực tế TreeMap) cho các bộ / bản đồ rất nhỏ (<10 mục) mặc dù tôi chưa kiểm tra xem liệu có bất kỳ lợi ích thực sự nào khi làm như vậy không. Đối với các bộ lớn, sự khác biệt có thể là đáng kể.

Bây giờ nếu bạn cần sắp xếp, thì TreeSetphù hợp, mặc dù sau đó nếu cập nhật thường xuyên và nhu cầu về kết quả được sắp xếp không thường xuyên, đôi khi sao chép nội dung vào danh sách hoặc mảng và sắp xếp chúng có thể nhanh hơn.


bất kỳ điểm dữ liệu nào cho các yếu tố lớn này, chẳng hạn như 10K trở lên
kuhajeyan

11

Nếu bạn không chèn đủ các yếu tố để dẫn đến việc thử lại thường xuyên (hoặc va chạm, nếu Hashset của bạn không thể thay đổi kích thước), Hashset chắc chắn mang lại cho bạn lợi ích của việc truy cập thời gian liên tục. Nhưng trên các bộ có nhiều tăng trưởng hoặc co lại, bạn thực sự có thể có hiệu suất tốt hơn với Plantsets, tùy thuộc vào việc thực hiện.

Thời gian khấu hao có thể gần với O (1) với cây đỏ đen chức năng, nếu bộ nhớ phục vụ cho tôi. Cuốn sách của Okasaki sẽ có một lời giải thích tốt hơn tôi có thể rút ra. (Hoặc xem danh sách xuất bản của anh ấy )


7

Tất nhiên, việc triển khai Hashset nhanh hơn nhiều - ít chi phí hơn vì không có đơn hàng. Một phân tích tốt về các triển khai Set khác nhau trong Java được cung cấp tại http://java.sun.com/docs/books/tutorial/collections/im THỰCations / set.html .

Cuộc thảo luận ở đó cũng chỉ ra một cách tiếp cận 'trung dung' thú vị cho câu hỏi Tree vs Hash. Java cung cấp một LinkedHashset, là một Hashset với danh sách được liên kết "định hướng chèn" chạy qua nó, nghĩa là, phần tử cuối cùng trong danh sách được liên kết cũng là phần tử được chèn gần đây nhất vào Hash. Điều này cho phép bạn tránh được sự không đáng tin cậy của hàm băm không có thứ tự mà không phải chịu chi phí tăng lên của TreeSet.


4

Các TreeSet là một trong hai bộ sưu tập được sắp xếp (hữu thể TreeMap khác). Nó sử dụng cấu trúc cây Đỏ-Đen (nhưng bạn biết điều đó) và đảm bảo rằng các yếu tố sẽ theo thứ tự tăng dần, theo thứ tự tự nhiên. Theo tùy chọn, bạn có thể xây dựng một TreeSet với một hàm tạo cho phép bạn đưa ra bộ sưu tập các quy tắc của riêng bạn cho thứ tự sẽ là gì (thay vì dựa vào thứ tự được xác định bởi lớp của các phần tử) bằng cách sử dụng Trình so sánh hoặc Trình so sánh

LinkedHashSet là phiên bản được đặt hàng của Hashset duy trì Danh sách liên kết đôi trên tất cả các yếu tố. Sử dụng lớp này thay vì Hashset khi bạn quan tâm đến thứ tự lặp. Khi bạn lặp qua Hashset, thứ tự không thể đoán trước được, trong khi LinkedHashset cho phép bạn lặp qua các phần tử theo thứ tự chúng được chèn


3

Rất nhiều câu trả lời đã được đưa ra, dựa trên những cân nhắc kỹ thuật, đặc biệt là về hiệu suất. Theo tôi, sự lựa chọn giữa TreeSetHashSetvấn đề.

Nhưng tôi muốn nói rằng sự lựa chọn nên được thúc đẩy bởi những cân nhắc về khái niệm đầu tiên.

Nếu, đối với các đối tượng bạn cần thao tác, một trật tự tự nhiên không có ý nghĩa, thì không sử dụng TreeSet.
Nó là một tập hợp được sắp xếp, vì nó thực hiện SortedSet. Vì vậy, nó có nghĩa là bạn cần ghi đè chức năng compareTo, phải phù hợp với chức năng trả về equals. Ví dụ: nếu bạn có một tập hợp các đối tượng của một lớp gọi là Học sinh, thì tôi không nghĩ làTreeSetsẽ có ý nghĩa, vì không có trật tự tự nhiên giữa các sinh viên. Bạn có thể đặt hàng theo cấp trung bình của họ, được thôi, nhưng đây không phải là "thứ tự tự nhiên". Hàm compareTosẽ trả về 0 không chỉ khi hai đối tượng đại diện cho cùng một học sinh mà cả khi hai học sinh khác nhau có cùng một lớp. Đối với trường hợp thứ hai, equalssẽ trả về false (trừ khi bạn quyết định biến trường sau trở thành đúng khi hai học sinh khác nhau có cùng một lớp, điều này sẽ khiến equalshàm có nghĩa sai, không nói nghĩa sai.)
Xin lưu ý sự thống nhất này giữa equalscompareTolà tùy chọn, nhưng rất khuyến khích. Nếu không, hợp đồng giao diện Setbị phá vỡ, làm cho mã của bạn gây hiểu lầm cho người khác, do đó cũng có thể dẫn đến hành vi không mong muốn.

Liên kết này có thể là một nguồn thông tin tốt về câu hỏi này.


3

Tại sao có táo khi bạn có thể có cam?

Nghiêm túc các chàng trai và cô gái - nếu bộ sưu tập của bạn lớn, hãy đọc và viết cho những ánh mắt nhiều lần và bạn đang trả tiền cho các chu kỳ CPU, thì việc lựa chọn bộ sưu tập chỉ có liên quan nếu bạn CẦN nó hoạt động tốt hơn. Tuy nhiên, trong hầu hết các trường hợp, điều này không thực sự quan trọng - một vài phần nghìn giây ở đây và không được chú ý về mặt con người. Nếu nó thực sự quan trọng đến vậy, tại sao bạn không viết mã bằng trình biên dịch mã hoặc C? [đưa ra một cuộc thảo luận khác]. Vì vậy, vấn đề là nếu bạn hài lòng khi sử dụng bất kỳ bộ sưu tập nào bạn đã chọn và nó sẽ giải quyết vấn đề của bạn [ngay cả khi đó không phải là loại bộ sưu tập tốt nhất cho nhiệm vụ] đánh gục bạn. Phần mềm có thể uốn được. Tối ưu hóa mã của bạn khi cần thiết. Bác Bob nói Tối ưu hóa sớm là gốc rễ của mọi tội lỗi. Chú Bob nói vậy


1

Chỉnh sửa tin nhắn ( viết lại hoàn toàn ) Khi thứ tự không quan trọng, đó là khi. Cả hai sẽ cung cấp cho Log (n) - sẽ rất hữu ích để xem nếu một trong hai nhanh hơn so với cái kia. Hashset có thể cung cấp thử nghiệm O (1) trong một vòng lặp sẽ tiết lộ liệu nó có.


-3
import java.util.HashSet;
import java.util.Set;
import java.util.TreeSet;

public class HashTreeSetCompare {

    //It is generally faster to add elements to the HashSet and then
    //convert the collection to a TreeSet for a duplicate-free sorted
    //Traversal.

    //really? 
    O(Hash + tree set) > O(tree set) ??
    Really???? Why?



    public static void main(String args[]) {

        int size = 80000;
        useHashThenTreeSet(size);
        useTreeSetOnly(size);

    }

    private static void useTreeSetOnly(int size) {

        System.out.println("useTreeSetOnly: ");
        long start = System.currentTimeMillis();
        Set<String> sortedSet = new TreeSet<String>();

        for (int i = 0; i < size; i++) {
            sortedSet.add(i + "");
        }

        //System.out.println(sortedSet);
        long end = System.currentTimeMillis();

        System.out.println("useTreeSetOnly: " + (end - start));
    }

    private static void useHashThenTreeSet(int size) {

        System.out.println("useHashThenTreeSet: ");
        long start = System.currentTimeMillis();
        Set<String> set = new HashSet<String>();

        for (int i = 0; i < size; i++) {
            set.add(i + "");
        }

        Set<String> sortedSet = new TreeSet<String>(set);
        //System.out.println(sortedSet);
        long end = System.currentTimeMillis();

        System.out.println("useHashThenTreeSet: " + (end - start));
    }
}

1
Bài đăng nói rằng thường nhanh hơn để thêm các phần tử vào Hashset và sau đó chuyển đổi bộ sưu tập thành một Setet để có một giao dịch được sắp xếp không trùng lặp. Đặt <String> s = new Treeset <String> (hashset); Tôi tự hỏi tại sao không trực tiếp Đặt <String> s = new Plantset <String> () nếu chúng ta biết nó sẽ được sử dụng để lặp lại được sắp xếp, vì vậy tôi đã thực hiện so sánh này và kết quả cho thấy nhanh hơn.
gli00001

"Trong trường hợp nào tôi muốn sử dụng Hashset trên TreeSet?"
Austin Henley

1
Quan điểm của tôi là, nếu bạn cần đặt hàng, sử dụng một mình TreeSet sẽ tốt hơn là đặt mọi thứ vào Hashset sau đó tạo một TreeSet dựa trên Hashset đó. Tôi hoàn toàn không thấy giá trị của Hashset + Treeset từ bài viết gốc.
gli00001

@ gli00001: bạn đã bỏ lỡ điểm. Nếu bạn không luôn cần tập hợp các yếu tố của mình, nhưng sẽ thao túng nó khá thường xuyên, thì bạn nên sử dụng một hàm băm để hưởng lợi từ các hoạt động nhanh hơn trong hầu hết thời gian. Đối với những lần thỉnh thoảng bạn cần xử lý các yếu tố theo thứ tự, sau đó chỉ cần bọc bằng một cái cây. Nó phụ thuộc vào trường hợp sử dụng của bạn, nhưng đó không phải là trường hợp sử dụng không phổ biến (và có thể giả định một tập hợp không chứa quá nhiều phần tử và với các quy tắc đặt hàng phức tạp).
haylem
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.