[アルゴリズムとデータ構造09]ハッシュテーブル-効率的な検索のための強力なツール


序文:

以前、線形テーブル、配列、文​​字列、およびツリーを調査しましたが、これらにはすべて、データの数値条件の検索でデータの全部または一部をトラバースする必要があるという欠点がありますでは、データ比較のプロセスを省略して、数値条件の検索効率をさらに向上させる方法はありますか?

答えはもちろんです:はい!このレッスンでは、このような効率的な検索アーティファクトであるハッシュテーブルを紹介します。

ここに画像の説明を挿入


1つは、ハッシュテーブルとは何ですか

ハッシュテーブルの名前はHashに由来し、ハッシュテーブルと呼ぶこともできますハッシュテーブルは特別なデータ構造であり、配列、リンクリスト、ツリーなど、これまでに学習したデータ構造とは大きく異なります。

1.1ハッシュテーブルの原理

ハッシュテーブルは、ハッシュ関数を使用してデータを整理し、迅速な挿入と検索をサポートするデータ構造ですハッシュテーブルの中心的なアイデアは、ハッシュ関数使用してキーをバケットにマップすることです。すなわち:

  • 新しいキーを挿入すると、ハッシュ関数はキーを割り当てるバケットを決定し、対応するバケットにキーを格納します。
  • キーを検索する場合、ハッシュテーブルは同じハッシュ関数を使用して対応するバケットを検索し、特定のバケットでのみ検索します。

これは簡単な例です、理解しましょう:

ここに画像の説明を挿入
この例では、ハッシュ関数としてy = x%5を使用します。この例を使用して、挿入と検索の戦略を完成させましょう。

  • 挿入:ハッシュ関数を使用してキーを解析し、対応するバケットにマップします。たとえば、1987はバケット2に割り当てられ、24はバケット4に割り当てられます。
  • 検索:同じハッシュ関数を使用してキーを解析し、特定のバケットでのみ検索します。たとえば、23を検索する場合、23を3にマップし、バケット3で検索します。23がバケット3にないことがわかります。これは、23がハッシュテーブルにないことを意味します。

1.2ハッシュ関数を設計する

ハッシュ関数は、ハッシュテーブルの最も重要な要素の、ハッシュテーブルをするために使用される特定のバケットにキーをマッピングします前の例では、ハッシュ関数としてy = x%5を使用しました。ここで、xはキー値、yは割り当てられたバケットのインデックスです

ハッシュ関数は、に依存する範囲キー値バケットの数
ここに画像の説明を挿入
ハッシュ関数の例を次に示しますハッシュ関数の設計は未解決の問題です。可能な限りキーをバケットに割り当てるという考え方です。理想的には、完璧なハッシュ関数は、キーとバケット間の1対1のマッピングです。ただし、ほとんどの場合、ハッシュ関数は完全ではなく、バケットの数とバケットの容量の間のトレードオフが必要です。

もちろん、いくつかのハッシュ関数をカスタマイズすることもできます。一般的な方法は次のとおりです。

  • 直接カスタマイズ方法ハッシュ関数は、キーワードからアドレスへの線形関数です。たとえば、H(キー)= a *キー+ bです。ここで、aとbは定数に設定されています。
  • デジタル分析法キーセット内の各キーがs桁の数字(k1、k2、..。、ks)で構成され、そこからいくつかの均等に分散されたビットが抽出されてハッシュアドレスが形成されるとします。
  • 正方形は中国の方法です。キーワードのすべての桁に特定の桁が繰り返されており、頻度が非常に高い場合は、最初にキーワードの2乗値を見つけ、その2乗で差を拡大してから、中央の桁を最終的なストレージアドレスとして使用できます。
  • 折りたたみ方法キーワードの桁数が多い場合は、キーワードを複数の等しい長さの部分に分割し、それらの重ね合わせの値と(切り上げ)をハッシュアドレスとして使用できます。
  • 残りの方法に加えてあらかじめ数値pを設定してから、キーワードの残りの操作を行ってください。つまり、アドレスはkey%pです。

2つ目は、ハッシュの競合を解決する

理想的には、ハッシュ関数が完全な1対1のマッピングである場合、競合に対処する必要はありません。残念ながら、ほとんどの場合、競合はほぼ避けられません。たとえば、以前のハッシュ関数(y = x%5)では、1987と2の両方がバケット2に割り当てられています。これは、ハッシュの衝突です。

