Java hashmap'ı gerçekten O (1) midir?


159

SO yeniden Java hashmaps ve onların O(1)arama süresi hakkında bazı ilginç iddiaları gördüm . Birisi bunun neden böyle olduğunu açıklayabilir mi? Bu hashmaps, satın aldığım hash algoritmalarından büyük ölçüde farklı olmadıkça, her zaman çarpışma içeren bir veri kümesi olmalıdır.

Bu durumda, arama daha O(n)ziyade olacaktır O(1).

Birisi olmadığını açıklayabilir olan O (1) ve eğer öyleyse, bu nasıl başarmak?


1
Bunun bir cevap olmayabileceğini biliyorum ama Wikipedia'nın bu konuda çok iyi bir makalesi olduğunu hatırlıyorum . Performans analizi bölümünü kaçırmayın
victor hugo

28
Büyük O gösterimi, yaptığınız belirli analiz türü için bir üst sınır verir. Yine de en kötü durum, ortalama durum vb.
İle

Yanıtlar:


127

Bir HashMap'in belirli bir özelliği, örneğin dengeli ağaçların aksine, davranışının olasılıklı olmasıdır. Bu durumlarda, genellikle en kötü durumun meydana gelme olasılığı açısından karmaşıklık hakkında konuşmak en yararlısıdır. Bir karma harita için, elbette haritanın ne kadar dolu olduğuna ilişkin bir çarpışma söz konusudur. Bir çarpışmayı tahmin etmek oldukça kolaydır.

p çarpışma = n / kapasite

Bu nedenle, az sayıda unsuru olan bir karma haritanın en az bir çarpışma yaşama olasılığı yüksektir. Büyük O notasyonu daha cazip bir şey yapmamızı sağlar. Herhangi bir keyfi, sabit sabit k için gözlemleyin.

O (n) = O (k * n)

Karma haritanın performansını artırmak için bu özelliği kullanabiliriz. Bunun yerine en fazla 2 çarpışma olasılığını düşünebiliriz.

p çarpışma x 2 = (n / kapasite) 2

Bu çok daha düşük. Fazladan bir çarpışmayı ele almanın maliyeti Big O performansı ile alakasız olduğundan, algoritmayı gerçekten değiştirmeden performansı artırmanın bir yolunu bulduk! Bunu genel olarak yapabiliriz

p çarpışma xk = (n / kapasite) k

Ve şimdi keyfi sayıda çarpışmayı göz ardı edebilir ve hesapladığımızdan çok daha fazla çarpışma olasılığını ortadan kaldırabiliriz. Algoritmanın gerçek uygulamasını değiştirmeden, doğru k'yi seçerek olasılıkları küçük bir düzeye çıkarabilirsiniz.

Bu konuda karma haritanın yüksek olasılıkla O (1) erişimine sahip olduğunu söyleyerek konuşuyoruz


HTML ile bile, ben hala kesirler ile gerçekten mutlu değilim. Bunu yapmak için güzel bir yol düşünebilirseniz onları temizleyin.
SingleNegationElimination

4
Aslında, yukarıdakilerin söylediği, O (log N) etkilerinin, N'nin aşırı olmayan değerleri için sabit yük tarafından gömüldüğü.
Hot Licks

Teknik olarak, verdiğiniz sayı tek bir çarpışma olasılığına eşit olabilecek çarpışma sayısının beklenen değeridir.
Simon Kuang

1
Bu itfa edilmiş analize benzer mi?
lostsoul29

1
@ OleV.V. HashMap'in iyi performansı her zaman hash işlevinizin iyi bir dağılımına bağlıdır. Girişinizde bir kriptografik karma işlevi kullanarak karma hız için daha iyi karma kalitesini takas edebilirsiniz.
SingleNegationElimination

38

En kötü durum davranışını ortalama durum (beklenen) çalışma zamanı ile karıştırıyor gibi görünüyorsunuz. Birincisi, genel olarak karma tablolar için O (n) 'dir (yani mükemmel bir karma kullanmamak), ancak bu pratikte nadiren ilgilidir.

Herhangi bir güvenilir hash tablosu uygulaması, yarı iyi bir hash ile birleştiğinde, beklenen durumda çok dar bir fark marjı dahilinde çok küçük bir faktörle (aslında 2) O (1) geri alma performansına sahiptir.


