數(shù)據結構(十)之哈希表實現(xiàn)

如需轉載, 請咨詢作者, 并且注明出處.
有任何問題, 可以關注我的微博: coderwhy, 或者添加我的微信: 372623326

前面, 我們使用了大量的篇幅來解析哈希表的理論知識.

現(xiàn)在, 我們進入代碼的實施階段, 但是實施之前, 先來深入一個比較重要的話題: 哈希函數(shù).

一. 哈希函數(shù)

講了很久的哈希表理論知識, 你有沒有發(fā)現(xiàn)在整個過程中, 一個非常重要的東西: 哈希函數(shù)呢?

我們這里來探討一下, 設計好的哈希函數(shù)應該具備哪些優(yōu)點.

快速的計算

  • 好的哈希函數(shù)應該盡可能讓計算的過程變得簡單, 應該可以快速計算出結果.
    • 哈希表的主要優(yōu)點是它的速度, 所以在速度上不能滿足, 那么就達不到設計的目的了.
    • 提高速度的一個辦法就是讓哈希函數(shù)中盡量少的有乘法和除法. 因為它們的性能是比較低的.
  • 在前面, 我們計算哈希值的時候使用的方式
    • cats = 3*273+1*272+20*27+17= 60337
    • 這種方式是直觀的計算結果, 那么這種計算方式會進行幾次乘法幾次加法呢? 當然, 我們可能不止4項, 可能有更多項
    • 我們抽象一下, 這個表達式其實是一個多項式: a(n)xn+a(n-1)x(n-1)+…+a(1)x+a(0)
    • 現(xiàn)在問題就變成了多項式有多少次乘法和加法:
      • 乘法次數(shù): n+(n-1)+…+1=n(n+1)/2
      • 加法次數(shù): n次
  • 多項式的優(yōu)化: 霍納法則
    • 解決這類求值問題的高效算法――霍納法則。在中國,霍納法則也被稱為秦九韶算法。
    • 通過如下變換我們可以得到一種快得多的算法,即Pn(x)= anx n+a(n-1)x(n-1)+…+a1x+a0=((…(((anx +an-1)x+an-2)x+ an-3)…)x+a1)x+a0,這種求值的安排我們稱為霍納法則。
    • 變換后, 我們需要多少次乘法, 多少次加法呢?
      • 乘法次數(shù): N次
      • 加法次數(shù): N次.
    • 如果使用大O表示時間復雜度的話, 我們直接從O(N2)降到了O(N).

