Hashset vs Treeset


496

Ağaçları hep sevdim, o kadar güzel O(n*log(n))ve düzenli. Ancak, tanıdığım her yazılım mühendisi bana a TreeSet. CS geçmişinden, kullandığınız kadar önemli olduğunu düşünmüyorum ve hash işlevleri ve kovalarla uğraşmak umurumda değil (durumunda Java).

Hangi durumlarda HashSetover a kullanmalıyım TreeSet?

Yanıtlar:


860

HashSet, TreeSet'ten çok daha hızlıdır (ekleme, kaldırma ve içerme gibi çoğu işlem için günlük zamana karşı sabit zaman), ancak TreeSet gibi sipariş garantisi vermez.

HashSet

  • sınıf temel işlemler için sabit zaman performansı sunar (ekleme, kaldırma, içerir ve boyutlandırma).
  • elemanların sırasının zaman içinde sabit kalacağını garanti etmez
  • yineleme performansı, başlangıç ​​kapasitesine ve HashSet'in yük faktörüne bağlıdır .
    • Varsayılan yük faktörünü kabul etmek oldukça güvenlidir, ancak setin büyümesini beklediğiniz boyutun yaklaşık iki katı olan bir başlangıç ​​kapasitesi belirtmek isteyebilirsiniz.

TreeSet

  • temel işlemler için günlük (n) zaman maliyetini garanti eder (ekleme, kaldırma ve içerir)
  • set öğelerinin sıralanacağını garanti eder (artan, doğal veya kurucu aracılığıyla sizin tarafınızdan belirtilen) (uygular SortedSet)
  • yineleme performansı için ayar parametreleri sunmuyor
  • teklifler birkaç kullanışlı yöntemler gibi sipariş seti ile başa çıkmak için first(), last(), headSet(), ve tailSet()vb

Önemli noktalar:

  • Her ikisi de yinelenen eleman koleksiyonunu garanti eder
  • HashSet'e öğe eklemek ve daha sonra, yinelenmemiş bir sıralı geçiş için koleksiyonu bir TreeSet'e dönüştürmek genellikle daha hızlıdır.
  • Bu uygulamaların hiçbiri senkronize değildir. Diğer bir deyişle, birden çok iş parçacığı bir kümeye aynı anda erişiyorsa ve iş parçacıklarından en az biri kümeyi değiştiriyorsa, dış olarak eşitlenmesi gerekir.
  • LinkedHashSet bir anlamda HashSetve arasında bir aradırTreeSet . Bununla birlikte, bağlantılı bir listenin içinden geçtiği bir karma tablosu olarak uygulanır, ancak TreeSet tarafından garanti edilen sıralı geçişle aynı olmayan ekleme siparişli yineleme sağlar .

Bu yüzden bir kullanım seçeneği tamamen ihtiyaçlarınıza bağlıdır, ancak sipariş edilmiş bir koleksiyona ihtiyacınız olsa bile, Set'i oluşturmak ve daha sonra TreeSet'e dönüştürmek için HashSet'i tercih etmeniz gerektiğini hissediyorum.

  • Örneğin SortedSet<String> s = new TreeSet<String>(hashSet);

38
"HashSet, TreeSet'ten (sabit zamana karşı log-zaman ...)" çok daha hızlı olguyu açıkça yanlış bulan sadece benim mi? Birincisi, bunun mutlak zaman değil zaman karmaşıklığı ile ilgilidir ve O (1) birçok durumda O (f (N)) 'den daha yavaş olabilir. İkincisi O (logN) "neredeyse" O (1) 'dir. Birçok yaygın durumda bir TreeSet bir HashSet'den daha iyi performans gösterdiğinde şaşırmam.
lvella

22
Sadece Ivella'nın yorumunu ikinci yapmak istiyorum. zaman karmaşıklığı çalışma zamanı ile aynı DEĞİLDİR ve O (1) her zaman O (2 ^ n) 'den daha iyi değildir. Sapkın bir örnek şu noktayı gösterir: 10 eleman için herhangi bir yaygın kabarcık sıralama (O (N ^ 2) ort / en kötü) uygulamasına karşı (O (1)) yürütmek için 1 trilyon makine talimatı alan bir karma algoritma kullanarak bir karma setini düşünün . Kabarcık sıralama her zaman kazanacak. Buradaki nokta, sınıfların herkese zaman karmaşıklığını kullanarak yaklaşımları düşünmeyi öğrettiği, ancak gerçek dünyada sık sık ÖNEMLİ sabit faktörler olduğunu öğretmektir .
Peter Oehlert

