終於知道哈希表是什麼了!

這是我參與8月更文挑戰的第7天,活動詳情查看:8月更文挑戰算法

哈希表

幾乎全部的編程語言都有直接或間接地應用哈希表這種數據結構。哈希表一般是基於數組實現的,它神奇的地方在於對下標值的一種變換,這種變換可稱爲哈希函數,經過哈希函數能夠獲取到HashCode。相對於數組,它的優點在於速度快,效率更高。編程

1、認識哈希表

  • 哈希化:將大數字轉化成數組範圍的下標的過程
  • 哈希函數:一般咱們會將單詞轉換爲大數字(每個字母對應的unicode),將大數字轉換爲哈希化的代碼的過程封裝成的函數稱爲哈希函數
  • 哈希表:最終將數據插入到的這個數組,對整個結構的封裝,就稱爲哈希表

哈希表中的每個key都是惟一值,並且每個key都有其對應的索引值,經過這個索引值直接在哈希表中找到這個key的value值,大大地提升了查找的效率。若是數據龐大的是否,每個索引值對應的就不止一個key,此時就會產生衝突。這個衝突常見有2種解決方式:鏈地址法和開放地址法。數組

在下面的封裝中,採用的基於數組的鏈地址法。markdown

2、封裝哈希表

在哈希表封裝的時候,設置哈希表的總長度。通常將總長度設置爲存放個數的兩倍,好比有50個數據,那咱們設置這個哈希表的總長度爲100。數據結構

function HashTable() {
    this.storage = [];  // 存放元素
    this.count = 0;     // 存放個數
    this.limit = 7;     // 當前哈希表的總長度
}
複製代碼

哈希函數

哈希函數在哈希表中是相當重要的,幾乎每個操做都須要它。它的做用在於獲取某個key在哈希表中的索引值,而後經過一些計算方法來計算出它的hashCode,再對這個hashCode進行取餘(hashCode的值是很大的,這裏是爲了讓數據量變小),而這個size,就是哈希表的this.limit,即哈希表的長度。編程語言

這裏的37實際上是一個底數,好比abc = 1*37^2 + 2*37^1 + 3。這裏取一個合適的數就行了,不必定就要37,不過最好是質數。函數

HashTable.prototype.hashFunc = function (str, size) {
    var hashCode = 0;
    // 霍納算法,計算hashCode的值
    for (var i = 0; i < str.length; i++) {
        hashCode = 37 * hashCode + str.charCodeAt(i);
    }
    // 取餘
    var index = hashCode % size;
    return index;
}
複製代碼

3、常見操做

這裏使用的是鏈地址法(基於數組,即每個下標值對應的是一個數組)post

a、插入和修改數據

由於在哈希表中,是沒有重複的值的,因此當用戶傳入一個(key, value)時,若是原來不存在這個key,就是插入操做;若是存在,就是修改操做。操做步驟:測試

  1. 根據key獲取索引值(經過哈希算法),將數據插入到對應的位置this

    var index = this.hashFunc(key, this.limit);
    複製代碼
  2. 根據索引值取出這個位置:先判斷該位置是否存在一個數組,若是不存在就建立

    var bucket = this.storage[index];
    if (bucket == null) {
        bucket = [];
        this.storage[index] = bucket;
    }
    複製代碼
  3. 判斷是新增仍是修改原來的值 (key同樣就是修改,不同就是新增)

    若是已經有這個key了,那麼就修改該key的值;

    這裏的bucket是哈希表中其中一個索引值的引用,存儲多個key: value的數組 (這些key經過哈希函數計算出來的索引值是想經過的)

    for (var i = 0; i < bucket.length; i++) {
        var tuple = bucket[i]
        if (tuple[0] == key) {
            tuple[1] = value;
            return;
        }
    }
    複製代碼
  4. 若是沒有,執行後續的添加操做;注意這裏push的是數組對象

    bucket.push([key, value]);  
    this.count += 1;
    複製代碼

b、獲取和刪除操做