均勻的分布

  • 均勻的分布
    • 在設計哈希表時, 我們已經有辦法處理映射到相同下標值的情況: 鏈地址法或者開放地址法.
    • 但是, 為了提供效率, 最好的情況還是讓數(shù)據在哈希表中均勻分布.
    • 因此, 我們需要在使用常量的地方, 盡量使用質數(shù).
    • 哪些地方我們會使用到常量呢?
  • 質數(shù)的使用:
    • 哈希表的長度.
    • N次冪的底數(shù)(我們之前使用的是27)
    • 下面我們簡單來說一下為什么.
  • 哈希表的長度使用質數(shù):
    • 這個在鏈地址法中事實上重要性不是特別明顯, 明顯的是在開放地址法中的再哈希法中.
    • 再哈希法中質數(shù)的重要性:
      • 假設表的容量不是質數(shù), 例如: 表長為15(下標值0~14)
      • 有一個特定關鍵字映射到0, 步長為5. 探測序列是多少呢?
      • 0 - 5 - 10 - 0 - 5 - 10, 依次類推, 循環(huán)下去.
      • 算法只嘗試著三個單元, 如果這三個單元已經有了數(shù)據, 那么會一直循環(huán)下去, 知道程序崩潰.
      • 如果容量是一個質數(shù), 比如13. 探測序列是多少呢?
      • 0 - 5 - 10 - 2 - 7 - 12 - 4 - 9 - 1 - 6 - 11 - 3, 一直這樣下去.
      • 不僅不會產生循環(huán), 而且可以讓數(shù)據在哈希表中更加均勻的分布.
    • 鏈地址法中質數(shù)沒有那么重要, 甚至在Java中故意是2的N次冪
      • Java中的哈希表采用的是鏈地址法.
      • HashMap的初始長度是16, 每次自動擴展(我們還沒有聊到擴展的話題), 長度必須是2的次冪.
      • 這是為了服務于從Key映射到index的算法.
      • HashMap中為了提高效率, 采用了位運算的方式.
        • HashMap中index的計算公式: index = HashCode(Key) & (Length - 1)
        • 比如計算book的hashcode,結果為十進制的3029737,二進制的101110001110101110 1001
        • 假定HashMap長度是默認的16,計算Length-1的結果為十進制的15,二進制的1111
        • 假定HashMap長度是默認的16,計算Length-1的結果為十進制的15,二進制的1111
        • 把以上兩個結果做與運算,101110001110101110 1001 & 1111 = 1001,十進制是9,所以 index=9
        • 這樣的方式相對于取模來說性能是高的, 因為計算機更運算計算二進制的數(shù)據.
      • 但是, 我個人發(fā)現(xiàn)JavaScript中進行較大數(shù)據的位運算時會出問題, 所以我的代碼實現(xiàn)中還是使用了取模.
  • N次冪的底數(shù), 使用質數(shù):
    • 這里采用質數(shù)的原因是為了產生的數(shù)據不按照某種規(guī)律遞增.
    • 比如我們這里有一組數(shù)據是按照4進行遞增的: 0 4 8 12 16, 將其映射到成都為8的哈希表中.
    • 它們的位置是多少呢? 0 - 4 - 0 - 4, 依次類推.
    • 如果我們哈希表本身不是質數(shù), 而我們遞增的數(shù)量可以使用質數(shù), 比如5, 那么 0 5 10 15 20
    • 它們的位置是多少呢? 0 - 5 - 2 - 7 - 4, 依次類推. 也可以盡量讓數(shù)據均勻的分布.
    • 我們之前使用的是27, 這次可以使用一個接近的數(shù), 比如31/37/41等等. 一個比較常用的數(shù)是37.

哈希函數(shù)實現(xiàn)

  • 現(xiàn)在, 我們就給出哈希函數(shù)的實現(xiàn):

    function hashFunc(str, max) {
        // 1.初始化hashCode的值
        var hashCode = 0
    
        // 2.霍納算法, 來計算hashCode的數(shù)值
        for (var i = 0; i < str.length; i++) {
            hashCode = 37 * hashCode + str.charCodeAt(i)
        }
    
        // 3.取模運算
        hashCode = hashCode % max
        return hashCode
    }
    
  • 代碼解析:

    • 理解了前面所有的內容, 其實代碼就非常簡單了.
    • 不再多做解釋, 有不懂的可以留言或者查看前面的內容.
  • 代碼測試:

    alert(hashFunc("abc", 7)) // 4
    alert(hashFunc("cba", 7)) // 3
    alert(hashFunc("nba", 7)) // 5
    alert(hashFunc("mba", 7)) // 1
    

二. 哈希表

經過前面那么多內容的學習, 我們現(xiàn)在可以真正實現(xiàn)自己的哈希表了.

可能你學到這里的時候, 已經感覺到數(shù)據結構的一些復雜性, 但是如果你仔細品味, 你也會發(fā)現(xiàn)它在設計時候的巧妙和優(yōu)美, 當你愛上它的那一刻, 你也真正愛上了編程.

我們這里采用鏈地址法來實現(xiàn)哈希表:

實現(xiàn)的哈希表(基于storage的數(shù)組)每個index對應的是一個數(shù)組(bucket).(當然基于鏈表也可以.)

bucket中存放什么呢? 我們最好將key和value都放進去, 我們繼續(xù)使用一個數(shù)組.(其實其他語言使用元組更好)

