資料結構和演算法-雜湊表 (HashTable)

littlexiaoshuishui發表於2020-06-13

我們知道陣列是順序儲存的,通過下標可獲取值的時間複雜度是O(1)。HashTable隨機訪問元素的複雜度也是O(1)。

我們通過雜湊函式把元素的鍵對映為下標,然後將資料儲存在陣列中對應下標的位置。當我們按照鍵查詢元素時,我們用同樣的雜湊函式,將鍵值轉化陣列下標,從對應的陣列下標的位置取資料。

雜湊函式

顧名思義,它是一個函式。我們可以把它定義成hash(key),其中 key表示元素的鍵值,hash(key) 的值表示經過雜湊函式計算得到的雜湊值。
該如何構造雜湊函式呢?我總結了三

點雜湊函式設計的基本要求:

1. 雜湊函式計算得到的雜湊值是一個非負整數;

2. 如果 key1 = key2,那 hash(key1) == hash(key2);

3. 如果 key1 ≠ key2,那 hash(key1) ≠ hash(key2)(理論上沒有hash函式能夠滿足這個條件,hash衝突需要其他方式解決)

雜湊衝突

當我們用不同的鍵經過hash函式得到的雜湊值相同時,我們稱之為雜湊衝突。一般有兩種解決方法

1.開放定址法

開放定址法的核心思想是,如果出現了雜湊衝突,我們就重新探測一個空閒位置,將其插入。比如線性探測(Linear Probing),當我們往雜湊表中插入資料時,如果某個資料經過雜湊函式雜湊之後,儲存位置已經被佔用了,我們就從當前位置開始,依次往後查詢,看是否有空閒位置,直到找到為止。
舉個例子,有兩個鍵”cat”,”dog”; 經過hash(“cat”),hash(“dog”)得到的值都是10。所以”cat”會存在下標為10的位置,存”dog”鍵時,發現10已經有值,會順序往後找空閒位置,然後存進去。當用”dog”鍵獲取資料時,先通過hash(“dog”)得到10,10下標放的是”cat”!=”dog”,就會順序往下找,直到找到”dog”。
但是刪除操作稍微有些特別。我們不能單純地把要刪除的元素設定為空,而是特殊標記為 deleted。插入元素時,當線性探測空閒位置時,遇到標記為 deleted 的空間,並不是停下來,而是繼續往下探測。

2. 連結串列法

在雜湊表中,每個“桶(bucket)”或者“槽(slot)”會對應一條連結串列,所有雜湊值相同的元素我們都放到相同槽位對應的連結串列中。

資料結構和演算法-雜湊表 (HashTable)
當插入的時候,我們只需要通過雜湊函式計算出對應的雜湊槽位,將其插入到對應連結串列中即可,所以插入的時間複雜度是 O(1)。當查詢、刪除一個元素時,我們同樣通過雜湊函式計算出對應的槽,然後遍歷連結串列查詢或者刪除。

雜湊表的裝載因子 = 填入表中的元素個數 / 雜湊表的長度

HashTable和連結串列一起使用

HashTable的特點是通過key值查詢目標資料的複雜度是O(1),而連結串列卻很方便我們把資料按照一定順序連線起來,很多情況下使用連結串列時,再把資料放入HashTable儲存起來。

LRU快取演算法改進

資料結構和演算法-雜湊表 (HashTable)
如果單用連結串列實現
新增一個快取key,先查詢連結串列是否有key,如果有就把這個key節點移動到表頭。
如果沒有,表未滿,直接加到表頭。連結串列已滿,就先刪除表尾一個節點,再把新增的key節點插入到表頭。
改進後:連結串列+HashTable
新增key節點,直接查詢HashTable是否有key,如果有,就把key節點加到連結串列頭。
如果沒有,表未滿,直接加到表頭。連結串列已滿,就先刪除表尾一個節點,再把新增的key節點插入到表頭。

資料雖然是寫到兩個表中,其實一個資料只有一個資料節點,兩個表共用了資料節點,連結串列只是靠指標串起來的。
底層實現

資料結構和演算法-雜湊表 (HashTable)

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章