6
Her zaman üst sınırın en kötü durum olduğunu düşündüm ama yanılmışım gibi görünüyor - ortalama durum için üst sınırın olabilir. Öyleyse O (1) iddiasında bulunanların bunu açıkça ifade etmiş olması gerekir. En kötü durum, O (n) yapan birçok çarpışmanın olduğu bir veri kümesidir. Bu şimdi mantıklı.
paxdiablo

2
Ortalama durum için büyük O gösterimini kullandığınızda, açık bir şekilde tanımlanmış bir matematik işlevi olan beklenen çalışma zamanı işlevinde bir üst sınırdan bahsettiğinizi açıkça belirtmelisiniz. Aksi takdirde cevabınız pek mantıklı değil.
'19

1
gmatt: İtirazınızı anladığımdan emin değilim: big-O notasyonu fonksiyonun tanımı gereği bir üst sınırdır . Bu nedenle başka ne ifade edebilirim?
Konrad Rudolph

3
genellikle bilgisayar literatüründe, bir algoritmanın çalışma zamanı veya alan karmaşıklığı işlevlerinde bir üst sınırı temsil eden büyük O gösterimini görürsünüz. Bu durumda üst sınır aslında bir işlev değil, işlevler (Rastgele Değişkenler) üzerinde bir işleç olan ve aslında bir integraldir (lebesgue). için verilen ve önemsiz değildir.
'19

31

Java'da HashMap, bir grubu bulmak için hashCode kullanarak çalışır. Her grup, o grupta bulunan öğelerin listesidir. Karşılaştırma için eşitler kullanılarak öğeler taranır. Öğe eklenirken, belirli bir yükleme yüzdesine ulaşıldığında HashMap yeniden boyutlandırılır.

Bu nedenle, bazen birkaç maddeyle karşılaştırmak zorunda kalacaktır, ancak genellikle O (1) 'e O (n)' den çok daha yakındır. Pratik amaçlar için bilmeniz gereken her şey budur.


11
Büyük-O'nun sınırları belirtmesi gerektiğinden, O (1) 'e daha yakın olup olmadığı hiçbir fark yaratmaz. O (n / 10 ^ 100) bile hala O (n) 'dir. Verimliliği düşürme oranını düşürüyorum ama oranı hala algoritmayı O (n) 'ye koyuyor.
paxdiablo

4
Hash-maps analizi genellikle O (1) (çarpışmalarla) olan ortalama durumdadır. En kötü durumda O (n) olabilir, ancak genellikle durum böyle değildir. farkla ilgili olarak - O (1), grafikteki öğelerin miktarından bağımsız olarak aynı erişim süresini elde ettiğiniz anlamına gelir ve genellikle durumdur (tablonun boyutu ile 'n arasında iyi bir oran olduğu sürece) ')
Liran Orevi

4
Ayrıca, kepçenin taranması biraz zaman alsa bile, içinde bazı öğeler olduğu için hala tam olarak O (1) olduğunu belirtmek gerekir. Kovalar sabit bir maksimum boyuta sahip olduğu sürece, bu sadece O () sınıflandırmasıyla ilgili olmayan sabit bir faktördür. Ancak elbette, "benzer" tuşlar eklenmiş daha fazla öğe olabilir, böylece bu kovalar taşar ve artık bir sabiti garanti edemezsiniz.
sth

@sth Kovaların neden sabit bir maksimum boyutu var?
Navin

31

O (1) öğesinin, her aramanın yalnızca tek bir öğeyi incelediği anlamına gelmediğini unutmayın; bu, denetlenen ortalama öğe sayısının kaptaki öğe sayısı ile sabit kaldığı anlamına gelir. Dolayısıyla, 100 maddelik bir kapsayıcıdaki bir öğeyi bulmak için ortalama 4 karşılaştırma gerekiyorsa, 10000 öğe içeren bir kaptaki bir öğeyi bulmak için ortalama 4 karşılaştırma gerekir ve diğer herhangi bir öğe için (her zaman bir özellikle karma tablonun yeniden toplandığı noktalarda ve çok az sayıda öğe olduğunda)

Bu nedenle, kova başına ortalama anahtar sayısı sabit bir sınır içinde kaldığı sürece, çarpışmalar kabın o (1) işlemine sahip olmasını engellemez.


16

Bunun eski bir soru olduğunu biliyorum, ama aslında yeni bir cevap var.

