Menu
Coddy logo textTech

Hash Map

最終更新

ハッシュマップはキーと値のペアを格納し、キーによって値を平均 O(1) で検索できます。ハッシュテーブルとまったく同じように動作しますが、各バケットのエントリはキーとそれに対応する値の両方を保持します。ペアを格納または取得するには、キーをバケットのインデックスにハッシュし、そのバケットのチェーンを走査して一致するキーを探します。上の再生を押すと、ペアがハッシュされたバケットに配置され、値がキーで取得される様子を見られます。

これは Python の dict、Java の HashMap、JavaScript の Map/オブジェクトの背後にある構造です。衝突はハッシュテーブルと同じ方法で処理され、ここでは分離連鎖法で扱われます。そのため性能は、良いハッシュ関数と低い負荷率に依存します。

時間計算量

操作平均最悪の場合
挿入/更新 (put)O(1)O(n)
検索 (get)O(1)O(n)
削除O(1)O(n)
空間O(n)O(n)

主要言語でのハッシュマップ

言語
Pythondict
JavaHashMap
JavaScriptMap / オブジェクト
C++std::unordered_map
Gomap

具体例

hash(key) % 8 を使って、ペア ("cat", 3)("dog", 5)("cat", 9)("emu", 7)8 個のバケットを持つマップに挿入します。hash("cat") % 8 = 2hash("dog") % 8 = 5hash("emu") % 8 = 2 と仮定します:

ステップ構造動作
Put ("cat", 3)バケット 2: [("cat", 3)]バケット 2 にハッシュ。チェーンが空なのでペアを追加。
Put ("dog", 5)バケット 2: [("cat", 3)], バケット 5: [("dog", 5)]バケット 5 にハッシュ。チェーンが空なのでペアを追加。
Put ("cat", 9)バケット 2: [("cat", 9)], バケット 5: [("dog", 5)]バケット 2 にハッシュ。キー "cat" は既にチェーン内にあるので、値を 9 に更新。
Put ("emu", 7)バケット 2: [("cat", 9), ("emu", 7)], バケット 5: [("dog", 5)]バケット 2 にハッシュ。"cat" と衝突するがキーは見つからないので、ペアを追加。
Get "emu"バケット 2: [("cat", 9), ("emu", 7)]バケット 2 にハッシュ。チェーンを走査し、"cat" をスキップして "emu" に一致、7 を返す。

ハッシュマップを使うべきとき

使うべきとき避けるべきとき
正確なキーによる平均 O(1) の検索・挿入・削除が必要なとき。キーをソート順に保つ、または範囲クエリが必要なとき — 平衡BSTやソート済みマップを使う。
キーがハッシュ可能で等価性が明確に定義されているとき(文字列、整数、タプル)。キーが可変で挿入後に変わり得るとき。そのバケットが壊れる。
識別子でカウント、重複排除、キャッシュ、インデックス付けをしているとき。償却された平均計算量ではなく、最悪の場合 O(log n) の保証が必要なとき。
反復順序が重要でない、または挿入順マップで十分なとき。メモリが非常に限られているとき — バケット、負荷率の余裕、ポインタがオーバーヘッドを加える。

Hash Mapのコード

Python, JavaScript, Java, C++, Cによるクリーンで実行可能なHash Mapの実装です。言語を選んでコードをコピーするか、Coddyプレイグラウンドに読み込んだ状態で開けます。

PythonでのHash Mapのコード

Python
1# Python's built-in dict is a hash map: O(1) average2# insert, lookup, and delete.3inventory = {"apple": 3, "banana": 7}4
5# Insert and update6inventory["cherry"] = 57inventory["apple"] += 28
9# Lookup, with .get for a safe default on missing keys10print("apple: ", inventory["apple"])11print("mango: ", inventory.get("mango", 0))12
13# Membership test and delete14print("banana in stock:", "banana" in inventory)15del inventory["banana"]16print("banana in stock:", "banana" in inventory)17
18# Iterate over key-value pairs19for fruit, count in sorted(inventory.items()):20    print(f"{fruit}: {count}")21
22# Classic hash map use case: counting frequencies23words = "the quick brown fox jumps over the lazy dog the end".split()24freq = {}25for word in words:26    freq[word] = freq.get(word, 0) + 127
28print("Occurrences of the:", freq["the"])29print("Most common word:  ", max(freq, key=freq.get))
このコードをPythonプレイグラウンドで実行

ハッシュマップのよくある質問

ハッシュマップとハッシュテーブルの違いは何ですか?
本質的には同じ構造で、キーのハッシュでアドレス指定されるバケットの配列です。一般的な用法では「ハッシュテーブル」はキーの集合や一般的な手法を指すことが多く、「ハッシュマップ」はキーと値のペアの格納を強調します。一部の言語ではスレッド安全性の区別もあります(例:Java の Hashtable と HashMap)が、中心となるアルゴリズムは同一です。
ハッシュマップの時間計算量はどのくらいですか?
put、get、delete は、良いハッシュ関数と低い負荷率のもとで平均 O(1) です。すべてのキーが1つのバケットに衝突する病的なケースでは O(n) に劣化するため、リサイズと良いハッシュが重要になります。
ハッシュマップは同じバケットにハッシュされる2つのキーをどう扱いますか?
衝突を解決します。この可視化は分離連鎖法を使います。各バケットは小さなリストを保持し、キーがそこにハッシュされる新しいペアはそのリストに追加されます。検索時、マップは短いチェーンを走査して一致するキーを探します。代替手法はオープンアドレス法で、配列内の別のスロットを探索します。
ハッシュマップと二分探索木のどちらを使うべきですか?
正確なキーによる検索だけが必要で、平均 O(1) の操作を求める場合はハッシュマップを使います。ソートされたキー、範囲クエリ、前者/後者の検索が必要な場合は、平衡二分探索木(TreeMapstd::map など)を使い、これらは O(log n) です。BST は、ハッシュマップが提供できない順序保証と引き換えに少し速度を犠牲にします。
負荷率とは何で、なぜリサイズを引き起こすのですか?
負荷率は、格納されているエントリ数とバケット数の比です。これが上がるとチェーンが長くなり、平均検索が遅くなるため、ほとんどの実装は 0.75 のようなしきい値を超えるとリサイズします(通常はバケットを倍にしてすべてを再ハッシュ)。リサイズは O(n) ですが稀なので償却され、平均操作を O(1) に保ちます。
リストのような可変オブジェクトをハッシュマップのキーに使えますか?
通常は使えません。キーはハッシュ可能である必要があり、マップに入っている間そのハッシュは一定でなければなりません。例えば Python は list に対して TypeError: unhashable type を送出します。挿入後にキーを変更するとハッシュが変わり、マップは正しいバケットでそれを見つけられなくなり、静かにエントリを失います。文字列、数値、タプルのような不変のキーを使いましょう。
Coddy programming languages illustration

Coddy でアルゴリズムをマスターしよう

始める