[數據結構]從散列(Hash)表、哈希函數的構造到解決衝突

一、什麼是散列表?

散列表(Hash table,也叫哈希表),是根據關鍵字碼值而直接進行訪問的數據結構,也就是通過把關鍵字碼值映射到表中一個位置來訪問記錄,以加快查找的速度。
哈希函數也叫做散列函數,是將記錄的關鍵字值與記錄的存儲位置對應起來的關係f,f(關鍵字)的結果稱位哈希地址。

哈希地址(記錄的存儲位置)= f(關鍵字),這裏對應的關係就叫哈希函數,也可叫散列函數。

在運用散列表來解決問題時,我們務必考慮好下面因素:

(1) 設計一個簡單、均勻、存儲效率高的散列函數是散列技術中最關鍵的問題。
(2)衝突問題:在理想的情況下,每一個關鍵字,通過散列函數計算出來的地址都是不一樣的,可現實中,這只是一個理想。但是我們經常會碰到兩個關鍵字k1=k2,但是卻有f(k1)=f(k2),這種現象我們稱爲衝突,並把k1和k2稱爲這個散列函數的同義詞。

散列表的應用:
(1)主要用於信息安全領域中加密算法,它把一些不同長度的信息轉化成雜亂的128位的編碼,這些編碼值叫做Hash值. 也可以說,Hash就是找到一種數據內容和數據存放地址之間的映射關係。
(2)查找:當我們知道key值就可以知道關鍵字對應的位置。

二、哈希函數的構造方法

構造散列函數的目標是使散列地址儘可能均勻地分佈在散列空間上,同時使計算儘可能簡單,以節省計算時間。
下面讓我們一起來了解一下幾種常用的散列函數構造方法。
1、直接定址法:取關鍵字或關鍵字的某個線性函數爲哈希地址

f(key)=key或f(key)=a*key+b

其中a,b爲常數,調整a與b的值可以使哈希地址取值範圍與存儲空間範圍一致。
在這裏插入圖片描述
這樣的散列函數優點就是簡單、均勻、也不會產生衝突,但問題是這需要事先知道關鍵字的分佈情況,適合查找表較小且連續的情況。此法並不常用。
2、數字分析法:對各個關鍵字的各個碼位進行分析,取關鍵字中某些取值較分散的數字位作爲散列地址的方法
我們的手機號前三位是接入號,一般對應不同運營商公司的子品牌,如130是聯通如意通、136是移動神州行、153是電信等,中間4位是HLR識別號,表示用戶歸屬地;後四位纔是真正的用戶號。如果用我們的手機號作爲關鍵字,那麼極有可能前7位都是相同的。我們就可以選擇後面的四位成爲散列地址,這就是抽取,也就是抽取關鍵字的一部分來計算散列存儲位置的方法,這在散列函數中常常用到的手段。
在這裏插入圖片描述
數字分析法通常適合處理關鍵字位數比較大的情況,如果事先知道關鍵字的分佈且關鍵字的若干位分佈較均勻,就可以考慮此法。
3、平方取中法:取關鍵字平方的中間幾位作爲散列地址的方法
假設關鍵字爲1234,它的平方就是1522756,再抽取中間的3位就是227,也可以是722,用作散列地址。平方取中法比較適合於不知道關鍵字的分佈,而位數又不是很大的情況。
4、摺疊法:將關鍵字從左到右分割成位數相等的幾部分(最後一部分不夠可以短些),然後將這幾部分疊加求和,並按散列表表長,取後幾位作爲散列地址。
比如關鍵字9876543210,分爲四組987|654|321|0,疊加求和987+654+321+0=1962,取最後三位962當作散列地址。
摺疊法事先不需要知道關鍵字的分佈,適合關鍵字位數較多的情況。
5、除留餘數法:選擇一個適當的正整數p,用p去除關鍵字,取所得餘數作爲散列地址

f(key) = key % p (p<=m),m爲散列表長