最終我們的哈希表的數(shù)據格式是這樣: [[ [k,v], [k,v], [k,v] ] , [ [k,v], [k,v] ], [ [k,v] ] ]

創(chuàng)建哈希表

  • 我們像封裝其他數(shù)據結構一樣, 先來創(chuàng)建一個哈希表的類: HashTable

    // 創(chuàng)建HashTable構造函數(shù)
    function HashTable() {
        // 定義屬性
        this.storage = []
        this.count = 0
        this.limit = 8
    
        // 定義相關方法
        // 哈希函數(shù)
        HashTable.prototype.hashFunc = function(str, max) {
            // 1.初始化hashCode的值
            var hashCode = 0
    
            // 2.霍納算法, 來計算hashCode的數(shù)值
            for (var i = 0; i < str.length; i++) {
                hashCode = 37 * hashCode + str.charCodeAt(i)
            }
          
            // 3.取模運算
            hashCode = hashCode % max
            return hashCode
        }
    }
    
  • 代碼解析:

    • 我們定義了三個屬性:
    • storage作為我們的數(shù)組, 數(shù)組中存放相關的元素.
    • count表示當前已經存在了多少數(shù)據.
    • limit用于標記數(shù)組中一共可以存放多少個元素.
    • 另外, 我們直接將哈希函數(shù)定義在了HashTable中.

插入&修改數(shù)據

  • 現(xiàn)在, 我們來做向哈希表中插入數(shù)據

    // 插入數(shù)據方法
    HashTable.prototype.put = function (key, value) {
        // 1.獲取key對應的index
        var index = this.hashFunc(key, this.limit)
    
        // 2.取出數(shù)組(也可以使用鏈表)
        var bucket = this.storage[index]
    
        // 3.判斷這個數(shù)組是否存在
        if (bucket === undefined) {
            // 3.1創(chuàng)建桶
            bucket = []
            this.storage[index] = bucket
        }
        alert(bucket)
        
        // 4.判斷是新增還是修改原來的值.
        var override = false
        for (var i = 0; i < bucket.length; i++) {
            var tuple = bucket[i]
            if (tuple[0] === key) {
                tuple[1] = value
                override = true
            }
        }
        
        // 5.如果是新增, 前一步沒有覆蓋
        if (!override) {
            bucket.push([key, value])
            this.count++
        }
    }
    
  • 代碼解析:

    • 步驟1: 根據傳入的key獲取對應的hashCode, 也就是數(shù)組的index
    • 步驟2: 從哈希表的index位置中取出桶(另外一個數(shù)組)
    • 步驟3: 查看上一步的bucket是否為null
      • 為null, 表示之前在該位置沒有放置過任何的內容, 那么就新建一個數(shù)組[]
    • 步驟4: 查看是否之前已經放置過key對應的value
      • 如果放置過, 那么就是依次替換操作, 而不是插入新的數(shù)據.
      • 我們使用一個變量override來記錄是否是修改操作
    • 步驟5: 如果不是修改操作, 那么插入新的數(shù)據.
      • 在bucket中push新的[key, value]即可.
      • 注意: 這里需要將count+1, 因為數(shù)據增加了一項.

獲取數(shù)據

  • 有插入和修改數(shù)據, 就應該有根據key獲取value

    // 獲取存放的數(shù)據
    HashTable.prototype.get = function (key) {
        // 1.獲取key對應的index
        var index = this.hashFunc(key, this.limit)
    
        // 2.獲取對應的bucket
        var bucket = this.storage[index]
    
        // 3.如果bucket為null, 那么說明這個位置沒有數(shù)據
        if (bucket == null) {
            return null
        }
    
        // 4.有bucket, 判斷是否有對應的key
        for (var i = 0; i < bucket.length; i++) {
            var tuple = bucket[i]
            if (tuple[0] === key) {
                return tuple[1]
            }
        }
        
        // 5.沒有找到, return null
        return null
    }
    
  • 代碼解析:

    • 步驟1: 根據key獲取hashCode(也就是index)
    • 步驟2: 根據index取出bucket.
    • 步驟3: 因為如果bucket都是null, 那么說明這個位置之前并沒有插入過數(shù)據.
    • 步驟4: 有了bucket, 就遍歷, 并且如果找到, 就將對應的value返回即可.
    • 步驟5: 沒有找到, 返回null