ハッシュの競合を解決するには、次の質問を検討する必要があります。

  • 同じバケット内の値を整理する方法は?
  • 同じバケットに割り当てられている値が多すぎる場合はどうなりますか?
  • 特定のバケットでターゲット値を検索するにはどうすればよいですか?

では、競合が発生したら、どのように解決しますか?

一般的に使用される方法には、オープンアドレス指定方法とチェーンアドレス指定方法の 2つがあります

2.1オープンアドレス指定方法

つまり、あるキーワードが別のキーワードと競合する場合、特定の検出技術を使用してハッシュテーブルに検出シーケンスが形成され、次に検出シーケンスが検索されます。空のセルが見つかると、そのセルに挿入されます。

一般的に使用される検出方法は、線形検出方法です。たとえば、キーワードのセット{12、13、25、23}があり、使用されるハッシュ関数はkey%11です。12、13、25を挿入する場合、直接挿入できます。アドレスはそれぞれ1、2、3です。23を挿入すると、ハッシュアドレスは23%11 = 1になります。

ただし、アドレス1はすでに占有されているため、アドレス4が検出されて空であることが判明するまで、アドレス1を順番にたどり、その後23を挿入します。以下に示すように:
ここに画像の説明を挿入

2.2チェーンアドレス方式

同じハッシュアドレスを持つレコードを線形リンクリストに保存します。たとえば、キーワードのセット{12,13,25,23,38,84,6,91,34}があり、使用されるハッシュ関数はキー%11です。以下に示すように:
ここに画像の説明を挿入

三、ハッシュテーブルの適用

3.1ハッシュテーブルの基本操作

多くの高レベル言語では、ハッシュ関数とハッシュの競合は下部でブラックボックス化されており、開発者は自分で設計する必要はありません。言い換えると、ハッシュテーブルはキーワードからアドレスへのマッピングを完了し、データは一定レベルの時間の複雑さの中でキーワードを介して見つけることができます。

どのハッシュ関数を使用するか、どの競合処理を使用するか、特定のデータレコードのハッシュアドレスなど、実装の詳細については、開発者が注意を払う必要はありません。次に、実際の開発の観点から、ハッシュテーブルによるデータの追加、削除、チェックを見てみましょう。

ハッシュテーブルのデータを追加および削除する操作は、追加または削除後にデータをシフトする問題を伴わないため(配列を考慮する必要があります)、処理は問題ありません。

ハッシュテーブルルックアップの詳細なプロセスは次のとおりです。特定のキーについて、ハッシュアドレスH(キー)はハッシュ関数を介して計算されます。

  • ハッシュアドレスに対応する値が空の場合、検索は失敗します。
  • それ以外の場合、検索は成功します。

ハッシュテーブルルックアップの詳細なプロセスは依然として面倒ですが、一部の高レベル言語のブラックボックス処理のため、開発者は実際に基礎となるコードを開発する必要はなく、関連する関数を呼び出すだけです。

3.2ハッシュテーブルの長所と短所

  • 利点:データの量に関係なく、非常に高速な挿入-削除-検索操作を提供でき、値の挿入と削除にほぼ一定の時間が必要です検索に関しては、ハッシュテーブルの速度はツリーの速度よりも速く、目的の要素を瞬時に見つけることができます。
  • 短所:ハッシュテーブルのデータに順序の概念ないため、要素を固定された方法(小さいものから大きいものへなど)でトラバースできません。データ処理の順序が敏感な場合、ハッシュテーブルを選択することは良い解決策ではありません。同時に、ハッシュテーブル内の
    キー繰り返すことは許可されておらず、ハッシュテーブルは非常に高い再現性を持つデータには適していません。

第四に、ハッシュマップを設計する

4.1設計要件

請求:

組み込みのハッシュテーブルライブラリを使用せずにハッシュマップを設計します。具体的には、設計に次の関数を含める必要があります。

  • put(key、value):(key、value)の値のペアをハッシュマップに挿入します。キーに対応する値がすでに存在する場合は、この値を更新します。
  • get(key):指定されたキーに対応する値を返します。キーがマップに含まれていない場合は、-1を返します。
  • remove(key):キーがマップに存在する場合は、値のペアを削除します。

例:

MyHashMap hashMap = new MyHashMap();
hashMap.put(1, 1);          
hashMap.put(2, 2);         
hashMap.get(1);            // 返回 1
hashMap.get(3);            // 返回 -1 (未找到)
hashMap.put(2, 1);         // 更新已有的值
hashMap.get(2);            // 返回 1 
hashMap.remove(2);         // 删除键为2的数据
hashMap.get(2);            // 返回 -1 (未找到)

注意:

 所有的值都在 [0, 1000000]的范围内。
 操作的总数目在[1, 10000]范围内。
 不要使用内建的哈希库。

4.2デザインのアイデア

ハッシュテーブルは、さまざまな言語で利用できる一般的なデータ構造です。たとえば、Pythonのdict、C ++のmap、JavaのHashmapなどです。ハッシュテーブルの特徴は、指定されたキーに応じて値にすばやくアクセスできることです。

最も簡単なアイデアは、ハッシュ方式としてモジュラー演算を使用することです。ハッシュの衝突の可能性を減らすために、通常、モジュロ2069などの素数のモジュラスが使用されます。

配列を記憶域として定義し、ハッシュ法で配列添え字を計算します。ハッシュの衝突を解決するために(つまり、キー値は異なりますが、マッピング添え字は同じです)、バケットを使用して対応するすべての値を格納します。バケットは、配列またはリンクリストとして実装できます。次の特定の実装では、Pythonで配列が使用されます。

ハッシュテーブルメソッドget()、put()、およびremove()を定義します。アドレス指定プロセスは次のとおりです。

  • 特定のキー値について、hashメソッドを使用してキー値のハッシュコード生成し、ハッシュコードを使用してストレージスペースを見つけます。ハッシュコードごとに、キー値に対応する値を格納するバケットを見つけることができます。
  • バケットを見つけたら、をトラバースしキーと値のペアがすでに存在するかどうかを確認します。

ここに画像の説明を挿入

4.3実際のケース

Pythonの実装は次のとおりです。

class Bucket:
    def __init__(self):
        self.bucket = []

    def get(self, key):
        for (k, v) in self.bucket:
            if k == key:
                return v
        return -1

    def update(self, key, value):
        found = False
        for i, kv in enumerate(self.bucket):
            if key == kv[0]:
                self.bucket[i] = (key, value)
                found = True
                break

        if not found:
            self.bucket.append((key, value))

    def remove(self, key):
        for i, kv in enumerate(self.bucket):
            if key == kv[0]:
                del self.bucket[i]


class MyHashMap(object):

    def __init__(self):
        """
        Initialize your data structure here.
        """
        # better to be a prime number, less collision
        self.key_space = 2069
        self.hash_table = [Bucket() for i in range(self.key_space)]


    def put(self, key, value):
        """
        value will always be non-negative.
        :type key: int
        :type value: int
        :rtype: None
        """
        hash_key = key % self.key_space
        self.hash_table[hash_key].update(key, value)


    def get(self, key):
        """
        Returns the value to which the specified key is mapped, or -1 if this map contains no mapping for the key
        :type key: int
        :rtype: int
        """
        hash_key = key % self.key_space
        return self.hash_table[hash_key].get(key)


    def remove(self, key):
        """
        Removes the mapping of the specified value key if this map contains a mapping for the key
        :type key: int
        :rtype: None
        """
        hash_key = key % self.key_space
        self.hash_table[hash_key].remove(key)


# Your MyHashMap object will be instantiated and called as such:
# obj = MyHashMap()
# obj.put(key,value)
# param_2 = obj.get(key)
# obj.remove(key)

複雑さの分析:

  • 時間の複雑さ:各メソッドの時間の複雑さはO(N / K)です。ここで、Nはすべての可能なキー値の数、Kはハッシュテーブル内の事前定義されたバケットの数です。ここで、Kは2069です。ここでは、キー値がすべてのバケットに均等に分散されており、バケットの平均サイズがN / Kであると想定しています。最悪の場合、バケット全体をトラバースする必要があるため、時間の複雑さはO(N / K)です。
  • スペースの複雑さ:O(K + M)、ここでKはハッシュテーブル内の事前定義されたバケットの数、Mはハッシュテーブルに挿入されたキー値の数です。

今日の共有は終わりました。あなたの研究に役立つことを願っています!

ここに画像の説明を挿入

最初のように習慣を身につけてから見てください!あなたのサポートが私の創造の最大の動機です!

おすすめ

転載: blog.csdn.net/wjinjie/article/details/108773366