資料結構 雜湊(hashing)

2021-07-10 03:02:46 字數 4376 閱讀 3742

宣告:本文為學習資料結構與演算法分析(第三版) clifford a.shaffer 著的學習筆記,**有參考該書的示例**。

把關鍵碼值對映到陣列中的位置來訪問記錄這個過程稱為雜湊(hashing)

把關鍵碼值對映到位置的函式稱為雜湊函式(hashing function),通常用 h 表示。

存放記錄的陣列稱為雜湊表(hash table),用 ht 表示。

雜湊表中的乙個位置稱為乙個槽(slot)。

雜湊表 ht 中的數目用變數 m 表示,槽從 0 到 m-1標號。

雜湊方法通常不適用於允許多條記錄有相同關鍵碼值的應用程式。雜湊方法一般不適用於範圍檢索。

對於乙個雜湊函式 h 和兩個關鍵碼值k1 、 k2,如果 h(k1) =b= h(k2) ,其中 b 為表中的乙個槽,那麼就說 k1 和 k2 對於 b 在雜湊函式 h 下有衝突。

對於衝突,有開雜湊和閉雜湊的解決方式。

從技術上來說,任何能把所有可能關鍵碼值對映到雜湊表槽中的函式都是雜湊函式。

下面介紹乙個常用的雜湊函式

平方取中方法是乙個用於數值的雜湊方法。對關鍵碼計算平方,取中間的幾位,雜湊到相應的位置。

這樣計算的原因是因為關鍵碼的大多數字或所有位對結果都有貢獻。

比如,基數為 10 的四位關鍵碼,雜湊到乙個長度為 100 的雜湊表中。假如關鍵碼為 4567 ,則

4567 * 4567 = 20857489

取的中間兩位是 57 。(位數等於 ln100 )

這是乙個用於字串的雜湊方法,計算的方法是將字串的 ascii 值累加起來,對 m 求模。

int h(char* str)

這個方法有乙個不好的地方就是,假如 sum 的值比 m 小,則會產生比較差的分布。

由於找不到這個方法的名字,就這麼叫吧。

這種雜湊方法的計算方式是,將字串解釋為乙個個大小為 4 的無符號整型,求和,然後求模。

int sfold(char* str)

使用無符號整數的目的是為了避免求模時結果會是負數。

在就算的過程中,如果數字過大,則會發生溢位。但是作為雜湊函式,並沒有關係。(意思就是,讓它溢位吧)

儘管雜湊函式的目的是儘量減少衝突,但是有些衝突是不可避免的。

解決衝突的技術可以分為兩類:開雜湊方法(open hashing,也稱單鏈方法,separate chaining)和閉雜湊方法(closed hashing,也稱開位址方法,open addressing)

開雜湊方法解決衝突是將衝突記錄在表外,而閉雜湊方法是將衝突記錄在表內的另乙個空槽。

看起來就好像很好實現的樣子,於是我就是實現了一下。

在我的實現中 m 為11,雜湊函式如下:

int h(const key& k) const

實話說,這並不是乙個好的雜湊函式。

閉雜湊方法將所有記錄都直接儲存在雜湊表中。

閉雜湊方法有集中雜湊方式:桶式雜湊、線性探查、二次探查、雙雜湊方法。

桶式雜湊是把雜湊的槽分成多個桶(bucket)。

把雜湊表中的 m 個槽分成 b 個桶,每個桶中包含 m/b 個槽。

雜湊函式把每一條記錄分配到某個桶的第乙個槽中。如果這個槽已經被占用,那麼就順序地沿著桶查詢,直到找到乙個空槽。

如果沒有空槽了,那麼就將該條記錄分配到乙個具有無限容量的溢位桶中。

插入順序是

9 30 27 4 8

有6個槽,3個桶的雜湊表,雜湊函式是

int h(int i)

我想這樣應該表達明確了吧。

桶式雜湊的乙個簡單變體是,先把關鍵碼雜湊到槽中。當該槽滿時,再把關鍵碼雜湊到同乙個桶的其他槽中。如果還沒有空槽,就雜湊到溢位桶中。

這時雜湊函式是:

int h(int i)

線性探查是比較常用的雜湊函式,它不使用桶式雜湊的方法,而是允許記錄儲存在雜湊表中的任何乙個空槽中。

衝突解決策略是產生一組有可能放置該記錄的槽,第乙個槽就是該關鍵碼的基槽。如果基槽被占用了,那麼就會尋找下乙個槽,直到記錄被存放。

這組槽就稱為衝突解決策略產生的探查序列(probe sequence)。

探查序列是由探查函式(probe function)的 p 函式生成的。

