算法預備軍(5)~散列表

散列表又稱爲Hash表,核心體現在Hash算法上,而Hash算法又是加密算法的一種,所以我們很有必要去了解一下散列表。

一些概念

我們看一個公式:存儲位置=f(關鍵字),我們將根據這個公式來理解散列技術與散列表的概念。

散列技術:散列技術是在記錄的存儲位置和它的關鍵字之間建立一個確定的對應關係f,使得每個關鍵字key對應一個存儲位置f(key)。查找時,根據這個確定的對應關係找到給定值key的映射f(key),若查找集合中存在這個記錄,則必定在f(key)的位置上。

哈希函數:我們把上述公式中的對應關係f稱爲散列函數,又稱爲哈希函數。

散列表(哈希表):採用散列技術將記錄存儲在一塊連續的存儲空間中,這塊連續存儲空間稱爲散列表或哈希表,關鍵字對應的記錄存儲位置我們稱爲散列地址。

散列技術既是一種存儲方法,也是一種查找方法,不過有一點需要注意的是,散列技術的記錄之間不存在什麼邏輯關係,它只與關鍵字有關聯。雖說散列技術既是存儲技術又是查找技術,但是散列主要是面向查找的存儲結構。

散列技術最適合的求解問題是查找與給定值相等的記錄。不適合求解一個關鍵字對應多個記錄的問題,也不適合查找範圍問題,最大值問題。

哈希衝突:在理想情況下,每一個關鍵字,通過散列函數計算出來的地址都是不一樣的,但是這只是理想情況。我們時常會碰到兩個關鍵字key1!=key2,但是卻有f(key1)=f(key2),這種現象我們稱爲衝突,並把key1和key2稱爲這個散列函數的同義詞。在Java的集合框架中,HashMap、Hashtable、ConcurrentHashMap都通過拉鍊法來解決的衝突。

散列技術的關鍵之處在於如何設計一個簡單、均勻、存儲利用率高的散列函數。

散列函數的好壞我們可以根據兩個原則來衡量:(1)計算是否簡單(2)散列地址是否分佈的均勻。本來我們使用散列函數就是爲了減少查找所需的時間,如果計算哈希值的過程複雜,耗費的時間較長,就失去了散列函數的意義。散列地址的均勻分佈是爲了減少衝突的發生,如果發生衝突,處理衝突也要花一定的時間。

常用的散列函數的構造方法

常用的散列函數的構造方法有:直接定址法、數字分析法、平方取中法、摺疊法、除留餘數法、隨機數法。

直接定址法:我們可以取關鍵字的某個線性函數值爲散列地址,即f(key)=a*key+b(a,b爲常數,注意a不能爲0)。這樣的散列函數優點就是簡單、均勻,也不會產生衝突,但問題是這需要事先知道關鍵字的分佈情況,適合查找表較小且連續的情況。由於這樣的限制,在現實應用中,此方法雖然簡單,但卻並不常用。

數字分析法:數字分析法是指抽取關鍵字的一部分來計算散列存儲位置的方法,如散列手機號碼,根據手機號的特點,我們可以選取後四位來做散列運算。數字分析法通常適合處理關鍵字位數比較大的情況,如果事先知道關鍵字的分佈且關鍵字的若干位分佈較均勻,就可以考慮用這個方法。

平方取中法:這個方法計算很簡單,假設關鍵字是1234,那麼它的平方就是1522756,再抽取中間的3位就是227,用作散列地址。平方取中法比較適合於不知道關鍵字的分佈,而位數又不是很大的情況。

摺疊法:摺疊法是將關鍵字從左到右分割成位數相等的幾部分(注意最後一部分位數不夠時可以短些),然後將這幾部分疊加求和,並按散列表表長,取後幾位作爲散列地址。123/456/789/0
,將它們求和爲1368,再求後3位得到散列地址爲368。摺疊法事先不需要知道關鍵字的分佈,適合關鍵字位數較多的情況。