刪除數(shù)據

  • 我們根據對應的key, 刪除對應的key/value

    // 刪除數(shù)據
    HashTable.prototype.remove = function (key) {
        // 1.獲取key對應的index
        var index = this.hashFunc(key, this.limit)
        
        // 2.獲取對應的bucket
        var bucket = this.storage[index]
        
        // 3.判斷同是否為null, 為null則說明沒有對應的數(shù)據
        if (bucket == null) {
            return null
        }
        
        // 4.遍歷bucket, 尋找對應的數(shù)據
        for (var i = 0; i < bucket.length; i++) {
            var tuple = bucket[i]
            if (tuple[0] === key) {
                bucket.splice(i, 1)
                this.count--
                return tuple[1]
            }
        }
        
        // 5.來到該位置, 說明沒有對應的數(shù)據, 那么返回null
        return null
    }
    
  • 代碼解析:

    • 代碼思路和查詢基本一致, 不再給出解析過程. 也可以查看注釋.

其他方法

  • 判斷哈希表是否為空: isEmpty

    // isEmpty方法
    HashTable.prototype.isEmpty = function () {
        return this.count == 0
    }
    
  • 獲取哈希表中數(shù)據的個數(shù)

    // size方法
    HashTable.prototype.size = function () {
        return this.count
    }
    

哈希表測試

  • 我們來簡單測試一下上面的代碼

    // 測試哈希表
    // 1.創(chuàng)建哈希表
    var ht = new HashTable()
    
    // 2.插入數(shù)據
    ht.put("abc", "123")
    ht.put("cba", "321")
    ht.put("nba", "521")
    ht.put("mba", "520")
    
    // 3.獲取數(shù)據
    alert(ht.get("abc"))
    ht.put("abc", "111")
    alert(ht.get("abc"))
    
    // 4.刪除數(shù)據
    alert(ht.remove("abc"))
    alert(ht.get("abc"))
    

三. 哈希表擴容

我們在來將講一個哈希表的概念: 哈希表擴容.

哈希表擴容的思想

  • 為什么需要擴容?
    • 目前, 我們是將所有的數(shù)據項放在長度為8的數(shù)組中的.
    • 因為我們使用的是鏈地址法, loadFactor可以大于1, 所以這個哈希表可以無限制的插入新數(shù)據.
    • 但是, 隨著數(shù)據量的增多, 每一個index對應的bucket會越來越長, 也就造成效率的降低.
    • 所以, 在合適的情況對數(shù)組進行擴容. 比如擴容兩倍.
  • 如何進行擴容?
    • 擴容可以簡單的將容量增加大兩倍(不是質數(shù)嗎? 質數(shù)的問題后面再討論)
    • 但是這種情況下, 所有的數(shù)據項一定要同時進行修改(重新哈希化, 來獲取到不同的位置)
    • 比如hashCode=12的數(shù)據項, 在length=8的時候, index=4. 在長度為16的時候呢? index=12.
    • 這是一個耗時的過程, 但是如果數(shù)組需要擴容, 那么這個過程是必要的.
  • 什么情況下擴容呢?
    • 比較常見的情況是loadFactor>0.75的時候進行擴容.
    • 比如Java的哈希表就是在裝填因子大于0.75的時候, 對哈希表進行擴容.