17
Belki de sadece benim, ama her şeyi bir hashete eklemek ve sonra onu korkunç bir ağaç setine dönüştürmek için tavsiye değil mi? 1) Bir karma kümesine ekleme, yalnızca veri kümenizin boyutunu önceden biliyorsanız hızlıdır, aksi takdirde O (n) yeniden karma işlemini, muhtemelen birden çok kez ödersiniz. ve 2) Seti dönüştürürken yine de TreeSet'in eklenmesi için ödeme yaparsınız. (bir intikamla, çünkü bir hashset yoluyla yineleme son derece verimli değildir)
TinkerTank

5
Bu tavsiye, bir küme için, bir öğeyi eklemeden önce bir kopya olup olmadığını kontrol etmeniz gerektiğine dayanmaktadır; bu nedenle, bir ağaç kümesi üzerinde bir karma kümesi kullanıyorsanız, kopyaları ortadan kaldırarak zaman kazanırsınız. Ancak, kopya olmayanlar için ikinci bir set oluşturmak için ödenecek bedel dikkate alındığında, kopyaların yüzdesinin bu fiyatın üstesinden gelmek ve zamandan tasarruf etmek için gerçekten büyük olması gerekir. Ve elbette, bu orta ve büyük setler içindir, çünkü küçük bir set için, ağaç kümesi muhtemelen bir hashsetten daha hızlıdır.
SylvainL

5
@PeterOehlert: Lütfen bunun için bir kıyaslama yapın. Ne demek istediğini anlıyorum, ancak her iki set arasındaki fark küçük koleksiyon boyutları ile neredeyse hiç önemli değil. Ve küme, uygulamanın önemli olduğu bir noktaya ulaşır büyümez, log (n) bir problem haline gelir. Genel olarak, karma fonksiyonları (hatta karmaşık olanlar), yaprak bulmak / erişmek / eklemek / değiştirmek için birkaç önbellek kaybından (neredeyse erişilen her düzey için büyük ağaçlarda bulunan) daha hızlı sipariş büyüklükleridir. En azından Java'daki bu iki set ile yaşadığım deneyim bu.
Bouncner

38

Henüz bahsedilmeyen bir avantaj TreeSet, daha büyük bir "yerellik" e sahip olmasıdır; bu, (1) sırayla iki girişin yakın olup olmadığını, TreeSetbunları veri yapısında ve dolayısıyla bellekte birbirine yakın yerleştirdiğini; ve (2) bu yerleşim, benzer verilere genellikle benzer sıklıkta bir uygulama tarafından erişildiğini söyleyen yerellik ilkesinden yararlanır.

Bu, HashSetanahtarları ne olursa olsun, girişleri belleğin her tarafına yayan a'nın aksine .

Bir sabit sürücüden okumanın gecikme maliyeti önbellekten veya RAM'den okuma maliyetinin binlerce katı olduğunda ve verilere yerle gerçekten erişildiğinde, TreeSetçok daha iyi bir seçim olabilir.


3
Bunu göstermek Can iki giriş sırayla yakındadır bile, bir TreeSet dolayısıyla bellekte veri yapısında birbirine yakın yerleştirir ve ?
David Soroko

6
Java için oldukça alakasız. Kümenin öğeleri yine de Nesnelerdir ve başka bir yere işaret eder, bu nedenle hiçbir şeyden tasarruf etmezsiniz.
Andrew Gallasch

Genel olarak Java'da yerellik eksikliği hakkında yapılan diğer yorumların yanı sıra, OpenJDK'nın TreeSet/ uygulaması uygulaması en uygun TreeMapyer değildir. Kırmızı-siyah bir ağacı temsil etmek ve böylece yerellik ve önbellek performansını artırmak için 4. sıradaki bir b-ağacını kullanmak mümkün olsa da, uygulama bu şekilde çalışmaz. Bunun yerine, her düğüm TreeMap.Entry için JDK 8 kaynak kodunda görünen kendi anahtarına, kendi değerine, üst öğesine ve sol ve sağ alt düğümlerine bir işaretçi depolar .
kbolino

25

HashSetelemanlara erişmek için O (1) 'dir, bu yüzden kesinlikle önemlidir. Ancak kümedeki nesnelerin sırasını korumak mümkün değildir.

TreeSetbir siparişin (ekleme sırası yerine değerler açısından) sizin için önemliyse yararlıdır. Ancak, belirttiğiniz gibi, bir öğeye erişmek için daha yavaş bir süre için işlem emri alıyorsunuz: Temel işlemler için O (log n).

Gönderen için javadocsTreeSet :

Bu uygulama, temel işlemler için garantili günlüğü (n) zamanında maliyetini sağlar ( add, removeve contains).


22

1.HashSet null nesneye izin verir.

2.TreeSet null nesneye izin vermez. Null değeri eklemeye çalışırsanız, bir NullPointerException oluşturur.