Bir karma haritanın gerçekten O(1), kesinlikle konuşmadığından haklısınız , çünkü elemanların sayısı keyfi olarak büyüdükçe, sonunda sabit zamanda arama yapamayacaksınız (ve O gösterimi, keyfi olarak büyür).

Ancak, gerçek zamanlı karmaşıklığın - O(n)kovaların doğrusal bir liste olarak uygulanması gerektiğini söyleyen bir kural olmadığı için gelmez.

Aslında, Java 8 kovaları TreeMapsbir eşiği aştıklarında uygular , bu da gerçek zamanı yapar O(log n).


4

Kepçe sayısı (b olarak adlandırın) sabit tutulursa (olağan durum), arama aslında O (n) olur.
N büyüdükçe, her bir gruptaki eleman sayısı ortalama n / b olur. Çarpışma çözünürlüğü normal yollardan biriyle yapılırsa (örneğin bağlantılı liste), arama O (n / b) = O (n) olur.

O gösterimi, n büyüdükçe ne olacağı ile ilgilidir. Belirli algoritmalara uygulandığında yanıltıcı olabilir ve hash tabloları buna bir örnektir. Kaç tane öğe ele almayı beklediğimize bağlı olarak kova sayısını seçiyoruz. N, b ile hemen hemen aynı boyutta olduğunda, arama kabaca sabit bir süredir, ancak O (1) diyemeyiz çünkü O, n → ∞ ile sınır olarak tanımlanmıştır.



2

