Javaハッシュマップは本当にO(1)ですか?


159

JavaハッシュマップとそのO(1)ルックアップ時間についての興味深い主張を見てきました。これがなぜそうであるか誰かが説明できますか?これらのハッシュマップが、購入したハッシュアルゴリズムと大きく異なる場合を除き、衝突を含むデータセットが常に存在している必要があります。

その場合、ルックアップはでO(n)はなくになりO(1)ます。

誰かがそれら O(1)であるかどうかを説明できますか?


1
これは答えではないかもしれませんが、ウィキペディアにこれに関する非常に優れた記事があることを覚えています。パフォーマンス分析セクションをお見逃しなく
victor hugo

28
Big O表記は、実行している特定のタイプの分析の上限を示します。あなたはまだあなたがなど最悪の場合、平均的なケース、に興味があるかどうかを指定する必要があります
ダンHomerick

回答:


127

HashMapの特定の機能は、たとえば、バランスツリーとは異なり、その動作は確率的です。これらのケースでは、ワーストケースのイベントが発生する確率の観点から複雑さについて話すことが通常最も役立ちます。ハッシュマップの場合、それはもちろん、マップがどれほどいっぱいであるかに関する衝突の場合です。衝突は簡単に推定できます。

p 衝突 = n /容量

したがって、要素の数が適度なハッシュマップでも、少なくとも1つの衝突が発生する可能性が高くなります。Big O表記を使用すると、より説得力のある何かを実行できます。任意の固定定数kがあることを確認します。

O(n)= O(k * n)

この機能を使用して、ハッシュマップのパフォーマンスを向上させることができます。代わりに、最大2つの衝突の確率について考えることができます。

p 衝突x 2 =(n /容量)2

これははるかに低いです。1つの余分な衝突を処理するコストはBig Oのパフォーマンスとは無関係であるため、実際にアルゴリズムを変更せずにパフォーマンスを向上させる方法を見つけました。これを一般化することができます

p 衝突xk =(n /容量)k

そして今、私たちはいくつかの任意の数の衝突を無視し、私たちが説明しているよりも多くの衝突の可能性がほとんどなくなる可能性があります。アルゴリズムの実際の実装を変更せずに、正しいkを選択することで、確率を任意の小さなレベルに設定できます。

これについては、ハッシュマップが高い確率で O(1)アクセス持っていると言います。


HTMLを使っても、分数にはまだ満足していません。あなたがそれを行うための良い方法を考えることができるなら、それらをきれいにしてください。
SingleNegationElimination 2009年

4
実際、上記の内容は、Nの非極端な値に対して、固定オーバーヘッドによってO(log N)効果が埋められることです。
Hot Licks 2014年

技術的には、あなたが与えたその数は衝突の数の期待値であり、これは単一の衝突の確率と等しくなる可能性があります。
Simon Kuang 2015

1
これは償却分析と似ていますか?
lostsoul29

1
@ OleV.V。HashMapの優れたパフォーマンスは、常にハッシュ関数の適切な分布に依存します。入力に暗号化ハッシュ関数を使用することで、ハッシュ品質を向上させてハッシュ速度を上げることができます。
SingleNegationElimination

38

ワーストケースの動作と平均ケース(予想)ランタイムを混同しているようです。前者は確かに一般にハッシュテーブルのO(n)です(つまり、完全なハッシュを使用していません)が、実際にはほとんど関係ありません。

信頼できるハッシュテーブルの実装は、ハーフディセントハッシュと相まって、非常に狭い分散範囲内で、予想されるケースでは非常に小さい係数(実際は2)のO(1)の検索パフォーマンスを持っています。


6
私はいつも上限が最悪のケースだと思っていましたが、それは私が間違っていたようです-あなたは平均的なケースの上限を持つことができます。したがって、O(1)を主張する人々は、それが平均的なケースであったことを明確にすべきだったようです。最悪のケースは、それがO(n)になる多くの衝突があるデータセットです。それは今では理にかなっています。
paxdiablo 2009年

2
平均的なケースで大きなO表記を使用する場合、明確に定義された数学関数である予想されるランタイム関数の上限について話していることをおそらく明確にする必要があります。そうでなければ、あなたの答えはあまり意味がありません。
ldog 2009年

1
gmatt:私はあなたの異論を理解していることを確信できません:big-O表記は、定義により、関数の上限です。したがって、私は他に何を意味するでしょうか?
Konrad Rudolph、

3
通常、コンピュータの文献では、アルゴリズムのランタイムまたはスペース複雑度関数の上限を表す大きなO表記が見られます。この場合、上限は実際にはそれ自体が関数ではなく、関数(ランダム変数)の演算子である期待値であり、実際には積分です(推測)。当然のことであり、些細なことではありません。
ldog 2009年

31