除留餘數法:此方法爲最常用的構造散列函數方法。對於散列表長爲m的散列函數公式爲:f(key)=key mod p(p<=m),mod是取模(求餘數)的意思。事實上,這方法不僅可以對關鍵字直接取模,也可在摺疊、平方取中後再取模。很顯然,本方法的關鍵就在於選擇合適的p,p如果選擇不好,就可能會容易產生同義詞。根據前輩們的經驗,若散列表表長爲m,通常p爲小於或等於表長(最好接近m)的最小質數或不包含小於20質因子的合數。

隨機數法:選擇一個隨機數,取關鍵字的隨機函數值爲它的散列地址。也就是f(key)=random(key)。這裏random是隨機函數。當關鍵字的長度不等時,採用這個方法構造散列函數是比較合適的。

現實中,應該視不同的情況採用不同的散列函數。這裏只能給出一些考慮的因素來提供參考:
1.計算散列地址所需的時間
2.關鍵字的長度
3.散列表的大小
4.關鍵字的分佈情況
5.記錄查找的頻率
綜合這些因素,才能決策選擇哪種散列函數更合適。

處理散列衝突的方法

處理散列衝突的方法常用的有:開放定址法、再散列函數法、鏈地址法、公共溢出區法。

開放定址法:所謂的開放定址法就是一旦發生了衝突,就去尋找下一個空的散列地址,只要散列表足夠大,空的散列地址總能找到,並將記錄存入。它的公式爲: fi(key)=(f(key)+di) MOD m。根據d的取值方式可以有線性探測法、二次探測法、隨機探測法等。
當d=1,2,3,4….,m-1時,我們稱上述公式解決衝突的開放定址法爲線性探測法。
當d=1*1,-1*1,2*2,-2*2,…,q*q,-q*q,q<=m/2時,我們稱上述公式解決衝突的開放定址法爲二次探測法,這裏增加平方運算的目的是爲了不讓關鍵字都聚集在某一塊區域。
當d爲採用隨機函數計算得到時,我們稱上述公式解決衝突的開放定址法爲隨機探測法。
在用開放定址法處理衝突時,會遇到堆積現象。堆積是指本來不是同義詞的關鍵字卻需要爭奪同一個地址的情況。總之,開放定址法只要在散列表未填滿時,總是能找到不發生衝突的地址,時我們常用的解決衝突的辦法。

再散列函數法:再散列函數法是指我們事先準備多個散列函數,當發生散列地址衝突時,就換一個散列函數計算,相信總會有一個可以把衝突解決掉。這種方法能夠使得關鍵字不產生聚集,當然,相應地也增加了計算的時間。其公式如下:
fi(key)=RHi(key)(i=1,2,…,k),這裏RH就是不同的散列函數,可以將前面說的摺疊、平方取中等方法都用上。

鏈地址法:讀過HashMap源碼的同學肯定對這種方法很熟悉,鏈地址法是在衝突的位置上引入單鏈表來解決衝突的,這種方法對於可能會造成很多衝突的散列函數來說,提供了絕不會出現找不到地址的保障。當然,這也就帶來了查找時需要遍歷單鏈表的性能損耗。

公共溢出區法:爲所有衝突的關鍵字建立一個公共的溢出區來存放,在查找時,對給定值通過散列函數計算出散列地址後,先與基本表的相應位置進行比對,如果相等,則查找成功;如果查找不相等,則到溢出表去進行順序查找。

散列表的平均查找長度取決於裝填因子,而不是取決於查找集合中的記錄個數。所謂的裝填因子a=填入表中的記錄個數/散列表長度。a標誌着散列表的裝滿的程度。當填入表中的記錄越多,a就越大,產生衝突的可能性就越大。

這個系列在第一篇文章裏就說了,內容是針對《大話數據結構》的筆記。轉載請註明出處:http://blog.csdn.net/android_jiangjun/article/details/78589565

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