注意,探查函式返回的是相對於初始位置的偏移量,而不是雜湊表的乙個槽。

線性探查的函式類似如下:

int p(int k, int i)

其中 i 是第幾次的探查引數, k 是關鍵碼,a 和 b 是常數。

使用的時候如下:

return (h(k)+p(k, i))%m;
為了是探查列走遍所有的槽,a 必須與 m互素。

線性探查會導致:基本聚集(primary clustering)

考慮順序插入:

9 30 27 4 8

加入使用最基本的探查函式return i;m為6

那麼 9 的探查序列:

3 4 5 0 1 2

而 27 的探查序列也是如此。

到插入的記錄多了,就是大部分地聚集到一起。

把記錄聚集到一起的傾向就是基本聚集了。

好的探查函式應該是使得它們的探查序列岔開。

而解決基本聚集問題就是使用二次探查或偽隨機探查。

二次探查的函式如下:

int p(int k, int i)

其中 a, b, c 為常數。

二次探查的缺陷在於,在某些特定的情況下,只有特定的槽被探查到。

考慮 m 為 3,p(k, i) = i*i;

那麼雜湊到槽 0 的只會探查到 0, 1 ,而不會探查到 2。

然而,這樣的情況是可以在低開銷的基礎上探查得到較好的結果。

當雜湊表長度為素數,以及探查函式為p(k, i) = i*i時,至少能夠訪問到表中一半的槽。

如果雜湊表長為 2 的指數,並且探查函式為p(k, i) = (i*i+i)/2, 那麼表中所有槽都能被探查序列訪問到。

在偽隨機探查中,探查序列中的第 i 個槽是(h(k) + ri) mod m,ri是 1 到 m-1 之間的數的隨機序列。

所有的插入和檢索都使用相同的偽隨機序列。

儘管二次探查和偽隨機探查能夠解決基本聚集問題,然而如果雜湊函式在某個基槽聚集,依然會保持聚集。這個問題稱為二次聚集(secondary clustering)

解決二次聚集問題可以使用雙雜湊方法

雙雜湊方法的形式:

int p(int k, int i)

h2 是第二個雜湊函式

好的雙雜湊實現方法應當保證所有探查序列常數都與表 m 長度互素。

其中一種方法是設定 m 為素數,而h2 返回1<=h2<=m-1之間的值。

另外一種方法是給定乙個 m 值,設定 m = 2m ,然後讓 h2 返回 1 到 2m 之間的乙個奇數值。

每個新插入操作產生的額外查詢代價將在雜湊表接近半滿時急劇增加。

如果還考慮到訪問模式,在理想情況下,記錄應當沿著探查序列按照訪問頻率排序。

刪除記錄的時候,要考慮到兩點:

刪除不應當影響後面的檢索。

刪除的槽應當可以為後來的插入操作所使用。

通過在刪除的槽中放置乙個特殊的標記就可以解決這個問題。這個標記稱為墓碑(tombstone)。

如果不想使用墓碑的標記,還可以在刪除的時候進行一次區域性的重組,或者定期重組雜湊表。

本文實現了開雜湊方法和閉雜湊方法中的一種。

**在本人的 github 上可以找到。

我的github

–end–

資料結構 雜湊

裝填因子 key的個數與表長的比值。雜湊表查詢成功的平均查詢長度,查詢失敗的平均查詢長度都是期望,期望怎麼求,平均查詢長度就怎麼求。雜湊表這裡有兩種實現方式 線性開型定址雜湊,鍊錶雜湊。1.線性開型定址雜湊 陣列實現,資料個數不大於表長,放乙個元素時,若發生衝突,則順次線性掃瞄直到找到乙個空位。2....

資料結構 雜湊

關鍵 不比較關鍵碼,直接搜尋得到需要的數。特點 與搜尋樹不一樣,雜湊結構元素的儲存位置與關鍵碼直接對應,可以不用進行比較遍歷。如圖,建立乙個陣列,把a 4 中的資料按特定的規則儲存到相應的位置,比如a i n,到時候搜尋資料的時候可以按照同樣的規律直接找到這個位置,如果這個位置有數,則存在。比如按照...

資料結構 雜湊

將元素的儲存位置和該元素的關鍵碼通過某種函式建立一一對應的關係,構造出來的儲存結構稱之為雜湊表,轉換時借助的函式稱之為雜湊函式,在理想情況下,根據關鍵碼搜尋元素時可以不經過任何比較,一次性從表中查詢到所要搜尋的元素 但是在通過雜湊函式進行元素儲存位置確立的時候會出現,不同元素的關鍵碼通過雜湊函式計算...