js數據結構-散列表(哈希表)

散列表

散列表(Hash table,也叫哈希表),是根據鍵(Key)而直接訪問在內存存儲位置的數據結構。也就是說,它經過計算一個關於鍵值的函數,將所需查詢的數據映射到表中一個位置來訪問記錄,這加快了查找速度。這個映射函數稱作散列函數,存放記錄的數組稱作散列表。

圖片描述

咱們從上圖開始分析前端

  • 有一個集合U,裏面分別是1000,10,152,9733,1555,997,1168
  • 右側是一個10個插槽的列表(散列表),咱們須要把集合U中的整數存放到這個列表中
  • 怎麼存放,分別存在哪一個槽裏?這個問題就是須要經過一個散列函數來解決了。個人存放方式是取10的餘數,咱們對應這圖來看算法

    • 1000%10=0,10%10=0 那麼1000和10這兩個整數就會被存儲到編號爲0的這個槽中
    • 152%10=2那麼就存放到2的槽中
    • 9733%10=3 存放在編號爲3的槽中

經過上面簡單的例子,應該會有一下幾點一個大體的理解數組

  • 集合U,就是可能會出如今散列表中的鍵
  • 散列函數,就是你本身設計的一種如何將集合U中的鍵值經過某種計算存放到散列表中,如例子中的取餘數
  • 散列表,存放着經過計算後的鍵

那麼咱們在接着看通常咱們會怎麼去取值呢?數據結構

好比咱們存儲一個key爲1000,value爲'張三' ---> {key:1000,value:'張三'}
從咱們上述的解釋,它是否是應該存放在1000%10的這個插槽裏。
當咱們經過key想要找到value張三,是否是到key%10這個插槽裏找就能夠了呢?到了這裏你能夠停下來思考一下。函數

散列的一些術語(能夠簡單的看一下)

  • 散列表中全部可能出現的鍵稱做全集U
  • 用M表示槽的數量
  • 給定一個鍵,由散列函數計算它應該出如今哪一個槽中,上面例子的散列函數h=k%M,散列函數h就是鍵k到槽的一個映射。
  • 1000和10都被存到了編號0的這個槽中,這種狀況稱之爲碰撞。

看到這裏不知道你是否大體理解了散列函數是什麼了沒。經過例子,再經過你的思考,你能夠回頭在讀一遍文章頭部關於散列表的定義。若是你能讀懂了,那麼我估計你應該是懂了。this

經常使用的散列函數

  • 處理整數 h=>k%M (也就是咱們上面所舉的例子)
  • 處理字符串:spa

    function h_str(str,M){
            return [...str].reduce((hash,c)=>{
                hash = (31*hash + c.charCodeAt(0)) % M
            },0)
        }

hash算法不是這裏的重點,我也沒有很深刻的去研究,這裏主要仍是去理解散列表是個怎樣的數據結構,它有哪些優勢,它具體作了怎樣一件事。
而hash函數它只是經過某種算法把key映射到列表中。設計

構建散列表

經過上面的解釋,咱們這裏作一個簡單的散列表3d

散列表的組成

  • M個槽
  • 有個hash函數
  • 有一個add方法去把鍵值添加到散列表中
  • 有一個delete方法去作刪除
  • 有一個search方法,根據key去找到對應的值

初始化

- 初始化散列表有多少個槽
- 用一個數組來建立M個槽
class HashTable {
        constructor(num=1000){
            this.M = num;
            this.slots = new Array(num);
        }
    }

散列函數

處理字符串的散列函數,這裏使用字符串是由於,數值也能夠傳換成字符串比較通用一些code

  • 先將傳遞過來的key值轉爲字符串,爲了可以嚴謹一些
  • 將字符串轉換爲數組, 例如'abc' => [...'abc'] => ['a','b','c']
  • 分別對每一個字符的charCodeAt進行計算,取M餘數是爲了恰好對應插槽的數量,你總共就10個槽,你的數值%10 確定會落到 0-9的槽裏
h(str){
        str = str + '';
        return [...str].reduce((hash,c)=>{
            hash = (331 * hash + c.charCodeAt()) % this.M;
            return hash;
        },0)
    }

添加

  • 調用hash函數獲得對應的存儲地址(就是咱們之間類比的槽)
  • 由於一個槽中可能會存多個值,因此須要用一個二維數組去表示,好比咱們計算得來的槽的編號是0,也就是slot[0],那麼咱們應該往slot[0] [0]裏存,後面進來的一樣是編號爲0的槽的話就接着往slot[0] [1]裏存
add(key,value) {
        const h = this.h(key);
        // 判斷這個槽是不是一個二維數組, 不是則建立二維數組
        if(!this.slots[h]){
            this.slots[h] = [];
        }
        // 將值添加到對應的槽中
        this.slots[h].push(value);
    }

刪除

  • 經過hash算法,找到所在的槽
  • 經過過濾來刪除
delete(key){
        const h = this.h(key);
        this.slots[h] = this.slots[h].filter(item=>item.key!==key);
    }

查找

  • 經過hash算法找到對應的槽
  • 用find函數去找同一個key的值
  • 返回對應的值
search(key){
        const h = this.h(key);
        const list = this.slots[h];
        const data = list.find(x=> x.key === key);
        return data ? data.value : null;    
    }

總結

講到這裏,散列表的數據結構已經講完了,其實咱們每學一種數據結構或算法的時候,不是去照搬實現的代碼,咱們要學到的是思想,好比說散列表它究竟作了什麼,它是一種存儲方式,能夠快速的經過鍵去查找到對應的值。那麼咱們會思考,若是咱們設計的槽少了,在同一個槽裏存放了大量的數據,那麼這個散列表它的搜索速度確定是會大打折扣的,這種狀況又應該用什麼方式去解決,又或者是否用其餘的數據結構的代替它。

補充一個小知識點

v8引擎中的數組 arr = [1,2,3,4,5] 或 new Array(100) 咱們都知道它是開闢了一塊連續的空間去存儲,而arr = [] , arr[100000] = 10 這樣的操做它是使用的散列,由於這種操做若是連續開闢100萬個空間去存儲一個值,那麼顯然是在浪費空間。

後續

後續可能會去介紹一下二叉樹,另外對於文章有什麼寫錯或者寫的很差的地方你們均可以提出來。我會持續的去寫關於前端的一些技術文章,若是你們喜歡的話能夠關注一下,點個贊哦謝謝

相關文章
相關標籤/搜索