| ハッシュテーブル | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| タイプ | 順序なし連想配列 | |||||||||||||||||||||||
| 発明した | 1953 | |||||||||||||||||||||||
| ||||||||||||||||||||||||

コンピュータサイエンスでは、ハッシュテーブルは連想配列(辞書または単にマップとも呼ばれる)を実装するデータ構造です。連想配列は、キーを値にマッピングする抽象データ型です。[ 3 ]ハッシュテーブルはハッシュ関数を使用して、バケットまたはスロットの配列へのインデックス(ハッシュコードとも呼ばれる)を計算し、そこから目的の値を見つけることができます。検索中、キーがハッシュ化され、結果として得られるハッシュは対応する値が格納されている場所を示します。ハッシュテーブルによって実装されるマップはハッシュマップと呼ばれます。
ほとんどのハッシュテーブル設計では、不完全なハッシュ関数が使用されています。そのため、ハッシュ関数が複数のキーに対して同じインデックスを生成するハッシュ衝突は、通常何らかの方法で対処する必要があります。ハッシュ衝突を処理する一般的な戦略には、リンクリストを使用して複数の要素を同じスロットに格納するチェイニングと、プロービングシーケンスに従って次に使用可能なスロットを検索するオープンアドレッシングがあります。[ 4 ]
適切な次元のハッシュテーブルでは、各ルックアップの平均時間計算量は、テーブルに格納されている要素の数に依存しません。多くのハッシュテーブル設計では、キーと値のペアの任意の挿入と削除も可能で、操作あたりの平均コストは償却定数です。 [ 5 ] [ 4 ] : 513–558 [ 6 ]
ハッシュ化は、空間と時間のトレードオフの一例である。メモリが無限であれば、キー全体をインデックスとして直接使用して、1回のメモリアクセスで値を見つけることができる。一方、時間が無限であれば、キーを考慮せずに値を格納し、二分探索または線形探索を使用して要素を取得できる。[ 7 ]: 458
多くの状況において、ハッシュテーブルは平均的に検索木や他のテーブル検索構造よりも効率的であることがわかります。そのため、ハッシュテーブルは、連想配列、データベースのインデックス、キャッシュ、セットなど、多くの種類のコンピュータソフトウェアで広く使用されています。[ 8 ]多くのプログラミング言語には、 Python の辞書、Java の HashMap、C++ の unordered_map、Go のマップなど、ハッシュの複雑さをプログラマから抽象化する組み込みのハッシュテーブル構造が用意されています。[ 9 ]
ハッシュ化の概念は、異なる場所で独立して生まれました。1953年1月、ハンス・ペーター・ルーンは、ハッシュ化と連鎖を組み合わせたIBM社内メモを作成しました。オープンアドレッシングの最初の例は、ルーンのメモを基にAD・リンによって提案されました。[ 4 ] : 547ほぼ同時期に、IBMリサーチのジーン・アムダール、エレイン・M・マクグロー、ナサニエル・ロチェスター、アーサー・サミュエルは、 IBM 701アセンブラにハッシュ化を実装しました。[ 10 ] : 124線形プロービングによるオープンアドレッシングはアムダールによるものとされていますが、アンドレイ・エルショフも独立して同じアイデアを持っていました。[ 10 ] : 124–125 「オープンアドレッシング」という用語は、 W・ウェズリー・ピーターソンが大規模ファイルにおける検索の問題を論じた記事の中で造語しました。 [ 11 ] : 15
連鎖を用いたハッシュに関する最初の発表論文は、素数を法とする剰余をハッシュ関数として用いるというアイデアを論じたアーノルド・デュミーによるものとされている。 [ 11 ] : 15「ハッシュ」という言葉は、ロバート・モリスによる論文で初めて発表された。[ 10 ] : 126線形プロービングの理論的分析は、もともとコンハイムとワイスによって提出された。[ 11 ] : 15
連想配列は、 (キー、値) ペアのセットを格納し、一意のキーという制約の下で挿入、削除、およびルックアップ (検索) を可能にします。連想配列のハッシュテーブルの実装では、配列は長さ部分的に満たされている要素、鍵となるものハッシュ関数を使用してハッシュ化されますインデックス位置を計算するハッシュテーブルでは、ハッシュテーブルの効率は、格納されている要素数と利用可能なスロット数の比率として定義される負荷係数に依存し、負荷係数が低いほど一般的に処理速度が速くなります。[ 12 ]このインデックスには、キーとその関連値の両方が格納されます。キーを値と一緒に格納することで、衝突が発生した場合でも、ルックアップ時にインデックスでキーを検証して正しい値を取得できます。妥当な仮定の下では、ハッシュテーブルは、自己平衡二分探索木と比較して、検索、削除、挿入操作の時間計算量の上限が優れています。[ 11 ] : 1
ハッシュテーブルは、各キーの格納値を省略し、キーが存在するかどうかのみを追跡することで、セットを実装するためにもよく使用されます。[ 11 ] : 1
負荷係数はハッシュテーブルの重要な統計量であり、次のように定義されます。[ 2 ] どこ
ハッシュテーブルのパフォーマンスは、負荷係数に応じて低下する。[ 11 ]: 2 大きな極限ではそして各バケットは統計的に期待値を持つポアソン分布に従います。理想的なランダムハッシュ関数の場合。
このソフトウェアは通常、負荷係数がある定数以下に留まり、これにより、良好なパフォーマンスが維持されます。そのため、負荷係数が一定以上になるとハッシュテーブルのサイズを変更したり、「再ハッシュ」したりするのが一般的なアプローチです。到達する同様に、負荷率が一定値以下になると、テーブルのサイズも変更される可能性があります。[ 13 ]
分離連鎖ハッシュテーブルでは、バケット配列の各スロットにデータのリストまたは配列へのポインタが格納されます。[ 14 ]
分離連鎖ハッシュテーブルは、負荷係数が増加するにつれて徐々にパフォーマンスが低下し、サイズ変更が絶対に必要となる固定点はありません。[ 13 ]
別個の連鎖では、最高のパフォーマンスを発揮する値は通常1から3の間である。[ 13 ]
オープンアドレス方式では、バケット配列の各スロットには正確に 1 つのアイテムが格納されます。したがって、オープンアドレス方式のハッシュテーブルの負荷係数は 1 を超えることはできません。[ 14 ]
負荷率が1に近づくと、オープンアドレッシングのパフォーマンスが非常に悪くなります。[ 13 ]
したがって、オープンアドレッシングを使用するハッシュテーブルは、負荷係数が増加するとサイズ変更または再ハッシュする必要があります。アプローチ1. [ 13 ]
オープンアドレッシングの場合、最大負荷係数の許容値は0.6~0.75の範囲であるべきである。[ 15 ] [ 16 ]: 110
ハッシュ関数宇宙を地図化するテーブル内のインデックスまたはスロットへのキー、つまり、のためにハッシュ関数の従来の実装は、テーブルのすべての要素が整数ユニバースから派生するという整数ユニバースの仮定に基づいています。ビット長はコンピュータアーキテクチャのワードサイズ内に収まる。[ 11 ]: 2
ハッシュ関数特定のセットに最適だと言われているもしそれが単射であればつまり、各要素が異なる値にマッピングされます[ 17 ] [ 18 ]すべてのキーが事前にわかっている場合は、完全なハッシュ関数を作成できます。[ 17 ]
整数宇宙仮定で使用されるハッシュ方式には、除算によるハッシュ、乗算によるハッシュ、ユニバーサルハッシュ、動的完全ハッシュ、静的完全ハッシュなどがあります。[ 11 ] : 2ただし、除算によるハッシュが一般的に使用されている方式です。[ 19 ] : 264 [ 16 ] : 110
除算によるハッシュ化の方式は次のとおりです。[ 11 ] : 2 どこはハッシュ値ですそしてテーブルのサイズです。
乗算によるハッシュ化の方式は以下のとおりです。[ 11 ]: 2-3 どこは非整数実数値定数であり、テーブルのサイズです。乗算によるハッシュ化の利点は、重要ではない。[ 11 ]: 2-3どんな値でもハッシュ関数を生成する際、ドナルド・クヌースは黄金比の使用を提案している。[ 11 ] : 3
一般的に、ハッシュ関数のキーとして文字列が使用されます。C ++ プログラミング言語の第 3 版では、初期値がゼロの符号なし整数を繰り返し 1 ビット左シフトし、次の文字の整数値と XOR 演算する単純なハッシュ関数が説明されています。このハッシュ値は、テーブルのサイズで剰余演算されます。[ 20 ]左シフトが循環的でない場合、文字列の長さは、符号なし整数のビットサイズより少なくとも 8 ビット小さくする必要があります。文字列を整数にハッシュ化するもう 1 つの一般的な方法は、多項式ローリングハッシュ関数を使用することです。
ハッシュ値の均一分布はハッシュ関数の基本的な要件です。不均一な分布は衝突の数と解決コストを増加させます。均一性は設計上保証するのが難しい場合もありますが、離散的な均一分布に対するピアソンのカイ二乗検定などの統計的検定を使用して経験的に評価できます。 [ 21 ] [ 22 ]
分布は、アプリケーションで発生するテーブルサイズに対してのみ均一である必要があります。特に、テーブルサイズを正確に2倍および半分にする動的サイズ変更を使用する場合、ハッシュ関数はサイズが2のべき乗の場合にのみ均一である必要があります。この場合、インデックスはハッシュ関数のビット範囲として計算できます。一方、一部のハッシュアルゴリズムは、サイズが素数であることを好みます。[ 23 ]
オープンアドレス方式の場合、ハッシュ関数は連続するスロットに 2 つ以上のキーをマッピングするランも回避する必要があります。このようなランは、負荷係数が低く衝突がまれであっても、検索コストを急上昇させる可能性があります。一般的な乗法ハッシュは、特にランの挙動が悪いとされています。[ 23 ] [ 4 ]
K独立ハッシュ法は、特定のハッシュ関数が、特定のタイプのハッシュテーブルに対して悪いキーセットを持たないことを証明する方法を提供する。線形プロービングやカッコウハッシュなどの衝突解決方式については、K独立性に関する多くの結果が知られている。K独立性によってハッシュ関数が機能することが証明できるため、可能な限り最速のハッシュ関数を見つけることに集中できる。[ 24 ]
A search algorithm that uses hashing consists of two parts. The first part is computing a hash function which transforms the search key into an array index. The ideal case is such that no two search keys hash to the same array index. However, this is not always the case and impossible to guarantee for unseen given data.[4]:515 Hence the second part of the algorithm is collision resolution. The two common methods for collision resolution are separate chaining and open addressing.[7]:458