事實上此法不僅可以直接取模,也可以在摺疊、平方取中後再取摸。本方法的關鍵在於選擇合適的p,p如果選的不好很容易產生同義詞。
如下面存在5個記錄(表長爲5)的散列表,選擇下標作爲哈希地址,p=11,其中只有12和144這兩個關鍵字地址有衝突。如果選擇p=12,我們就會出現很多衝突,構造更好地哈希函數就是爲了儘可能地減少衝突。p的選擇很重要。若散列表長爲m,通常p爲小於或等於表長(最好接近m)的最小質數或不包含小於20質因子的合數。
在這裏插入圖片描述
除留餘數法是最常用地構造散列函數方法。
6、隨機數法:選擇一個隨機數,取關鍵字的隨機函數值爲它的散列地址。

f(key) = random(key),其中random是隨機函數。

當關鍵字的長度不等時,採用這個方法構造散列函數比較合適。

綜上地,不同情況採用不同的散列函數,我們可以按照以下因素來選擇。
(1)計算散列地址所需地時間
(2)關鍵字的長度。
(3)散列表的大小。
(4)關鍵字的分佈情況。
(5)記錄查找的頻率。

三、處理散列衝突的方法

瞭解了除留餘數法的例子可以看出,我們設計再好的散列函數也不可能完全避免衝突,既然衝突不能避免,就要考慮如何處理它。
1、開放定址法:一旦發生了衝突,就去尋找下一個空的散列地址,只要數列表足夠大,空的散列地址總能找到,並將記錄存入。

fi(key) = (f(key)+di) % m, (di=1,2,3,…,m-1)

比如我們的關鍵字集合{12 ,67 ,51 2,16 ,37,48 },表長爲6,f(key)=f(key)%6.
當計算前面的4個數時,都是沒有衝突的散列地址。

下標 0 1 2 3 4 5
關鍵字 12 67 51 16

當key=37時,發現f(key)=1,此時與67所在位置衝突,f(key)=(f(key)+1)%6=2。

下標 0 1 2 3 4 5
關鍵字 12 67 37 51 16

當key=48時,f(48)=0,與12所在0的位置發生了衝突,當我們用到以上的公式di=1,還是衝突…di=2…直到di=5,也就是(f(48)+5)=5,纔沒有衝突。

下標 0 1 2 3 4 5
關鍵字 12 67 37 51 16 48

我們把這種解決衝突的開放定地址稱爲線性探測法。從這例子看出,48和37本來都不是同義詞卻要爭奪一個地址的情況,我們稱這種現象爲堆積。這樣不斷的處理衝突,無論存入還是查找效率都會大大降低。
爲了增進,可以改進這個公式:
①增加平方運算的目的是爲了不讓關鍵字都聚集在某一塊區域,這種方法叫做二次探測法。

fi(key) = (f(key)+di) % m ,(di=pow(1,2),-pow(1,2),pow(2,2),-pow(2,2),…,pow(q,2),–pow(q,2),q<=m/2)

②在衝突時,位移量di採用隨機函數計算得到,我們稱此法爲隨機探測法。

fi(key) = (f(key)+di) % m ,(di爲一個隨機數列)

開放定址法(我們常用的解決衝突的1辦法)適用於只要在散列表未填滿,總是能找到不發生衝突的地址。
2、再散列函數法
fi(key) = Hi(key) (i=1,2,3,…,k)
Hi就是不同的散列函數,把前面的什麼除留餘數、摺疊…全部用上,當發生衝突時,就換一個散列函數計算,總有一個可以把衝突解決掉,此法使得關鍵字不聚集,增加了計算時間。
3、鏈地址法:將關鍵字爲同義詞存儲在一個單鏈表中,我們稱爲這種表爲同義詞子表。
對於關鍵字集合{12,67,56,16,25,37,22,29,15,47,48,34},運用除留餘數法得到下面的鏈表:
在這裏插入圖片描述
此法不存在衝突換地址的問題了,只不過在當前位置增加結點的問題,鏈地址提供了絕不會出現炸不到地址的保障。但是查找時遍歷單鏈表造成性能的損耗。
4、公共溢出區法:包含兩個表:基本表和溢出表,凡是發生衝突的關鍵字都存儲在溢出表中
在這裏插入圖片描述
此法用於查找時,現在基本表查找,當關鍵字相等,則查找成功;如果不等就到溢出表進行順序查找

四、參考資料

1、大話數據結構
2、https://blog.csdn.net/yyyljw/article/details/80903391

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章