3.HashSet, TreeSet'ten çok daha hızlıdır.

Örneğin

 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), TreeSet içinde ilk Object olarak null eklenirse TreeSet için iyi çalışır. Ve bundan sonra eklenen herhangi bir nesne, CompareTo karşılaştırıcı yönteminde NullPointerException verecektir.
Shoaib Chikate

2
Gerçekten setinize nulliki şekilde eklememelisiniz .
kabarık

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

@Shevchyk tarafından Haritalar'da güzel görsel cevaplara dayanarak buradayım:

╔══════════════╦═════════════════════╦═══════════════════╦═════════════════════╗
   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

En çok kullanımın nedeni HashSet, işlemlerin O (log n) yerine (ortalama) O (1) olmasıdır. Set standart öğeler içeriyorsa, sizin için yapıldığı gibi "karma işlevlerle uğraşmak" zorunda kalmazsınız. Küme özel sınıflar içeriyorsa, hashCodekullanmak için uygulamanız gerekir HashSet(Etkili Java nasıl yapılacağını göstermesine rağmen), ancak a kullanırsanız TreeSetbunu yapmanız Comparableveya bir tedarik etmeniz gerekir Comparator. Sınıfın belirli bir sırası yoksa, bu bir sorun olabilir.

Bazen çok küçük setler / haritalar (<10 madde) için kullandım TreeSet(veya aslında TreeMap), ancak bunu yaparken gerçek bir kazanç olup olmadığını kontrol etmedim. Büyük setler için fark önemli olabilir.

Şimdi sıralamaya ihtiyacınız varsa, o TreeSetzaman uygundur, ancak o zaman bile güncellemeler sık ​​ve sıralı bir sonuç ihtiyacı nadiren olsa da, bazen içeriği bir listeye veya diziye kopyalamak ve sıralamak daha hızlı olabilir.


10K veya daha fazla gibi bu büyük öğeler için herhangi bir veri noktası
kuhajeyan

11

