データ構造シリーズ:ハッシュ表


導入



フォークを使ってパスタを食べたり、スプーンにスープを食べたり、箸を食べたりします.各々のsilverwaresはその利点/欠点を持っています、したがって、それがそれがよく相互作用する食物のためにもう一方よりよく働きます.そのように、別のデータ構造は、より適しており、状況/ユースケースに基づいて他よりも良い実行します.彼らはそれぞれ賛否両論を持っている.それはあなたが状況/目標に基づいて適切なデータ構造を選択できるようになるように、これらの長所と短所を理解することは、より良いプログラマに役立つことができます、それは大幅に適用されているアルゴリズムのパフォーマンスを向上させるのに役立ちます.私はJavaScriptでよく知られているプログラミングデータ構造にこれらのブログシリーズを入れていて、将来の1つのブログ柱でそれらをすべてリンクします.あなたが質問をするならば、コメントを残してください!

目次


1 .What Is Hash Table?
2 .Hash Function
3 .Implementation in JavaScript
4 .Helper Functions
5 .Handling Collisions
6 .Big O
7.Helpful Resources

1ハッシュテーブルとは

Hash Table is a data structure of associative array that stores key/value paired data into buckets.

Considered being one of the most important data structures in computing, Hash Table is used in many areas of applications: password verifications, cryptography, compilers, and the list goes on. Due to its efficiency and speed in searching, insertion, and removal of data, it is a widely applicable and preferred data structure in many cases. A Hash Table is a data structure of associative array that stores data as a key/value pair into a bucket.


ハッシュテーブルがどのように働くかは、それが入力としてキーと値をとるということです、そして、インデックスにそれを回すハッシュ関数でキーを実行します.このプロセスをハッシングと呼ぶ.インデックスは、テーブルのバケツに入力の値をマップするために使用されます.ハッシュ関数は不可逆です.そして、それはそれを安全で信頼できるようにします.しかし、2つの異なるキーが同じインデックスになる可能性があり、これは衝突と呼ばれます.衝突した場合、前のキーのプレースホルダをオーバーライドできます.ハッシュ衝突を扱う様々な方法があります--別々の連鎖は、しばしば同じインデックスに複数のデータを保存するためにバケツの中でリンクリストを使用するそれらのうちの1つである.あとでこのポストに入ります.しかし最初に、ハッシュ関数がどのように動作するかを議論しましょう.

2 .ハッシュ関数

The hashing process in computing is like hashing a potato to make hashed brown.


ハッシュ関数、ハッシュアルゴリズムは与えられた入力から固定長の結果を生成します.このプロセスをハッシングと呼ぶ.固定長の結果はハッシュバケットに入力をマップするインデックスとしてハッシュテーブルで使用されます.コンピューティングのハッチングプロセスは、ハッチングブラウンを作るためにジャガイモをハックするようです.あなたは、ハッシュ関数の結果としてハッシュされたインデックスとして、ジャガイモのキー入力、ハッシュ関数としてgrater、および破砕ジャガイモと考えることができました.どのようにジャガイモ全体に刻まれたジャガイモを返すことができないように、ハッシュ関数は不可逆です-それは1つの方法のアルゴリズムです.
以下はJavaScriptのハッシュ関数の例です.
function hash (key, size) {
    let hashedKey = 0;
    for (let i = 0; i < key.length; i++) {
        hashedKey += key.charCodeAt(i)
    }
    return hashedKey % size
}