In separate chaining, the process involves building a linked list with key–value pair for each search array index. The collided items are chained together through a single linked list, which can be traversed to access the item with a unique search key.[7]:464 Collision resolution through chaining with linked list is a common method of implementation of hash tables. Let and be the hash table and the node respectively, the operation involves as follows:[19]:258
Chained-Hash-Insert(T, k) insertxat the head of linked listT[h(k)] Chained-Hash-Search(T, k) search for an element with keykin linked listT[h(k)] Chained-Hash-Delete(T, k) deletexfrom the linked listT[h(k)]
If the element is comparable either numerically or lexically, and inserted into the list by maintaining the total order, it results in faster termination of the unsuccessful searches.[4]:520–521
If the keys are ordered, it could be efficient to use "self-organizing" concepts such as using a self-balancing binary search tree, through which the theoretical worst case could be brought down to , although it introduces additional complexities.[4]:521
動的完全ハッシュでは、ルックアップの複雑さを軽減するために2レベルのハッシュテーブルが使用され、最悪の場合。この手法では、エントリは完全なハッシュテーブルとして整理され、スロットは、最悪ケースの検索時間を一定に保ち、挿入の償却時間を短縮します。[ 25 ]ある研究では、配列ベースの分離連鎖は、高負荷時において標準的なリンクリスト方式と比較して97%高いパフォーマンスを発揮することが示されています。[ 26 ]: 99
各バケットに融合ツリーを使用するなどの手法も、高い確率で全ての操作を一定時間で実行できる結果をもたらします。[ 27 ]
分離連鎖実装のリンクリストは、リンクリストのノードがメモリ全体に分散している場合、空間的局所性(参照の局所性)のためにキャッシュを意識しない可能性があり、挿入および検索時のリスト走査はCPUキャッシュの非効率性を招く可能性がある。[ 26 ]: 91
分離連鎖による衝突解決のキャッシュ意識の高いバリアントでは、通常リンクリストや自己平衡二分探索木が使用される場所に、よりキャッシュフレンドリーであることがわかっている動的配列が使用されます。これは、配列の連続割り当てパターンが、変換ルックアサイドバッファなどのハードウェアキャッシュプリフェッチャによって利用され、アクセス時間とメモリ消費が削減されるためです。[ 28 ] [ 29 ] [ 30 ]