Sık sık yeniden paylaşımlara (veya HashSet'iniz yeniden boyutlandırılamıyorsa çarpışmalara) neden olacak kadar öğe eklemiyorsanız, bir HashSet kesinlikle size sürekli zaman erişimi avantajı sağlar. Ancak çok fazla büyüme veya büzülme olan setlerde, uygulamaya bağlı olarak, aslında Treesets ile daha iyi performans elde edebilirsiniz.

Eğer hafıza bana hizmet ediyorsa, amortisman süresi işlevsel kırmızı-siyah bir ağaçla O (1) 'e yakın olabilir. Okasaki'nin kitabının çekebileceğimden daha iyi bir açıklaması olurdu. (Veya yayın listesine bakınız )


7

HashSet uygulamaları elbette çok daha hızlıdır - daha az ek yük olduğundan sipariş yoktur. Java'daki çeşitli Set uygulamalarının iyi bir analizi http://java.sun.com/docs/books/tutorial/collections/implementations/set.html adresinde sunulmaktadır .

Buradaki tartışma, Tree vs Hash sorusuna ilginç bir 'orta yol' yaklaşımına da işaret ediyor. Java, içinden geçen "ekleme-yönelimli" bağlantılı listeye sahip bir HashSet olan LinkedHashSet sağlar, yani bağlı listedeki son öğe de Hash'e en son eklenen öğedir. Bu, bir TreeSet'in maliyetini arttırmadan düzensiz bir karmanın haksızlığından kaçınmanıza izin verir.


4

TreeSet iki sıralı koleksiyonları (diğer bir varlık TreeMap) biridir. Kırmızı-Siyah ağaç yapısı kullanır (ancak bunu biliyordunuz) ve doğal düzene göre elemanların artan sırada olacağını garanti eder. İsteğe bağlı olarak, Karşılaştırılabilir veya Karşılaştırıcı kullanarak koleksiyona siparişin ne olması gerektiği konusunda kendi kurallarınızı vermenizi sağlayan bir yapıcı ile bir TreeSet oluşturabilirsiniz (öğelerin sınıfı tarafından tanımlanan sıraya güvenmek yerine)

ve A LinkedHashSet tüm elemanlarda doubly bağlı listesini tutmaktadır HashSet sıralı bir versiyonudur. Yineleme sırasını önemsediğinizde HashSet yerine bu sınıfı kullanın. Bir HashSet üzerinden yinelediğinizde, sipariş önceden kestirilemezken, LinkedHashSet öğeleri yerleştirildikleri sırayla yinelemenizi sağlar


3

Özellikle performans konusunda, teknik hususlara dayanarak birçok cevap verilmiştir. Bana göre seçim TreeSetve HashSetönemli.

Ancak tercihin önce kavramsal düşüncelerle yönlendirilmesi gerektiğini söyleyebilirim .

Eğer manipüle etmeniz gereken nesneler için, doğal bir düzen mantıklı değilse, o zaman kullanmayın TreeSet.
Uyguladığı için sıralı bir kümedir SortedSet. Bu compareTo, işlevi döndüren işlevle tutarlı olması gereken işlevi geçersiz kılmanız gerektiği anlamına gelir equals. Örneğin, Öğrenci adında bir sınıfın nesnelerinden oluşan bir kümeniz varsa,TreeSetöğrenciler arasında doğal bir düzen olmadığı için anlamlı olur. Onları ortalama derecelerine göre sipariş edebilirsiniz, tamam, ama bu bir "doğal sipariş" değil. İşlev compareTosadece iki nesne aynı öğrenciyi temsil ettiğinde değil, aynı zamanda iki farklı öğrenci aynı sınıfa sahip olduğunda da 0 değerini döndürür. İkinci durumda, equalsyanlış döndürür (iki farklı öğrenci aynı sınıfa sahip olduğunda ikincisinin doğru olmasına karar vermedikçe, bu equalsişlev yanlış bir anlam söylemek için yanıltıcı bir anlama sahip olacaktır .)
Lütfen equalsve compareToisteğe bağlıdır, ancak şiddetle önerilir. Aksi takdirde arayüz sözleşmesi Setbozulur, bu da kodunuzu diğer insanlara yanlış yönlendirir, bu da muhtemelen beklenmedik davranışlara yol açar.

Bu bağlantı , bu soruya ilişkin iyi bir bilgi kaynağı olabilir.


3

Portakal alabileceğinizde neden elmalar var?

Ciddi adamlar ve kızlar - koleksiyonunuz büyükse, okuyun ve milyarlarca kez yazılırsa ve CPU döngüleri için ödeme yapıyorsanız, koleksiyonun seçimi SADECE daha iyi performans göstermeniz gerekiyorsa geçerlidir. Ancak, çoğu durumda, bu gerçekten önemli değil - burada birkaç milisaniye ve insani terimlerle fark edilmiyor. Gerçekten bu kadar önemliyse, neden montajcıya veya C'ye kod yazmıyorsunuz? [başka bir tartışma başlat]. Buradaki nokta, seçtiğiniz koleksiyonu kullanmaktan memnun olmanız ve sorununuzu çözmesidir (özellikle görev için en iyi koleksiyon türü olmasa bile). Yazılım dövülebilir. Kodunuzu gereken yerlerde optimize edin. Bob Amca, Erken Optimizasyon'un tüm kötülüklerin kökü olduğunu söylüyor. Bob Amca öyle diyor


1

Mesaj Düzenleme ( tam yeniden yazma ) Sipariş önemli olmadığında, tam zamanı . Her ikisi de Log (n) vermelidir - ikisinden birinin diğerinden yüzde beş daha hızlı olup olmadığını görmek faydalı olacaktır. HashSet bir döngüde O (1) testi verebilir, olup olmadığını ortaya koymalıdır.


-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
Yazı, HashSet'e öğe eklemek ve daha sonra koleksiyonu yinelenmemiş bir sıralı geçiş için bir TreeSet'e dönüştürmek daha hızlı olduğunu söyledi. <String> s = yeni TreeSet <String> (hashSet) ayarını yapın; Sıralı yineleme için kullanılacağını bildiğimizde neden doğrudan <String> s = new TreeSet <String> () ayarlanmadığını merak ediyorum, bu yüzden bu karşılaştırmayı yaptım ve sonuç daha hızlı gösterdi.
gli00001 26:12

"Hangi durumlarda bir TreeSet üzerinde bir HashSet kullanmak isterim?"
Austin Henley

1
Demek istediğim, siparişe ihtiyacınız varsa, TreeSet'i tek başına kullanmak her şeyi HashSet'e koymaktan daha sonra o HashSet'i temel alan bir TreeSet oluşturmaktan daha iyidir. Orijinal yazıdan hiç HashSet + TreeSet değerini görmüyorum.
gli00001

@ gli00001: noktayı kaçırdınız. Eğer yoksa hep sıralanmasını elemanların sizin kümesi gerekir, ancak oldukça sık manipüle edecek, o zaman çoğu zaman daha hızlı operasyonlardan yararına bir HashSet kullanmak için değer o olacak. İçin ara sıra sırayla elemanları işlemek için gereken zamanlarda, o zaman sadece bir TreeSet ile sarın. Kullanım durumunuza bağlıdır, ancak bu çok nadir bir kullanım durumundan değildir (ve muhtemelen çok fazla öğe içermeyen ve karmaşık sipariş kurallarına sahip bir kümeyi varsayar).
haylem
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.