擬似コード:
  • この関数は2つの引数key ハッシュするsize ハッシュバケットの
  • 変数名を初期化するhashedKey 終了時に0を返す
  • 文字列の各文字を繰り返して文字コードをまとめます
  • 反復の後、剰余演算(%)を使用してhashedKey / size そしてそれを新しいhashedKey
  • リターンhashedKey
  • 解説
    上記のアルゴリズムでは、変数hashedKey AS0 . この変数の値は文字列に基づいて変更され、この関数の結果として返されます.各文字を数字に表現する方法が必要です.このようにして、関数を通過するマッチング文字列キーは常に同じ整数に変換されます.JavaScriptの文字列メソッドcharCodeAt() 文字列文字をUTF - 16コード単位を表す整数に変換できます.
    これにより、forループはキー入力のすべての文字を反復します.反復される各文字に対して、charCodeAt() 文字を変換し、hashedKey 変数を先頭に定義します.各文字を表す整数を合計すると、モジュロ演算を実行します% 使用size バケット(関数の2番目の引数)の除数として.モジュロ演算は、結果の整数が0からバケツのサイズまでの範囲にあることを保証するだけでなく、結果を不可逆にします.
    これは、より良く改善される非常にシンプルで基本的なハッシュ関数です.チェックアウトするthis blog post あなたは数学者や世界中のコンピュータ科学者によって設計されたさまざまなハッシュ関数について学ぶことに興味がある場合.今、JavaScriptのハッシュテーブルを実装する時間です!

    JavaScriptの実装

    class HashTable {
        constructor(size=53) {
            this.size = size
            this.buckets = new Array(size);
        }
        _hash (key) {
            let hashedKey = 0;
            for (let i = 0; i < key.length; i++) {
                hashedKey += key.charCodeAt(i)
            }
            return hashedKey % this.size
        }
    }
    
    let table = new HashTable()
    
    console.log(table) // HashTable {size: 53, buckets: Array(53)}
    
    

    The above Hash Table class has two properties:

    1. size : the number representing the size of the buckets, and we are using prime number 53 as a default value (choosing a prime number for the hash table's size reduces the chances of collisions)
    2. buckets : buckets are the placeholders for each data (key/value pair), and we are using Array class to create an empty array with size of 53 indices

    And we have the _hash method similar to what we created earlier, but only difference is that it is not taking in the size as second argument since we are using the size of the object created from the Hash Table class. With this, we can create an object with buckets array that contains default size of 53 indices or a specified size .

    Let's go ahead and add some methods to this Hash Table!


    ヘルパー関数

    set()

    // adds key-value pair into hash table's bucket
    set(key, value) {
        let index = this._hash(key)
        this.buckets[index] = [key, value];
    }
    

    Pseudocode:

    • Accepts a key and a value
    • Hashes the key
    • Stores the key-value pair in the hash bucket

    get()

    // retrieves the value of the key from its respective bucket
    get(key) {
        let index = this._hash(key)
        return this.buckets[index][1] // returns value of the key
    }
    

    Pseudocode:

    • Accepts a key
    • Hashes the key
    • Retrieves the key-value pair in the hash bucket

    remove()

    // removes the key-value pair from the hash table's bucket
    remove(key) {
        let index = this._hash(key)
        let deleted = this.buckets[index]
        delete this.buckets[index]
        return deleted
    }
    

    Pseudocode:

    • Accepts a key
    • Hashes the key
    • Retrieves the key-value pair in the hash bucket and stores it
    • Delete the key-value pair in the hash bucket (use delete operator to empty the element, doesn't affect the array size)
    • Returns the stored key-value pair

    All the helper functions in this data structure is fairly simple -- they all utilize the hash function we defined earlier to retrieve the index that is associated with the key passed, and access the array's element in that index . There's a problem with these methods though. What happens if the hash function returns the same index for two different inputs? Our hash function is fairly simple so this will happen for sure. If so, it will override the bucket that is already occupied or get method will retrieve a wrong value that we aren't looking for. How can we improve these helper methods to handle the collisions?


    衝突の取り扱い


    我々が以前に議論したように、ハッシュ関数が衝突を生じるのは、可能です.残念ながら、状況の中でさえ、衝突はほとんど避けられない.出力より多くの入力によるどんなハッシュ関数も、そのような衝突を必然的に持ちます;見つけるのが難しいほど、ハッシュ関数はより安全です.
    衝突を扱うために複数の方法があります、そして、2つの一般のテクニックは別々の連鎖と線形探査です.
    別々の連鎖:配列のインデックスにポインティングしているハッシュコードだけがあるならば、値は直接そのインデックスに格納されます.2番目の値のハッシュコードも同じインデックスを指している場合は、インデックス値をリンクリストまたは配列と置換し、そのインデックスを指すすべての値をリストに格納します.値を取得しながら同じロジックを適用すると、バケツが複数のキー値のペアを格納する場合、バケツ内のすべての要素を反復処理する必要があります.要するに、別々の連鎖は衝突で複数のデータを保存するためにバケツの中のオブジェクトのようなリストを作成します.
    線形プロービング技術は、空のバケツを見つけるまで、ハッシュされたインデックスをインクリメントし続ける維持の概念に取り組みます.このように、線形プロービングは別々の連鎖より少ないスペースを取って、別々の連鎖よりかなり速く実行します(バケットの中でリストを通してループする必要がないので).
    別々の連鎖は、線形プロービングよりもかなり効率的ではありませんが、実装が簡単です.ここでは、別々の連鎖を利用して定義されているヘルパーメソッドを改善する方法を説明します.
    set ()
    // adds key-value pair into hash table's bucket
    set(key, value) {
        let index = this._hash(key)
        if(!this.buckets[index]) {
            this.buckets[index] = [];
        }
        this.buckets[index].push([key, value]);
    }
    
    擬似コード:
  • Aを受け入れるkeyvalue
  • ハッシュkey
  • ハッシュバケットが空の場合、空の配列として設定します
  • キー値ペアをバケツの中の配列にプッシュする
  • get ()
    // retrieves the value of the key from its respective bucket
    get(key) {
        let index = this._hash(key)
        if(this.buckets[index]) {
            for(let i = 0; i < this.buckets[index].length; i++) {
                if(this.buckets[index][i][0] === key) {
                    return this.buckets[index][i][1]
                }
            }
        }
        return undefined
    }
    
    擬似コード:
  • Aを受け入れるkey
  • ハッシュkey
  • バケツが真実であるならば、バケツの中で各々のキー値組を繰り返してください
  • If the key ペアにマッチし、value 一対の
  • リターンundefined バケツが空ならば
  • remove ()
    // removes the key-value pair from the hash table's bucket
    remove(key) {
        let index = this._hash(key)
        if(this.buckets[index]) {
            for(let i = 0; i < this.buckets[index].length; i++) {
                if(this.buckets[index][i][0] === key) {
                    return this.buckets[index].splice(i, 1)
                }
            }
        }
    }
    
    擬似コード:
  • Aを受け入れるkey
  • ハッシュkey
  • バケツが真実であるならば、バケツの中で各々のキー値組を繰り返してください
  • If the key ペアにマッチし、ペアを削除し、それを返す
  • ビッグ・オー


  • 空間の複雑さ
  • o ( n )
  • このデータ構造の空間複雑さは線形です:バケットのサイズが増加するので、スペース

  • 設定/取得/削除:
  • 平均:O(1)時間複雑さ
  • 最悪の場合:O(n)時間複雑度
  • これらのすべてのヘルパーメソッドはインデックスを調べるためにハッシュ関数を使用します.ハッシュ関数は一定の時間がかかりますが、時間の複雑さは、衝突のために複数の要素を持つバケツで直線を得ることができます.より多くの項目は、バケツの中を見て、それゆえに、線形時間( o ( n )
  • 参考になるリソース

    Online Course (udemyコース)
    JavaScriptアルゴリズムとデータ構造MasterClassという名前のこのUDEMYコースをチェック!これは作成され、私はこのブログ記事のデータ構造の実装部分のための彼のコードを参照します.個人的には、私はどこからアルゴリズムやデータ構造、特に非技術的な背景から来て知っていない.このコースは非常によく初心者のためのこれらのトピックの基礎を構築するために構築されます.
    Visual Animation ビジュアルゴ
    データ構造は、コード/テキストを見るだけで、一部の人々のために理解するのが難しいかもしれません.上記のインストラクターは、Visualgoという名前のウェブサイトを使用して、アニメーションを通してアルゴリズムやデータ構造を視覚的に表現しています.
    Data Structure Cheat Sheet (インタビューケーキ)
    また、ここでは本当によく整理されたカンニングペーパー/データ構造の可視化です.
    YouTube動画
    私はこのYouTubeのビデオの向こう側に来ました.
    !ハーバードのCS 50コースの一部であり、ハッシュテーブルを説明する素晴らしい仕事をしています.