オープンアドレッシングは、すべてのエントリレコードがバケット配列自体に格納され、ハッシュ解決がプロービングによって実行される別の衝突解決手法です。新しいエントリを挿入する必要がある場合、ハッシュ先のスロットから始めて、空いているスロットが見つかるまで、あるプローブシーケンスに従ってバケットが調べられます。エントリを検索する場合、ターゲットレコードが見つかるか、未使用の配列スロットが見つかるまで、同じシーケンスでバケットがスキャンされます。未使用の配列スロットが見つかると、検索が失敗したことを示します。[ 31 ]
よく知られているプローブ配列には以下が含まれる。
オープンアドレッシングのパフォーマンスは、負荷係数が増加するとプローブシーケンスが増加するため、分離チェイニングと比較して遅くなる可能性がある。1に近づく。[ 13 ] [ 26 ] : 93完全に満たされたテーブルの場合、負荷係数が1に達すると、プロービングは無限ループになる。 [ 7 ] : 471線形プロービングの平均コストは、ハッシュ関数がテーブル全体に要素を均等に分散させてランを回避する能力に依存する。ランが発生すると検索時間が増加するためである。[ 7 ] : 472
スロットは連続した位置に配置されているため、線形プロービングは参照の局所性によりCPUキャッシュの利用効率を高め、メモリレイテンシを低減する可能性がある。[ 32 ]
結合ハッシュは、テーブル内でバケットまたはノードがリンクされる、分離連鎖とオープンアドレッシングの両方のハイブリッドです。[ 34 ] : 6-8このアルゴリズムは、固定メモリ割り当てに最適です。[ 34 ] : 4結合ハッシュにおける衝突は、ハッシュテーブル上でインデックスが最大の空きスロットを特定し、衝突した値をそのスロットに挿入することによって解決されます。バケットは、衝突したハッシュアドレスを含む挿入されたノードのスロットにもリンクされます。[ 34 ] : 8
カッコウハッシュは、オープンアドレス衝突解決技術の一種であり、最悪の場合の検索複雑度と挿入の償却時間が定数となる。衝突は、それぞれ独自のハッシュ関数を持つ2つのハッシュテーブルを維持することによって解決され、衝突したスロットは指定された項目に置き換えられ、そのスロットの占有要素はもう一方のハッシュテーブルに移動される。このプロセスは、すべてのキーがテーブルの空のバケットに独自の場所を持つまで続く。手順が無限ループに入った場合(これはしきい値ループカウンタを維持することによって識別される)、両方のハッシュテーブルが新しいハッシュ関数で再ハッシュされ、手順が続行される。[ 35 ]: 124-125
ホップスコッチハッシュは、オープンアドレスベースのアルゴリズムであり、バケットの近傍(占有されているバケットの周囲にある後続のバケット、いわゆる「仮想」バケット)の概念を通して、カッコウハッシュ、線形プロービング、およびチェイニングの要素を組み合わせたものです。 [ 36 ] : 351-352このアルゴリズムは、ハッシュテーブルの負荷率が90%を超えた場合に優れたパフォーマンスを発揮するように設計されています。また、並行処理環境で高いスループットを提供するため、サイズ変更可能な並行ハッシュテーブルの実装に適しています。[ 36 ] : 350ホップスコッチハッシュの近傍特性により、近傍内の任意のバケットから目的のアイテムを見つけるコストは、バケット自体でそれを見つけるコストと非常に近いことが保証されます。このアルゴリズムは、アイテムをその近傍に移動させようと試みますが、他のアイテムを移動させるコストが発生する可能性があります。[ 36 ] : 352
ハッシュテーブル内の各バケットには、追加の「ホップ情報」が含まれています。これは、最初に現在の仮想バケットにハッシュされた項目が、H − 1 エントリ以内の相対距離を示すH ビットの配列です。[ 36 ] : 352 そしてそれぞれ挿入されるキーとキーがハッシュされるバケットとする。挿入手順にはいくつかのケースがあり、アルゴリズムの近傍特性が保証される。[ 36 ]: 352-353が空の場合、要素が挿入され、ビットマップの最左端のビットが1 に設定されます。空でない場合、テーブル内の空きスロットを見つけるために線形プロービングが使用され、バケットのビットマップが更新され、その後挿入が行われます。空きスロットが近傍の範囲内、つまりH − 1 にない場合、各バケットのその後のスワップとホップ情報ビット配列操作は、その近傍不変特性 に従って実行されます。[ 36 ] : 353
ロビンフッドハッシュは、オープンアドレスベースの衝突解決アルゴリズムです。衝突は、アイテムがハッシュされたバケット、つまり「ホームロケーション」から最も遠い要素、つまり最も長いプローブシーケンス長(PSL)の要素を優先することで解決されます。 [ 37 ] : 12これは、金持ちから盗んで貧しい人々に分け与えた伝説の英雄的なアウトロー、ロビンフッドにちなんで名付けられました。
ロビンフッドハッシュは理論上の検索コストを変更しませんが、バケット上のアイテムの分布の分散に大きな影響を与えます[ 38 ]: 2 、つまりハッシュテーブルにおけるロングランの形成に対処します[ 39 ] 。ロビンフッドハッシュを使用するハッシュテーブル内の各ノードは、追加のPSL値を格納するように拡張する必要があります[ 40 ] 。挿入される鍵となる。(増分)PSLの長さは、ハッシュテーブルとインデックスの場合、挿入手順は次のとおりです。[ 37 ]: 12-13 [ 41 ]: 5
繰り返し挿入するとハッシュテーブルのエントリ数が増加し、結果として負荷係数が増加します。償却を維持するために検索および挿入操作のパフォーマンス、ハッシュ テーブルは動的にサイズ変更され、テーブルの項目は新しいハッシュ テーブルのバケットに再ハッシュされます。 [ 13 ]テーブルのサイズが変化すると剰余演算によりハッシュ値が異なるため、項目をコピーすることはできません。[ 42 ]ハッシュ テーブルが一部の要素を削除した後に「空になりすぎた」場合は、過剰なメモリ使用を避けるためにサイズ変更が実行される場合があります。[ 43 ]
一般的に、元のハッシュテーブルの2倍のサイズの新しいハッシュテーブルがプライベートに割り当てられ、元のハッシュテーブルのすべての項目は、項目のハッシュ値を計算して挿入操作を行うことで、新しく割り当てられたハッシュテーブルに移動されます。リハッシュは単純ですが、計算コストが高くなります。[ 44 ]: 478-479
ハッシュテーブルの実装、特にリアルタイムシステムでは、ハッシュテーブルを一度に拡張すると時間制約のある処理が中断される可能性があるため、そのコストを負担できない場合があります。動的なサイズ変更を避けられない場合は、リハッシュ中にストレージの一時的な増加(通常は新しいテーブルサイズの50%)を回避し、古いハッシュテーブルによって発生した大きなメモリブロックの解放によるヒープ圧縮を引き起こすメモリ断片化を回避するために、サイズ変更を段階的に実行するのが解決策となります。 [ 45 ]: 2-3このような場合、リハッシュ操作は、古いハッシュテーブルに割り当てられた以前のメモリブロックを拡張することによって段階的に実行され、ハッシュテーブルのバケットは変更されません。償却リハッシュの一般的なアプローチは、2つのハッシュ関数を維持することです。そしてバケットのアイテムを新しいハッシュ関数に従って再ハッシュするプロセスはクリーニングと呼ばれ、コマンドパターンを使用して次のような操作をカプセル化することで実装されます。、そしてを通じてバケット内の各要素が再ハッシュされるようなラッパーで、その手順は次のようになります。 [ 45 ] : 3
線形ハッシュはハッシュテーブルの実装であり、一度に1つのバケットずつテーブルを動的に拡張または縮小することを可能にする。[ 46 ]
ハッシュテーブルのパフォーマンスは、ハッシュ関数が準乱数を生成する能力に依存します(ハッシュテーブルのエントリの場合、、そしては、キー、バケット数、ハッシュ関数を表し、ハッシュ関数が同じ値を生成する場合異なるキーの場合() これにより衝突が発生し、さまざまな方法で対処されます。定数時間計算量 (ハッシュテーブルにおける演算の分散性は、ハッシュ関数が衝突するインデックスを生成しないという条件を前提としています。したがって、ハッシュテーブルのパフォーマンスは、選択されたハッシュ関数のインデックス分散能力に直接比例します。 [ 47 ] : 1しかし、そのようなハッシュ関数を構築することは実際には不可能であるため、実装では、より高いパフォーマンスを実現するために、ケース固有の衝突解決技術に依存しています。[ 47 ] : 2
ハッシュ関数がユニバースの要素を均一に分散し、テーブルに格納される要素がユニバースからランダムに抽出される場合に、最高のパフォーマンスが得られます。この場合、連鎖ハッシュでは、検索が成功するまでの期待時間は検索が失敗した場合の予想時間は[ 48 ]
ハッシュテーブルは、多くの種類のインメモリテーブルを実装するためによく使用されます。連想配列を実装するために使用されます。[ 33 ]
ハッシュテーブルは、ディスクベースのデータ構造やデータベースのインデックス( dbmなど)としても使用できますが、これらのアプリケーションではBツリーの方が一般的です。[ 49 ]
ハッシュテーブルは、主に低速なメディアに保存されているデータへのアクセスを高速化するために使用される補助データテーブルであるキャッシュを実装するために使用できます。このアプリケーションでは、ハッシュ衝突は、衝突している 2 つのエントリのうちの 1 つを破棄することによって処理できます。通常は、テーブルに現在保存されている古い項目を消去し、新しい項目で上書きすることで、テーブル内のすべての項目が一意のハッシュ値を持つようになります。[ 50 ] [ 51 ]
ハッシュテーブルは、特定の順序なしに一意の値を格納できるセットデータ構造の実装に使用できます。セットは通常、要素の取得ではなく、コレクション内の値のメンバーシップをテストするために使用されます。[ 52 ]
多くのプログラミング言語は、組み込みの連想配列として、あるいは標準ライブラリモジュールとして、ハッシュテーブル機能を提供している。
Map、任意の値をキーとして受け入れるデータ構造も追加されました。[ 55 ]unordered_mapの標準ライブラリには、任意の型のキーと値を格納するための機能が含まれています。[ 56 ]mapの形式でマップ型を実装しており、これは多くの場合 (ただし保証されているわけではない) ハッシュテーブルです。[ 57 ]HashSet、、、、および汎用HashMapコレクションが含まれています。[ 58 ]LinkedHashSetLinkedHashMapdictの形式でハッシュ テーブルを実装しています。[ 59 ]HashRuby 2.4 以降、オープンアドレッシングモデルを使用しています。[ 60 ]HashMap、HashSetRust標準ライブラリの一部としてが含まれています。[ 61 ]HashSet、Dictionary[ 62 ] [ 63 ] C #やVB.NETなどの言語から使用できます。[ 64 ]{{cite web}}: CS1 maint: bot: 元の URL の状態が不明です (リンク)