“處理沖突”的含義是:為產生沖突的關鍵字尋找下一個哈希地址。通常有兩類方法處理沖突:開放定址(Open Addressing)法和拉鏈(Chaining)法。前者是將所有結點均存放在散列表T[0..m-1]中;后者通常是將互為同義詞的結點鏈成一個單鏈表,而將此鏈表的頭指針放在散列表T[0..m-1]中。
1 開放地址法
這個方法的基本思想是:當發生地址沖突時,按照某種方法繼續探測哈希表中的其他存儲單元,直到找到空位置為止。這個過程可用下式描述:
H i ( key ) = ( H ( key )+ d i ) mod m ( i = 1,2,…… , k ( k ≤ m – 1))
其中: H ( key ) 為關鍵字 key 的直接哈希地址, m 為哈希表的長度, di 為每次再探測時的地址增量。
采用這種方法時,首先計算出元素的直接哈希地址 H ( key ) ,如果該存儲單元已被其他元素占用,則繼續查看地址為 H ( key ) + d 2 的存儲單元,如此重復直至找到某個存儲單元為空時,將關鍵字為 key 的數據元素存放到該單元。
增量 d 可以有不同的取法,並根據其取法有不同的稱呼:
( 1 ) d i = 1 , 2 , 3 , …… 線性探測再散列;
( 2 ) d i = 1^2 ,- 1^2 , 2^2 ,- 2^2 , k^2, -k^2…… 二次探測再散列;
( 3 ) d i = 偽隨機序列 偽隨機再散列;
例1設有哈希函數 H ( key ) = key mod 7 ,哈希表的地址空間為 0 ~ 6 ,對關鍵字序列( 32 , 13 , 49 , 55 , 22 , 38 , 21 )按線性探測再散列和二次探測再散列的方法分別構造哈希表。
①線性探查法(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,則無論是查找還是插入均意味着失敗(此時表滿)。
聚集或堆積現象
用線性探查法解決沖突時,當表中i,i+1,…,i+k的位置上已有結點時,一個散列地址為i,i+1,…,i+k+1的結點都將插入在位置i+k+1上。把這種散列地址不同的結點爭奪同一個后繼散列地址的現象稱為聚集或堆積(Clustering)。這將造成不是同義詞的結點也處在同一個探查序列之中,從而增加了探查序列的長度,即增加了查找時間。若散列函數不好或裝填因子過大,都會使堆積現象加劇。
②二次探查法(Quadratic Probing)
二次探查法的探查序列是:
hi=(h(key)+i*i)%m 0≤i≤m-1 //即di=i2
即探查序列為d=h(key),d+12,d+22,…,等。
該方法的缺陷是不易探查到整個散列空間。
③雙重散列法(Double Hashing)
該方法是開放定址法中最好的方法之一,它的探查序列是:
hi=(h(key)+i*h1(key))%m 0≤i≤m-1 //即di=i*h1(key)
即探查序列為:
d=h(key),(d+h1(key))%m,(d+2h1(key))%m,…,等。
該方法使用了兩個散列函數h(key)和h1(key),故也稱為雙散列函數探查法。
注意:
定義h1(key)的方法較多,但無論采用什么方法定義,都必須使h1(key)的值和m互素,才能使發生沖突的同義詞地址均勻地分布在整個表中,否則可能造成同義詞地址的循環計算。
【例】 若m為素數,則h1(key)取1到m-1之間的任何數均與m互素,因此,我們可以簡單地將它定義為:
h1(key)=key%(m-2)+1
【例】對例9.1,我們可取h(key)=key%13,而h1(key)=key%11+1。
【例】若m是2的方冪,則h1(key)可取1到m-1之間的任何奇數。
2、拉鏈法
(1)拉鏈法解決沖突的方法
拉鏈法解決沖突的做法是:將所有關鍵字為同義詞的結點鏈接在同一個單鏈表中。若選定的散列表長度為m,則可將散列表定義為一個由m個頭指針組成的指針數組T[0..m-1]。凡是散列地址為i的結點,均插入到以T[i]為頭指針的單鏈表中。T中各分量的初值均應為空指針。在拉鏈法中,裝填因子α可以大於1,但一般均取α≤1。
(2)拉鏈法的優點
與開放定址法相比,拉鏈法有如下幾個優點:
(1)拉鏈法處理沖突簡單,且無堆積現象,即非同義詞決不會發生沖突,因此平均查找長度較短;
(2)由於拉鏈法中各鏈表上的結點空間是動態申請的,故它更適合於造表前無法確定表長的情況;
(3)開放定址法為減少沖突,要求裝填因子α較小,故當結點規模較大時會浪費很多空間。而拉鏈法中可取α≥1,且結點較大時,拉鏈法中增加的指針域可忽略不計,因此節省空間;
(4)在用拉鏈法構造的散列表中,刪除結點的操作易於實現。只要簡單地刪去鏈表上相應的結點即可。而對開放地址法構造的散列表,刪除結點不能簡單地將被刪結點的空間置為空,否則將截斷在它之后填人散列表的同義詞結點的查找路徑。這是因為各種開放地址法中,空地址單元(即開放地址)都是查找失敗的條件。因此在用開放地址法處理沖突的散列表上執行刪除操作,只能在被刪結點上做刪除標記,而不能真正刪除結點。
(3)拉鏈法的缺點
拉鏈法的缺點是:指針需要額外的空間,故當結點規模較小時,開放定址法較為節省空間,而若將節省的指針空間用來擴大散列表的規模,可使裝填因子變小,這又減少了開放定址法中的沖突,從而提高平均查找速度。
3、建立一個公共溢出區
假設哈希函數的值域為[0,m-1],則設向量HashTable[0..m-1]為基本表,另外設立存儲空間向量OverTable[0..v]用以存儲發生沖突的記錄。經過以上方法,基本可以解決掉hash算法沖突的問題。