JavaハッシュマップとそのO(1)
ルックアップ時間についての興味深い主張を見てきました。これがなぜそうであるか誰かが説明できますか?これらのハッシュマップが、購入したハッシュアルゴリズムと大きく異なる場合を除き、衝突を含むデータセットが常に存在している必要があります。
その場合、ルックアップはでO(n)
はなくになりO(1)
ます。
誰かがそれらが O(1)であるかどうかを説明できますか?
JavaハッシュマップとそのO(1)
ルックアップ時間についての興味深い主張を見てきました。これがなぜそうであるか誰かが説明できますか?これらのハッシュマップが、購入したハッシュアルゴリズムと大きく異なる場合を除き、衝突を含むデータセットが常に存在している必要があります。
その場合、ルックアップはでO(n)
はなくになりO(1)
ます。
誰かがそれらが O(1)であるかどうかを説明できますか?
回答:
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)アクセスを持っていると言います。
ワーストケースの動作と平均ケース(予想)ランタイムを混同しているようです。前者は確かに一般にハッシュテーブルのO(n)です(つまり、完全なハッシュを使用していません)が、実際にはほとんど関係ありません。
信頼できるハッシュテーブルの実装は、ハーフディセントハッシュと相まって、非常に狭い分散範囲内で、予想されるケースでは非常に小さい係数(実際は2)のO(1)の検索パフォーマンスを持っています。
Javaでは、HashMapはhashCodeを使用してバケットを検索することで機能します。各バケットは、そのバケット内にあるアイテムのリストです。比較のために等しいを使用して、アイテムがスキャンされます。アイテムを追加するとき、特定のロードパーセンテージに達すると、HashMapのサイズが変更されます。
したがって、いくつかの項目と比較する必要がある場合もありますが、一般的にはO(n)よりもO(1)にはるかに近くなります。実用的な目的のために、あなたが知る必要があるのはそれだけです。
o(1)は、各ルックアップが単一の項目のみを検査することを意味しないことに注意してください。つまり、チェックされる項目の平均数は、コンテナー内の項目の数に対して一定のままです。したがって、100アイテムのコンテナーでアイテムを見つけるために平均4つの比較が必要な場合、10000アイテムのコンテナーでアイテムを見つけるために、平均4つの比較を行う必要があります。特に、ハッシュテーブルが再ハッシュされるポイントの周り、およびアイテムの数が非常に少ないときの差異)。
そのため、バケットごとのキーの平均数が一定の範囲内にある限り、衝突によってコンテナがo(1)オペレーションを持つことを妨げることはありません。
バケットの数(bと呼びます)が一定に保たれている場合(通常の場合)、ルックアップは実際にはO(n)です。
nが大きくなると、各バケットの要素数は平均してn / bになります。衝突解決が通常の方法(リンクされたリストなど)のいずれかで行われる場合、ルックアップはO(n / b)= O(n)になります。
O表記は、nがどんどん大きくなるとどうなるかを表しています。特定のアルゴリズムに適用すると誤解を招く可能性があり、ハッシュテーブルがその好例です。処理する要素の数に基づいてバケットの数を選択します。nがbとほぼ同じサイズの場合、ルックアップはほぼ一定時間ですが、Oはnから∞の制限で定義されているため、O(1)と呼ぶことはできません。
O(1+n/k)
どこk
バケットの数です。
実装が設定されている場合k = n/alpha
、それは定数であるO(1+alpha) = O(1)
ためalpha
です。
ハッシュテーブルルックアップの標準的な説明がO(1)であることは、厳密な最悪の場合のパフォーマンスではなく、平均ケースの予想時間を参照することを確立しました。チェーンとの衝突を解決するハッシュテーブル(Javaのハッシュマップなど)の場合、これは技術的には良いハッシュ関数を持つO(1 +α)です。ここで、αはテーブルの負荷係数です。格納するオブジェクトの数がテーブルサイズよりも大きい一定の因数である限り、依然として一定です。
厳密に言えば、確定的ハッシュ関数に対してO(n)ルックアップを必要とする入力を構築することが可能であることも説明されています。しかし、平均検索時間とは異なる最悪の場合の予想時間を考慮することも興味深いです。チェーンを使用すると、これはO(1 +最長チェーンの長さ)になります。たとえば、α= 1の場合はΘ(log n / log log n)です。
ワーストケースのルックアップを一定時間予測する理論的な方法に興味がある場合は、別のハッシュテーブルとの衝突を再帰的に解決する動的完全ハッシュについて読むことができます。
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回のルックアップを実行するだけでよいため、実行時間はO(1)です。衝突が存在する場合、複数のルックアップを実行する必要があります。これにより、パフォーマンスがO(n)に向かって低下します。
アカデミックは別として、実用的な観点からは、HashMapはパフォーマンスに取るに足らない影響を与えるものとして受け入れられるべきです(プロファイラーが他に指示しない限り)。
もちろん、ハッシュマップのパフォーマンスは、与えられたオブジェクトのhashCode()関数の品質に依存します。ただし、衝突の可能性が非常に低くなるように関数が実装されている場合は、非常に優れたパフォーマンスが得られます(これは、すべての場合で厳密にO(1)とは限りませんが、ほとんどの場合)。
たとえば、Oracle JREのデフォルトの実装では、乱数を使用します(これはオブジェクトインスタンスに格納されるため、変更されません-バイアスロックも無効になりますが、これは別の議論です)衝突の可能性はとても低い。
hashCode % tableSize
競合が発生する可能性があることを介して決定されます。32ビットを十分に活用できていません。これがハッシュテーブルの要点です。大きなインデックススペースを小さなインデックススペースに減らします。