這裏的前兩個步驟跟上面的添加或修改數據是相同的。先經過哈希函數算出這個key對應的索引值,根據這個索引值找到這個key所在的數組,若是存在,再從這個數組裏面找這個key對應的value。

  1. 根據key獲取對應的index

  2. 根據index獲取對應的位置bucket(數組)

  3. 判斷bucket是否爲null,若是爲null直接返回null

    if (bucket == null)   return null;
    複製代碼
  4. 線性查找bucket中每個key是否等於傳入的key

    若是等於就返回對應的value;若是遍歷結束了還沒找到就返回null

    for (var i = 0; i < bucket.length; i++) {
        var tuple = bucket[i]
        if (tuple[0] == key) {
            return tuple[1];
        }
    }
    return null;
    複製代碼

刪除操做在這裏思路是差很少的,只不過獲取操做獲取到這個key對應的value就返回,而刪除操做時把它刪除;再判斷語句中加入下面兩行代碼對獲取到的value值連帶key刪除掉便可。

bucket.splice(i, 1);
this.count -= 1;
複製代碼

c、測試代碼

其中,0, 3, 4, 5分別爲轉換以後的哈希表索引值,當他們的索引值相同時,就會被以key = value的形式存入到一個數組中。

var hash = new HashTable();
 hash.put('mannqo', 123);
 hash.put('mama', 222);
 hash.put('Ytao', 321);
 hash.put('fafa', 213);
 hash.put('neinei', 123);
 hash.remove('neinei');
 console.log(hash.get('neinei'));
 console.log(hash.get('mannqo'));
 console.log(hash);
複製代碼

輸出結果:

哈希表.png 能夠看到,刪除後的元素再次get的話返回的是空,並且在哈希表中也看不到了。而索引值相同的mamafafa被存放到索引值爲0的數組當中。

4、哈希表擴容

隨着數據量的增多,每個索引值對應的bucket(數組)長度也會愈來愈長,會致使效率的下降。這個是否就須要擴容哈希表。在合適的狀況下對數組擴容能夠提升效率,通常是在loadFactor(存放個數/哈希表長度)>0.75的時候進行擴容。在擴容以後,要記得對全部數據項的位置進行修改(從新調用哈希函數,獲取不一樣的位置),由於擴容以後每個元素的位置也會發生改變。

  1. 保存舊的數據內容 var oldStorage = this.storage;

  2. 重置全部的屬性

    this.storage = [];
    this.count = 0;
    this.limit = newLimt;
    複製代碼
  3. 遍歷oldStorage中全部的bucket

    這裏要先判斷每個舊的bucket有沒有東西,若是沒有就繼續執行程序,若是有就把它存放到新的bucket中。

    for (var i = 0; i < oldStorage.length; i++) {
        var bucket = oldStorage[i];
        if (bucket == null) {
            continue;
        }
        for (var j = 0; j < bucket.length; j++) {
            var tuple = bucket[j];
            this.put(tuple[0], tuple[1]);
        }
    }
    複製代碼

判斷是否須要擴容

if (this.count > this.limit * 0.75) {
    this.resize(this.limit * 2);
}
複製代碼

在刪除的元素過多的是否,也能夠對哈希表進行縮小容量,能夠把下面這段判斷語句加到刪除操做中。

if (this.limit > 7 && this.count < this.limit * 0.25) {
    this.resize(Math.floor(this.limit / 2))
}
複製代碼

5、總結

  • 哈希表的做用是十分強大的,最突出的特色在於它查找速度之快。每個key都會有它所對應的索引值,根據這個索引值能夠很是快的找到它所在的位置。在這個位置可能不止一個元素(衝突不可避免),可是衝突的元素通常都很少,效率仍是很高滴。
  • 不過哈希表仍是有它的不足,在哈希表中,數據是沒有順序的,並且哈希表中的key是不容許重複的,每個key都對應着一個value值,用於保存不一樣的元素。不一樣放置相同的key。
相關文章
相關標籤/搜索