Javaでは、HashMapはhashCodeを使用してバケットを検索することで機能します。各バケットは、そのバケット内にあるアイテムのリストです。比較のために等しいを使用して、アイテムがスキャンされます。アイテムを追加するとき、特定のロードパーセンテージに達すると、HashMapのサイズが変更されます。

したがって、いくつかの項目と比較する必要がある場合もありますが、一般的にはO(n)よりもO(1)にはるかに近くなります。実用的な目的のために、あなたが知る必要があるのはそれだけです。


11
まあ、big-Oは制限を指定することになっているので、O(1)に近いかどうかに関係なく、違いはありません。O(n / 10 ^ 100)でもO(n)です。効率を上げて比率を下げるというあなたのポイントはわかりますが、それでもアルゴリズムはO(n)になります。
paxdiablo 2009年

4
ハッシュマップ分析は通常、平均的なケースであり、これはO(1)(共謀あり)です。最悪の場合、O(n)になる可能性がありますが、通常はそうではありません。違いについて-O(1)は、グラフ上のアイテムの量に関係なく同じアクセス時間を取得することを意味します。これは通常そうです(テーブルのサイズと 'n ')
リラン・オレビ2009年

4
すでにいくつかの要素が含まれているためにバケットのスキャンに時間がかかる場合でも、それはまだ正確にO(1)であることも注目に値します。バケットの最大サイズが固定されている限り、これはO()分類に関係のない一定の要素にすぎません。しかし、もちろん、「類似した」キーが追加された要素がさらに存在する可能性があるため、これらのバケットはオーバーフローし、定数を保証できなくなります。
STH

@sthバケットの最大サイズが固定されているのはなぜですか?
Navin、2015年

31

o(1)は、各ルックアップが単一の項目のみを検査することを意味しないことに注意してください。つまり、チェックされる項目の平均数は、コンテナー内の項目の数に対して一定のままです。したがって、100アイテムのコンテナーでアイテムを見つけるために平均4つの比較が必要な場合、10000アイテムのコンテナーでアイテムを見つけるために、平均4つの比較を行う必要があります。特に、ハッシュテーブルが再ハッシュされるポイントの周り、およびアイテムの数が非常に少ないときの差異)。

そのため、バケットごとのキーの平均数が一定の範囲内にある限り、衝突によってコンテナがo(1)オペレーションを持つことを妨げることはありません。


16

これは古い質問ですが、実際には新しい答えがあります。

O(1)厳密に言えば、ハッシュマップは実際にはとは言えません。要素の数が任意に大きくなると、最終的には一定の時間で検索できなくなります(そしてO表記は、任意に大きくなります)。

しかし、リアルタイムの複雑さはO(n)そうではありません-バケットを線形リストとして実装する必要があることを示すルールがないためです。

実際、Java 8はバケットをTreeMapsしきい値を超えると実装するため、実際の時間が長くなりO(log n)ます。


4

バケットの数(bと呼びます)が一定に保たれている場合(通常の場合)、ルックアップは実際にはO(n)です。
nが大きくなると、各バケットの要素数は平均してn / bになります。衝突解決が通常の方法(リンクされたリストなど)のいずれかで行われる場合、ルックアップはO(n / b)= O(n)になります。

O表記は、nがどんどん大きくなるとどうなるかを表しています。特定のアルゴリズムに適用すると誤解を招く可能性があり、ハッシュテーブルがその好例です。処理する要素の数に基づいてバケットの数を選択します。nがbとほぼ同じサイズの場合、ルックアップはほぼ一定時間ですが、Oはnから∞の制限で定義されているため、O(1)と呼ぶことはできません。



2

ハッシュテーブルルックアップの標準的な説明がO(1)であることは、厳密な最悪の場合のパフォーマンスではなく、平均ケースの予想時間を参照することを確立しました。チェーンとの衝突を解決するハッシュテーブル(Javaのハッシュマップなど)の場合、これは技術的には良いハッシュ関数を持つO(1 +α)です。ここで、αはテーブルの負荷係数です。格納するオブジェクトの数がテーブルサイズよりも大きい一定の因数である限り、依然として一定です。

厳密に言えば、確定的ハッシュ関数に対してO(n)ルックアップを必要とする入力を構築することが可能であることも説明されています。しかし、平均検索時間とは異なる最悪の場合の予想時間を考慮することも興味深いです。チェーンを使用すると、これはO(1 +最長チェーンの長さ)になります。たとえば、α= 1の場合はΘ(log n / log log n)です。

ワーストケースのルックアップを一定時間予測する理論的な方法に興味がある場合は、別のハッシュテーブルとの衝突を再帰的に解決する動的完全ハッシュについて読むことができます。


2

ハッシュ関数が非常に優れている場合のみ、O(1)になります。Javaハッシュテーブルの実装は、不正なハッシュ関数を保護しません。