哈希表擴容的實現(xiàn)

  • 我們來實現(xiàn)擴容函數(shù)

    // 哈希表擴容
    HashTable.prototype.resize = function (newLimit) {
        // 1.保存舊的數(shù)組內容
        var oldStorage = this.storage
    
        // 2.重置屬性
        this.limit = newLimit
        this.count = 0
        this.storage = []
    
        // 3.遍歷舊數(shù)組中的所有數(shù)據項, 并且重新插入到哈希表中
        oldStorage.forEach(function (bucket) {
            // 1.bucket為null, 說明這里面沒有數(shù)據
            if (bucket == null) {
                return
            }
    
            // 2.bucket中有數(shù)據, 那么將里面的數(shù)據重新哈?;迦?        for (var i = 0; i < bucket.length; i++) {
                var tuple = bucket[i]
                this.put(tuple[0], tuple[1])
            }
        }.bind(this))
    }
    
  • 代碼解析:

    • 步驟1: 先將之前數(shù)組保存起來, 因為我們待會兒會將storeage = []
    • 步驟2: 之前的屬性值需要重置.
    • 步驟3: 遍歷所有的數(shù)據項, 重新插入到哈希表中.
  • 在什么時候調用擴容方法呢?

    • 在每次添加完新的數(shù)據時, 都進行判斷. (也就是put方法中)
  • 修改put方法

    • 代碼第5步中的內容
    // 插入數(shù)據方法
    HashTable.prototype.put = function (key, value) {
        // 1.獲取key對應的index
        var index = this.hashFunc(key, this.limit)
    
        // 2.取出數(shù)組(也可以使用鏈表)
        // 數(shù)組中放置數(shù)據的方式: [[ [k,v], [k,v], [k,v] ] , [ [k,v], [k,v] ]  [ [k,v] ] ]
        var bucket = this.storage[index]
    
        // 3.判斷這個數(shù)組是否存在
        if (bucket === undefined) {
            // 3.1創(chuàng)建桶
            bucket = []
            this.storage[index] = bucket
        }
    
        // 4.判斷是新增還是修改原來的值.
        var override = false
        for (var i = 0; i < bucket.length; i++) {
            var tuple = bucket[i]
            if (tuple[0] === key) {
                tuple[1] = value
                override = true
            }
        }
    
        // 5.如果是新增, 前一步沒有覆蓋
        if (!override) {
            bucket.push([key, value])
            this.count++
            // 數(shù)組擴容
            if (this.count > this.limit * 0.75) {
                this.resize(this.limit * 2)
            }
        }
    }
    
  • 如果我們不斷的刪除數(shù)據呢?

    • 如果不斷的刪除數(shù)據, 當loadFactor < 0.25的時候, 最好將數(shù)量限制在一半.
  • 修改remove方法

    • 代碼第4步中的內容
    // 刪除數(shù)據
    HashTable.prototype.remove = function (key) {
        // 1.獲取key對應的index
        var index = this.hashFunc(key, this.limit)
    
        // 2.獲取對應的bucket
        var bucket = this.storage[index]
    
        // 3.判斷同是否為null, 為null則說明沒有對應的數(shù)據
        if (bucket == null) {
            return null
        }
    
        // 4.遍歷bucket, 尋找對應的數(shù)據
        for (var i = 0; i < bucket.length; i++) {
            var tuple = bucket[i]
            if (tuple[0] === key) {
                bucket.splice(i, 1)
                this.count--
                
                // 縮小數(shù)組的容量
                if (this.limit > 8 && this.count < this.limit * 0.25) {
                    this.resize(Math.floor(this.limit / 2))
                }
            }
            return tuple[1]
        }
    
        // 5.來到該位置, 說明沒有對應的數(shù)據, 那么返回null
        return null
    }
    

四. 容量質數(shù)

我們前面提到過, 容量最好是質數(shù).

雖然在鏈地址法中將容量設置為質數(shù), 沒有在開放地址法中重要, 但是其實鏈地址法中質數(shù)作為容量也更利于數(shù)據的均勻分布. 所以, 我們還是完成一下這個步驟.