Karma tablo aramalarının standart açıklamasının O (1) olduğunu, en kötü durum performansını değil, ortalama vaka beklenen süreyi ifade ettiğini belirledik. Zincirleme (Java'nın hashmap gibi) ile çarpışmaları çözen bir karma tablo için, bu teknik olarak iyi bir karma işlevine sahip O (1 + α) ' dır; burada α, tablonun yük faktörüdür. Sakladığınız nesne sayısı tablo boyutundan daha büyük bir sabit faktörden fazla olmadığı sürece hala sabittir.

Ayrıca, kesin olarak söylemek gerekirse , herhangi bir deterministik hash fonksiyonu için O ( n ) aramaları gerektiren girdi inşa etmenin mümkün olduğu açıklanmıştır . Ancak , ortalama arama süresinden farklı olan en kötü durum beklenen süresinin de dikkate alınması ilginçtir . Zincirleme kullanıldığında bu O (1 + en uzun zincirin uzunluğu), örneğin α = 1 olduğunda Θ (log n / log log n ) şeklindedir.

Sabit zaman beklenen en kötü durum aramaları elde etmek için teorik yollarla ilgileniyorsanız, çarpışmaları başka bir hash tablosu ile özyinelemeli olarak çözen dinamik mükemmel karma hakkında okuyabilirsiniz !


2

O sadece (karma) fonksiyonunuz çok iyi ise O (1) 'dir. Java karma tablosu uygulaması, hatalı karma işlevlerine karşı koruma sağlamaz.

Öğe ekleyip eklemediğinizde tabloyu büyütmeniz gerekip gerekmediği, arama süresi ile ilgili olduğu için soru ile ilgili değildir.


2

HashMap içindeki öğeler bir dizi bağlantılı liste (düğüm) olarak saklanır, dizideki her bağlantılı liste bir veya daha fazla anahtarın benzersiz karma değeri için bir grubu temsil eder.
HashMap'e bir giriş eklerken, anahtarın hashcode'u, dizideki grubun konumunu belirlemek için kullanılır, örneğin:

location = (arraylength - 1) & keyhashcode

Burada &, bitsel VE operatörünü temsil eder.

Örneğin: 100 & "ABC".hashCode() = 64 (location of the bucket for the key "ABC")

Alma işlemi sırasında, anahtar için kepçe konumunu belirlemek için aynı yolu kullanır. En iyi durumda, her anahtarın benzersiz bir hashcode'u vardır ve her anahtar için benzersiz bir grupla sonuçlanır, bu durumda get yöntemi yalnızca grup konumunu belirlemek ve O (1) sabiti olan değeri almak için zaman harcar.

En kötü durumda, tüm anahtarlar aynı hash koduna sahiptir ve aynı kovada saklanır, bu da O (n) 'ye yol açan tüm liste boyunca geçişe neden olur.

Java 8 söz konusu olduğunda, eğer boyut 8'den fazla büyürse Bağlantılı Liste grubu bir TreeMap ile değiştirilir, bu durum en kötü durum arama verimliliğini O (log n) olarak azaltır.


1

Bu, algoritmanın kendisi gerçekten değişmediği için, çoğu programlama dilinde çoğu karma tablo uygulaması için geçerlidir.

Tabloda herhangi bir çarpışma yoksa, sadece tek bir arama yapmanız gerekir, bu nedenle çalışma süresi O (1) 'dir. Mevcut çarpışmalar varsa, performansı O (n) yönünde düşüren birden fazla arama yapmanız gerekir.


1
Bu, çalışma süresinin arama süresiyle sınırlı olduğunu varsayar. Uygulamada, karma işlevinin sınır (Dize) sağladığı birçok durum bulacaksınız
Stephan Eggermont

1

Çarpışmalardan kaçınmak için seçtiğiniz algoritmaya bağlıdır. Uygulamanız ayrı zincirleme kullanıyorsa, en kötü durum senaryosu, her veri öğesinin aynı değere hash edildiği durumlarda ortaya çıkar (örneğin, karma işlevinin zayıf seçimi). Bu durumda, veri araması, bağlantılı bir listede (O (n)) yapılan doğrusal bir aramadan farklı değildir. Bununla birlikte, bunun olma olasılığı ihmal edilebilir ve en iyi ve ortalama vakaların araması sabit kalmaktadır, yani O (1).


1

Akademisyenler bir yana, pratik bir bakış açısıyla, HashMaps'in sonuçsuz bir performans etkisi olduğu kabul edilmelidir (profiliniz size aksini söylemedikçe).


4
Pratik uygulamalarda değil. Bir dizeyi anahtar olarak kullandığınız anda, tüm karma işlevlerinin ideal olmadığını ve bazılarının gerçekten yavaş olduğunu fark edeceksiniz.
Stephan Eggermont

1

Sadece teorik durumda, karma kodlar her zaman farklı olduğunda ve her karma kod için kova da farklı olduğunda, O (1) mevcut olacaktır. Aksi takdirde, sabit bir düzende olur, yani hashmap artışında, arama sırası sabit kalır.


0

Elbette hashmap'ın performansı, verilen nesne için hashCode () işlevinin kalitesine bağlı olacaktır. Bununla birlikte, fonksiyon çarpışma olasılığı çok düşük olacak şekilde uygulanırsa, çok iyi bir performansa sahip olacaktır (bu, her olası durumda kesinlikle O (1 değildir), ancak çoğu durumda).

Örneğin, Oracle JRE'deki varsayılan uygulama rastgele bir sayı kullanmaktır (nesne örneğinde değişmeyecek şekilde saklanır - ancak önyargı kilitlemeyi de devre dışı bırakır, ancak bu başka bir tartışmadır), böylece çarpışma olasılığı Çok düşük.


"çoğu durumda". Daha spesifik olarak, toplam süre, N sonsuza doğru ilerlediğinden K çarpı N'ye (K'nin sabit olduğu yerde) doğru yönelir.
ChrisW

7
Bu yanlış. Karma tablodaki dizin belirlenecek, hashCode % tableSizebu da kesinlikle çarpışma olabileceği anlamına gelir. 32 bit'i tam olarak kullanmıyorsunuz. Bu bir tür karma tabloların noktasıdır ... büyük bir dizinleme alanını küçük bir alana indirgersiniz.
FogleBird

1
"Çarpışma olmayacağından emin olabilirsiniz" Hayır, çünkü haritanın boyutu karma boyutundan küçük olduğu için değilsiniz: örneğin haritanın boyutu iki ise, çarpışma garanti edilir (önemli değil) hash) üç öğe eklemeye çalıştığımda / ne zaman.
ChrisW

Fakat bir anahtardan O (1) 'deki hafıza adresine nasıl dönüşürsünüz? Yani x = dizi ["anahtar"]. Anahtar bellek adresi değildir, bu yüzden hala O (n) araması gerekir.
paxdiablo

1
Msgstr "hashCode'u uygulamazsanız nesnenin bellek adresini kullanacağına inanıyorum". Bunu kullanabilir, ancak standart Oracle Java için varsayılan hashCode aslında nesne başlığında depolanan 25 bit rasgele bir sayıdır, bu nedenle 64/32 bitin bir sonucu yoktur.
Mart'ta Boann
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.