アイテムを追加するときにテーブルを拡張する必要があるかどうかは、ルックアップ時間に関するものであるため、問題には関係ありません。


2

HashMap内の要素は、リンクリスト(ノード)の配列として格納されます。配列内の各リンクリストは、1つ以上のキーの一意のハッシュ値のバケットを表します。
HashMapにエントリを追加するときに、キーのハッシュコードを使用して、配列内のバケットの場所を決定します。

location = (arraylength - 1) & keyhashcode

ここでは、&はビットごとのAND演算子を表します。

例えば: 100 & "ABC".hashCode() = 64 (location of the bucket for the key "ABC")

get操作の間、同じ方法を使用して、キーのバケットの場所を決定します。最良のケースでは、各キーに一意のハッシュコードがあり、各キーに一意のバケットが作成されます。この場合、getメソッドは時間を費やしてバケットの場所を特定し、定数O(1)である値を取得します。

最悪の場合、すべてのキーは同じハッシュコードを持ち、同じバケットに保存されます。これにより、リスト全体を走査してO(n)を導きます。

Java 8の場合、リンクリストバケットは、サイズが8を超えるとTreeMapに置き換えられます。これにより、最悪の場合の検索効率がO(log n)に低下します。


1

アルゴリズム自体は実際には変更されないため、これは基本的に、ほとんどのプログラミング言語のほとんどのハッシュテーブル実装に当てはまります。

テーブルに衝突がない場合、1回のルックアップを実行するだけでよいため、実行時間はO(1)です。衝突が存在する場合、複数のルックアップを実行する必要があります。これにより、パフォーマンスがO(n)に向かって低下します。


1
これは、実行時間が検索時間によって制限されることを前提としています。実際には、ハッシュ関数が境界(文字列)を提供する多くの状況が見つかります
ステファンエガーモント2009

1

衝突を回避するために選択したアルゴリズムによって異なります。実装で個別のチェーンを使用する場合、すべてのデータ要素が同じ値にハッシュされる最悪のシナリオが発生します(たとえば、ハッシュ関数の選択が不適切)。その場合、データ検索は、リンクされたリスト、つまりO(n)の線形検索と同じです。ただし、その発生の確率はごくわずかであり、最適なルックアップと平均のケースは一定、つまりO(1)のままです。


1

アカデミックは別として、実用的な観点からは、HashMapはパフォーマンスに取るに足らない影響を与えるものとして受け入れられるべきです(プロファイラーが他に指示しない限り)。


4
実際のアプリケーションではありません。キーとして文字列を使用するとすぐに、すべてのハッシュ関数が理想的であるとは限らず、一部のハッシュ関数は本当に遅いことに気づくでしょう。
ステファンエガーモント2009

1

理論的な場合にのみ、ハッシュコードが常に異なり、すべてのハッシュコードのバケットも異なる場合、O(1)が存在します。それ以外の場合、それは一定の順序です。つまり、ハッシュマップの増分では、検索の順序は一定のままです。


0

もちろん、ハッシュマップのパフォーマンスは、与えられたオブジェクトのhashCode()関数の品質に依存します。ただし、衝突の可能性が非常に低くなるように関数が実装されている場合は、非常に優れたパフォーマンスが得られます(これは、すべての場合で厳密にO(1)と限りませんが、ほとんどの場合)。

たとえば、Oracle JREのデフォルトの実装では、乱数を使用します(これはオブジェクトインスタンスに格納されるため、変更されません-バイアスロックも無効になりますが、これは別の議論です)衝突の可能性はとても低い。


「ほとんどの場合です」。より具体的には、Nは無限大に向かう傾向にあるため、合計時間はK倍N(Kは一定)に向かう傾向があります。
ChrisW、2009年

7
これは間違っています。ハッシュテーブルのインデックスは、hashCode % tableSize競合が発生する可能性があることを介して決定されます。32ビットを十分に活用できていません。これがハッシュテーブルの要点です。大きなインデックススペースを小さなインデックススペースに減らします。
FogleBird 09年

1
「衝突がないことが保証されています」いいえ、マップのサイズがハッシュのサイズよりも小さいためです。たとえば、マップのサイズが2の場合、衝突は保証されます(問題ではありません)どのようなハッシュ)3つの要素を挿入しようとした場合
ChrisW

しかし、どのようにしてO(1)のキーからメモリアドレスに変換しますか?x = array ["key"]のようなものです。キーはメモリアドレスではないため、O(n)ルックアップである必要があります。
paxdiablo 2009年

1
「hashCodeを実装しない場合、オブジェクトのメモリアドレスが使用されると思います。」それを使用することもできますが、標準のOracle JavaのデフォルトのhashCodeは実際にはオブジェクトヘッダーに格納された25ビットの乱数であるため、64/32ビットは重要ではありません。
Boann、2014年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.