判斷質數(shù)

  • 我們這里先討論一個常見的面試題, 判斷一個數(shù)是質數(shù).

  • 質數(shù)的特點:

    • 質數(shù)也稱為素數(shù).
    • 質數(shù)表示大于1的自然數(shù)中, 只能被1和自己整除的數(shù).
  • OK, 了解了這個特點, 應該不難寫出它的算法:

    function isPrime(num) {
        for (var i = 2; i < num; i++) {
            if (num % i == 0) {
                return false
            }
        }
        return true
    }
    
    // 測試
    alert(isPrime(3)) // true
    alert(isPrime(32)) // false
    alert(isPrime(37)) // true
    
  • 但是, 這種做法的效率并不高. 為什么呢?

    • 對于每個數(shù)n,其實并不需要從2判斷到n-1
    • 一個數(shù)若可以進行因數(shù)分解,那么分解時得到的兩個數(shù)一定是一個小于等于sqrt(n),一個大于等于sqrt(n).
    • 比如16可以被分別. 那么是2*8, 2小于sqrt(16), 也就是4, 8大于4. 而4*4都是等于sqrt(n)
    • 所以其實我們遍歷到等于sqrt(n)即可
    function isPrime(num) {
        // 1.獲取平方根
        var temp = parseInt(Math.sqrt(num))
    
        // 2.循環(huán)判斷
        for (var i = 2; i <= temp; i++) {
            if (num % i == 0) {
                return false
            }
        }
        return true
    }
    

擴容的質數(shù)

  • 首先, 將初始的limit為8, 改成7

  • 前面, 我們有對容量進行擴展, 方式是: 原來的容量 x 2

    • 比如之前的容量是7, 那么擴容后就是14. 14還是一個質數(shù)嗎?
    • 顯然不是, 所以我們還需要一個方法, 來實現(xiàn)一個新的容量為質數(shù)的算法.
  • 那么我們可以封裝獲取新的容量的代碼(質數(shù))

    // 判斷是否是質數(shù)
    HashTable.prototype.isPrime = function (num) {
        var temp = parseInt(Math.sqrt(num))
        // 2.循環(huán)判斷
        for (var i = 2; i <= temp; i++) {
            if (num % i == 0) {
                return false
            }
        }
        return true
    }
    
    // 獲取質數(shù)
    HashTable.prototype.getPrime = function (num) {
        while (!isPrime(num)) {
            num++
        }
        return num
    }
    
  • 修改插入和刪除的代碼:

  • 插入數(shù)據的代碼:

    // 擴容數(shù)組的數(shù)量
    if (this.count > this.limit * 0.75) {
        var primeNum = this.getPrime(this.limit * 2)
        this.resize(primeNum)
    }
    
  • 刪除數(shù)據的代碼:

    // 縮小數(shù)組的容量
    if (this.limit > 7 && this.count < this.limit * 0.25) {
        var primeNum = this.getPrime(Math.floor(this.limit / 2))
        this.resize(primeNum)
    }
    

