雖然我們不希望發生衝突,但實際上發生衝突的可能性仍是存在的。當關鍵字值域遠大於哈希表的長度,而且事先並不知道關鍵字的具體取值時。衝突就難免會發 生。另外,當關鍵字的實際取值大於哈希表的長度時,而且表中已裝滿了記錄,如果插入一個新記錄,不僅發生衝突,而且還會發生溢出。因此,處理衝突和溢出是 哈希技術中的兩個重要問題。
1、開放定址法
用開放定址法解決衝突的做法是:當衝突發生時,使用某種探查(亦稱探測)技術在散列表中形成一個探查(測)序列。沿此序列逐個單元地查找,直到找到給定 的關鍵字,或者碰到一個開放的地址(即該地址單元爲空)爲止(若要插入,在探查到開放的地址,則可將待插入的新結點存人該地址單元)。查找時探查到開放的 地址則表明表中無待查的關鍵字,即查找失敗。
注意:
①用開放定址法建立散列表時,建表前須將表中所有單元(更嚴格地說,是指單元中存儲的關鍵字)置空。
②空單元的表示與具體的應用相關。
按照形成探查序列的方法不同,可將開放定址法區分爲線性探查法、線性補償探測法、隨機探測等。
(1)線性探查法(Linear Probing)
該方法的基本思想是:
將散列表T[0..m-1]看成是一個循環向量,若初始探查的地址爲d(即h(key)=d),則最長的探查序列爲:
d,d+l,d+2,…,m-1,0,1,…,d-1
即:探查時從地址d開始,首先探查T[d],然後依次探查T[d+1],…,直到T[m-1],此後又循環到T[0],T[1],…,直到探查到T[d-1]爲止。
探查過程終止於三種情況:
(1)若當前探查的單元爲空,則表示查找失敗(若是插入則將key寫入其中);
(2)若當前探查的單元中含有key,則查找成功,但對於插入意味着失敗;
(3)若探查到T[d-1]時仍未發現空單元也未找到key,則無論是查找還是插入均意味着失敗(此時表滿)。
利用開放地址法的一般形式,線性探查法的探查序列爲:
hi=(h(key)+i)%m 0≤i≤m-1 //即di=i
用線性探測法處理衝突,思路清晰,算法簡單,但存在下列缺點:
① 處理溢出需另編程序。一般可另外設立一個溢出表,專門用來存放上述哈希表中放不下的記錄。此溢出表最簡單的結構是順序表,查找方法可用順序查找。
② 按上述算法建立起來的哈希表,刪除工作非常困難。假如要從哈希表 HT 中刪除一個記錄,按理應將這個記錄所在位置置爲空,但我們不能這樣做,而只能標上已被刪除的標記,否則,將會影響以後的查找。
③ 線性探測法很容易產生堆聚現象。所謂堆聚現象,就是存入哈希表的記錄在表中連成一片。按照線性探測法處理衝突,如果生成哈希地址的連續序列愈長 ( 即不同關鍵字值的哈希地址相鄰在一起愈長 ) ,則當新的記錄加入該表時,與這個序列發生衝突的可能性愈大。因此,哈希地址的較長連續序列比較短連續序列生長得快,這就意味着,一旦出現堆聚 ( 伴隨着衝突 ) ,就將引起進一步的堆聚。
(2)線性補償探測法
線性補償探測法的基本思想是:
將線性探測的步長從 1 改爲 Q ,即將上述算法中的 j = (j + 1) % m 改爲: j = (j + Q) % m ,而且要求 Q 與 m 是互質的,以便能探測到哈希表中的所有單元。
【例】 PDP-11 小型計算機中的彙編程序所用的符合表,就採用此方法來解決衝突,所用表長 m = 1321 ,選用 Q = 25 。
(3)隨機探測
隨機探測的基本思想是:
將線性探測的步長從常數改爲隨機數,即令: j = (j + RN) % m ,其中 RN 是一個隨機數。在實際程序中應預先用隨機數發生器產生一個隨機序列,將此序列作爲依次探測的步長。這樣就能使不同的關鍵字具有不同的探測次序,從而可以避 免或減少堆聚。基於與線性探測法相同的理由,在線性補償探測法和隨機探測法中,刪除一個記錄後也要打上刪除標記。
2、拉鍊法
(1)拉鍊法解決衝突的方法
拉鍊法解決衝突的做法是:將所有關鍵字爲同義詞的結點鏈接在同一個單鏈表中。若選定的散列表長度爲m,則可將散列表定義爲一個由m個頭指針組成的指針數 組T[0..m-1]。凡是散列地址爲i的結點,均插入到以T[i]爲頭指針的單鏈表中。T中各分量的初值均應爲空指針。在拉鍊法中,裝填因子α可以大於 1,但一般均取α≤1。
【例】設有 m = 5 , H(K) = K mod 5 ,關鍵字值序例 5 , 21 , 17 , 9 , 15 , 36 , 41 , 24 ,按外鏈地址法所建立的哈希表如下圖所示:
(2)拉鍊法的優點
與開放定址法相比,拉鍊法有如下幾個優點:
①拉鍊法處理衝突簡單,且無堆積現象,即非同義詞決不會發生衝突,因此平均查找長度較短;
②由於拉鍊法中各鏈表上的結點空間是動態申請的,故它更適合於造表前無法確定表長的情況;
③開放定址法爲減少衝突,要求裝填因子α較小,故當結點規模較大時會浪費很多空間。而拉鍊法中可取α≥1,且結點較大時,拉鍊法中增加的指針域可忽略不計,因此節省空間;
④在用拉鍊法構造的散列表中,刪除結點的操作易於實現。只要簡單地刪去鏈表上相應的結點即可。而對開放地址法構造的散列表,刪除結點不能簡單地將被刪結 點的空間置爲空,否則將截斷在它之後填人散列表的同義詞結點的查找路徑。這是因爲各種開放地址法中,空地址單元(即開放地址)都是查找失敗的條件。因此在 用開放地址法處理衝突的散列表上執行刪除操作,只能在被刪結點上做刪除標記,而不能真正刪除結點。
(3)拉鍊法的缺點
拉鍊法的缺點是:指針需要額外的空間,故當結點規模較小時,開放定址法較爲節省空間,而若將節省的指
哈希表及處理衝突的方法
哈希法又稱散列法、雜湊法以及關鍵字地址計算法等,相應的表稱爲哈希表。這種方法的基本思想是:首先在元素的關鍵字k和元素的存儲位置p之間建立一個對應關係f,使得p=f(k),f稱爲哈希函數。創建哈希表時,把關鍵字爲k的元素直接存入地址爲f(k)的單元;以後當查找關鍵字爲k的元素時,再利用哈希函數計算出該元素的存儲位置p=f(k),從而達到按關鍵字直接存取元素的目的。
當關鍵字集合很大時,關鍵字值不同的元素可能會映象到哈希表的同一地址上,即 k1≠k2 ,但 H(k1)=H(k2),這種現象稱爲衝突,此時稱k1和k2爲同義詞。實際中,衝突是不可避免的,只能通過改進哈希函數的性能來減少衝突。
綜上所述,哈希法主要包括以下兩方面的內容:
1)如何構造哈希函數
2)如何處理衝突。
8.4.1 哈希函數的構造方法
構造哈希函數的原則是:①函數本身便於計算;②計算出來的地址分佈均勻,即對任一關鍵字k,f(k) 對應不同地址的概率相等,目的是儘可能減少衝突。
下面介紹構造哈希函數常用的五種方法。
1. 數字分析法
如果事先知道關鍵字集合,並且每個關鍵字的位數比哈希表的地址碼位數多時,可以從關鍵字中選出分佈較均勻的若干位,構成哈希地址。例如,有80個記錄,關鍵字爲8位十進制整數d1d2d3…d7d8,如哈希表長取100,則哈希表的地址空間爲:00~99。假設經過分析,各關鍵字中 d4和d7的取值分佈較均勻,則哈希函數爲:h(key)=h(d1d2d3…d7d8)=d4d7。例如,h(81346532)=43,h(81301367)=06。相反,假設經過分析,各關鍵字中 d1和d8的取值分佈極不均勻, d1 都等於5,d8 都等於2,此時,如果哈希函數爲:h(key)=h(d1d2d3…d7d8)=d1d8,則所有關鍵字的地址碼都是52,顯然不可取。
2. 平方取中法
當無法確定關鍵字中哪幾位分佈較均勻時,可以先求出關鍵字的平方值,然後按需要取平方值的中間幾位作爲哈希地址。這是因爲:平方後中間幾位和關鍵字中每一位都相關,故不同關鍵字會以較高的概率產生不同的哈希地址。
例:我們把英文字母在字母表中的位置序號作爲該英文字母的內部編碼。例如K的內部編碼爲11,E的內部編碼爲05,Y的內部編碼爲25,A的內部編碼爲01, B的內部編碼爲02。由此組成關鍵字“KEYA”的內部代碼爲11052501,同理我們可以得到關鍵字“KYAB”、“AKEY”、“BKEY”的內部編碼。之後對關鍵字進行平方運算後,取出第7到第9位作爲該關鍵字哈希地址,如圖8.23所示。
關鍵字 | 內部編碼 | 內部編碼的平方值 | H(k)關鍵字的哈希地址 |
KEYA | 11050201 | 122157778355001 | 778 |
KYAB | 11250102 | 126564795010404 | 795 |
AKEY | 01110525 | 001233265775625 | 265 |
BKEY | 02110525 | 004454315775625 | 315 |
圖8.23平方取中法求得的哈希地址
3. 分段疊加法
這種方法是按哈希表地址位數將關鍵字分成位數相等的幾部分(最後一部分可以較短),然後將這幾部分相加,捨棄最高進位後的結果就是該關鍵字的哈希地址。具體方法有摺疊法與移位法。移位法是將分割後的每部分低位對齊相加,摺疊法是從一端向另一端沿分割界來回摺疊(奇數段爲正序,偶數段爲倒序),然後將各段相加。例如:key=12360324711202065,哈希表長度爲1000,則應把關鍵字分成3位一段,在此捨去最低的兩位65,分別進行移位疊加和摺疊疊加,求得哈希地址爲105和907,如圖8.24所示。
1 2 3 1 2 3
6 0 3 3 0 6
2 4 7 2 4 7
1 1 2 2 1 1
+) 0 2 0 +) 0 2 0
———————— —————————
1 1 0 5 9 0 7
(a)移位疊加 (b) 摺疊疊加
圖8.24 由疊加法求哈希地址
4. 除留餘數法
假設哈希表長爲m,p爲小於等於m的最大素數,則哈希函數爲
h(k)=k % p ,其中%爲模p取餘運算。
例如,已知待散列元素爲(18,75,60,43,54,90,46),表長m=10,p=7,則有
h(18)=18 % 7=4 h(75)=75 % 7=5 h(60)=60 % 7=4
h(43)=43 % 7=1 h(54)=54 % 7=5 h(90)=90 % 7=6
h(46)=46 % 7=4
此時衝突較多。爲減少衝突,可取較大的m值和p值,如m=p=13,結果如下:
h(18)=18 % 13=5 h(75)=75 % 13=10 h(60)=60 % 13=8
h(43)=43 % 13=4 h(54)=54 % 13=2 h(90)=90 % 13=12
h(46)=46 % 13=7
此時沒有衝突,如圖8.25所示。
0 1 2 3 4 5 6 7 8 9 10 11 12
54 | 43 | 18 | 46 | 60 | 75 | 90 |
圖8.25 除留餘數法求哈希地址
5. 僞隨機數法
採用一個僞隨機函數做哈希函數,即h(key)=random(key)。
在實際應用中,應根據具體情況,靈活採用不同的方法,並用實際數據測試它的性能,以便做出正確判定。通常應考慮以下五個因素 :
l 計算哈希函數所需時間 (簡單)。
l 關鍵字的長度。
l 哈希表大小。
l 關鍵字分佈情況。
l 記錄查找頻率
8.4.2 處理衝突的方法
通過構造性能良好的哈希函數,可以減少衝突,但一般不可能完全避免衝突,因此解決衝突是哈希法的另一個關鍵問題。創建哈希表和查找哈希表都會遇到衝突,兩種情況下解決衝突的方法應該一致。下面以創建哈希表爲例,說明解決衝突的方法。常用的解決衝突方法有以下四種:
1. 開放定址法
這種方法也稱再散列法,其基本思想是:當關鍵字key的哈希地址p=H(key)出現衝突時,以p爲基礎,產生另一個哈希地址p1,如果p1仍然衝突,再以p爲基礎,產生另一個哈希地址p2,…,直到找出一個不衝突的哈希地址pi ,將相應元素存入其中。這種方法有一個通用的再散列函數形式:
Hi=(H(key)+di)% m i=1,2,…,n
其中H(key)爲哈希函數,m 爲表長,di稱爲增量序列。增量序列的取值方式不同,相應的再散列方式也不同。主要有以下三種:
l 線性探測再散列
dii=1,2,3,…,m-1
這種方法的特點是:衝突發生時,順序查看錶中下一單元,直到找出一個空單元或查遍全表。
l 二次探測再散列
di=12,-12,22,-22,…,k2,-k2 ( k<=m/2 )
這種方法的特點是:衝突發生時,在表的左右進行跳躍式探測,比較靈活。
l 僞隨機探測再散列
di=僞隨機數序列。
具體實現時,應建立一個僞隨機數發生器,(如i=(i+p) % m),並給定一個隨機數做起點。
例如,已知哈希表長度m=11,哈希函數爲:H(key)= key % 11,則H(47)=3,H(26)=4,H(60)=5,假設下一個關鍵字爲69,則H(69)=3,與47衝突。如果用線性探測再散列處理衝突,下一個哈希地址爲H1=(3 + 1)% 11 = 4,仍然衝突,再找下一個哈希地址爲H2=(3 + 2)% 11 = 5,還是衝突,繼續找下一個哈希地址爲H3=(3 + 3)% 11 = 6,此時不再衝突,將69填入5號單元,參圖8.26 (a)。如果用二次探測再散列處理衝突,下一個哈希地址爲H1=(3 + 12)% 11 = 4,仍然衝突,再找下一個哈希地址爲H2=(3 - 12)% 11 = 2,此時不再衝突,將69填入2號單元,參圖8.26 (b)。如果用僞隨機探測再散列處理衝突,且僞隨機數序列爲:2,5,9,……..,則下一個哈希地址爲H1=(3 + 2)% 11 = 5,仍然衝突,再找下一個哈希地址爲H2=(3 + 5)% 11 = 8,此時不再衝突,將69填入8號單元,參圖8.26 (c)。
0 1 2 3 4 5 6 7 8 9 10
47 | 26 | 60 | 69 |
(a) 用線性探測再散列處理衝突
0 1 2 3 4 5 6 7 8 9 10
69 | 47 | 26 | 60 |
(b) 用二次探測再散列處理衝突
0 1 2 3 4 5 6 7 8 9 10
47 | 26 | 60 | 69 |
(c) 用僞隨機探測再散列處理衝突
圖8.26開放地址法處理衝突
從上述例子可以看出,線性探測再散列容易產生“二次聚集”,即在處理同義詞的衝突時又導致非同義詞的衝突。例如,當表中i, i+1 ,i+2三個單元已滿時,下一個哈希地址爲i, 或i+1 ,或i+2,或i+3的元素,都將填入i+3這同一個單元,而這四個元素並非同義詞。線性探測再散列的優點是:只要哈希表不滿,就一定能找到一個不衝突的哈希地址,而二次探測再散列和僞隨機探測再散列則不一定。
2. 再哈希法
這種方法是同時構造多個不同的哈希函數:
Hi=RH1(key) i=1,2,…,k
當哈希地址Hi=RH1(key)發生衝突時,再計算Hi=RH2(key)……,直到衝突不再產生。這種方法不易產生聚集,但增加了計算時間。
3. 鏈地址法
這種方法的基本思想是將所有哈希地址爲i的元素構成一個稱爲同義詞鏈的單鏈表,並將單鏈表的頭指針存在哈希表的第i個單元中,因而查找、插入和刪除主要在同義詞鏈中進行。鏈地址法適用於經常進行插入和刪除的情況。
例如,已知一組關鍵字(32,40,36,53,16,46,71,27,42,24,49,64),哈希表長度爲13,哈希函數爲:H(key)= key % 13,則用鏈地址法處理衝突的結果如圖8.27所示:
圖8.27 鏈地址法處理衝突時的哈希表
本例的平均查找長度 ASL=(1*7+2*4+3*1)=1.5
4、建立公共溢出區
這種方法的基本思想是:將哈希表分爲基本表和溢出表兩部分,凡是和基本表發生衝突的元素,一律填入溢出表