五. 完整代碼

  • 最后, 還是給出實現(xiàn)哈希表的完整代碼:

    // 創(chuàng)建HashTable構造函數(shù)
    function HashTable() {
        // 定義屬性
        this.storage = []
        this.count = 0
        this.limit = 8
    
        // 定義相關方法
        // 判斷是否是質數(shù)
        HashTable.prototype.isPrime = function (num) {
            var temp = parseInt(Math.sqrt(num))
            // 2.循環(huán)判斷
            for (var i = 2; i <= temp; i++) {
                if (num % i == 0) {
                    return false
                }
            }
            return true
        }
    
        // 獲取質數(shù)
        HashTable.prototype.getPrime = function (num) {
            while (!isPrime(num)) {
                num++
            }
            return num
        }
    
        // 哈希函數(shù)
        HashTable.prototype.hashFunc = function(str, max) {
            // 1.初始化hashCode的值
            var hashCode = 0
    
            // 2.霍納算法, 來計算hashCode的數(shù)值
            for (var i = 0; i < str.length; i++) {
                hashCode = 37 * hashCode + str.charCodeAt(i)
            }
    
            // 3.取模運算
            hashCode = hashCode % max
            return hashCode
        }
    
        // 插入數(shù)據方法
        HashTable.prototype.put = function (key, value) {
            // 1.獲取key對應的index
            var index = this.hashFunc(key, this.limit)
    
            // 2.取出數(shù)組(也可以使用鏈表)
            // 數(shù)組中放置數(shù)據的方式: [[ [k,v], [k,v], [k,v] ] , [ [k,v], [k,v] ]  [ [k,v] ] ]
            var bucket = this.storage[index]
    
            // 3.判斷這個數(shù)組是否存在
            if (bucket === undefined) {
                // 3.1創(chuàng)建桶
                bucket = []
                this.storage[index] = bucket
            }
    
            // 4.判斷是新增還是修改原來的值.
            var override = false
            for (var i = 0; i < bucket.length; i++) {
                var tuple = bucket[i]
                if (tuple[0] === key) {
                    tuple[1] = value
                    override = true
                }
            }
    
            // 5.如果是新增, 前一步沒有覆蓋
            if (!override) {
                bucket.push([key, value])
                this.count++
    
                if (this.count > this.limit * 0.75) {
                    var primeNum = this.getPrime(this.limit * 2)
                    this.resize(primeNum)
                }
            }
        }
    
        // 獲取存放的數(shù)據
        HashTable.prototype.get = function (key) {
            // 1.獲取key對應的index
            var index = this.hashFunc(key, this.limit)
    
            // 2.獲取對應的bucket
            var bucket = this.storage[index]
    
            // 3.如果bucket為null, 那么說明這個位置沒有數(shù)據
            if (bucket == null) {
                return null
            }
    
            // 4.有bucket, 判斷是否有對應的key
            for (var i = 0; i < bucket.length; i++) {
                var tuple = bucket[i]
                if (tuple[0] === key) {
                    return tuple[1]
                }
            }
    
            // 5.沒有找到, return null
            return null
        }
    
        // 刪除數(shù)據
        HashTable.prototype.remove = function (key) {
            // 1.獲取key對應的index
            var index = this.hashFunc(key, this.limit)
    
            // 2.獲取對應的bucket
            var bucket = this.storage[index]
    
            // 3.判斷同是否為null, 為null則說明沒有對應的數(shù)據
            if (bucket == null) {
                return null
            }
    
            // 4.遍歷bucket, 尋找對應的數(shù)據
            for (var i = 0; i < bucket.length; i++) {
                var tuple = bucket[i]
                if (tuple[0] === key) {
                    bucket.splice(i, 1)
                    this.count--
    
                    // 縮小數(shù)組的容量
                    if (this.limit > 7 && this.count < this.limit * 0.25) {
                        var primeNum = this.getPrime(Math.floor(this.limit / 2))
                        this.resize(primeNum)
                    }
                }
                return tuple[1]
            }
    
            // 5.來到該位置, 說明沒有對應的數(shù)據, 那么返回null
            return null
        }
    
        // isEmpty方法
        HashTable.prototype.isEmpty = function () {
            return this.count == 0
        }
    
        // size方法
        HashTable.prototype.size = function () {
            return this.count
        }
    
        // 哈希表擴容
        HashTable.prototype.resize = function (newLimit) {
            // 1.保存舊的數(shù)組內容
            var oldStorage = this.storage
    
            // 2.重置屬性
            this.limit = newLimit
            this.count = 0
            this.storage = []
    
            // 3.遍歷舊數(shù)組中的所有數(shù)據項, 并且重新插入到哈希表中
            oldStorage.forEach(function (bucket) {
                // 1.bucket為null, 說明這里面沒有數(shù)據
                if (bucket == null) {
                    return
                }
    
                // 2.bucket中有數(shù)據, 那么將里面的數(shù)據重新哈?;迦?            for (var i = 0; i < bucket.length; i++) {
                    var tuple = bucket[i]
                    this.put(tuple[0], tuple[1])
                }
            }).bind(this)
        }
    }
    
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容