解決hash衝突的四種方法

原文地址:https://www.cnblogs.com/wuchaodzxx/p/7396599.html

通過構造性能良好的哈希函數,可以減少衝突,但一般不可能完全避免衝突,因此解決衝突是哈希法的另一個關鍵問題。創建哈希表和查找哈希表都會遇到衝突,兩種情況下解決衝突的方法應該一致。下面以創建哈希表爲例,說明解決衝突的方法。常用的解決衝突方法有以下四種:

一、開放定址法

這種方法也稱再散列法,其基本思想是:當關鍵字key的哈希地址p=H(key)出現衝突時,以p爲基礎,產生另一個哈希地址p1,如果p1仍然衝突,再以p爲基礎,產生另一個哈希地址p2,…,直到找出一個不衝突的哈希地址pi ,將相應元素存入其中。這種方法有一個通用的再散列函數形式:

Hi=(H(key)+di)% m   i=1,2,…,n

其中H(key)爲哈希函數,m 爲表長,di稱爲增量序列。增量序列的取值方式不同,相應的再散列方式也不同。主要有以下三種:

1.線性探測再散列

dii=1,2,3,…,m-1

這種方法的特點是:衝突發生時,順序查看錶中下一單元,直到找出一個空單元或查遍全表。

2.二次探測再散列

di=12,-12,22,-22,…,k2,-k2    ( k<=m/2 )

這種方法的特點是:衝突發生時,在表的左右進行跳躍式探測,比較靈活。

3.僞隨機探測再散列

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號單元。

如果用二次探測再散列處理衝突,下一個哈希地址爲H1=(3 + 12)% 11 = 4,仍然衝突,再找下一個哈希地址爲H2=(3 - 12)% 11 = 2,此時不再衝突,將69填入2號單元。

如果用僞隨機探測再散列處理衝突,且僞隨機數序列爲:2,5,9,……..,則下一個哈希地址爲H1=(3 + 2)% 11 = 5,仍然衝突,再找下一個哈希地址爲H2=(3 + 5)% 11 = 8,此時不再衝突,將69填入8號單元。

二、再哈希法

這種方法是同時構造多個不同的哈希函數:

Hi=RH1(key)  i=1,2,…,k

當哈希地址Hi=RH1(key)發生衝突時,再計算Hi=RH2(key)……,直到衝突不再產生。這種方法不易產生聚集,但增加了計算時間。

三、鏈地址法

這種方法的基本思想是將所有哈希地址爲i的元素構成一個稱爲同義詞鏈的單鏈表,並將單鏈表的頭指針存在哈希表的第i個單元中,因而查找、插入和刪除主要在同義詞鏈中進行。鏈地址法適用於經常進行插入和刪除的情況

四、建立公共溢出區

這種方法的基本思想是:將哈希表分爲基本表和溢出表兩部分,凡是和基本表發生衝突的元素,一律填入溢出表。
 

優缺點

開放散列(open hashing)/ 拉鍊法(針對桶鏈結構)

1)優點:

①對於記錄總數頻繁可變的情況,處理的比較好(也就是避免了動態調整的開銷) 

②由於記錄存儲在結點中,而結點是動態分配,不會造成內存的浪費,所以尤其適合那種記錄本身尺寸(size)很大的情況,因爲此時指針的開銷可以忽略不計了

③刪除記錄時,比較方便,直接通過指針操作即可

2)缺點:

①存儲的記錄是隨機分佈在內存中的,這樣在查詢記錄時,相比結構緊湊的數據類型(比如數組),哈希表的跳轉訪問會帶來額外的時間開銷

②如果所有的 key-value 對是可以提前預知,並之後不會發生變化時(即不允許插入和刪除),可以人爲創建一個不會產生衝突的完美哈希函數(perfect hash function),此時封閉散列的性能將遠高於開放散列

③由於使用指針,記錄不容易進行序列化(serialize)操作

封閉散列(closed hashing)/ 開放定址法

1)優點:

①記錄更容易進行序列化(serialize)操作

②如果記錄總數可以預知,可以創建完美哈希函數,此時處理數據的效率是非常高的

2)缺點:

①存儲記錄的數目不能超過桶數組的長度,如果超過就需要擴容,而擴容會導致某次操作的時間成本飆升,這在實時或者交互式應用中可能會是一個嚴重的缺陷 

②使用探測序列,有可能其計算的時間成本過高,導致哈希表的處理性能降低

③由於記錄是存放在桶數組中的,而桶數組必然存在空槽,所以當記錄本身尺寸(size)很大並且記錄總數規模很大時,空槽佔用的空間會導致明顯的內存浪費

④刪除記錄時,比較麻煩。比如需要刪除記錄a,記錄b是在a之後插入桶數組的,但是和記錄a有衝突,是通過探測序列再次跳轉找到的地址,所以如果直接刪除a,a的位置變爲空槽,而空槽是查詢記錄失敗的終止條件,這樣會導致記錄b在a的位置重新插入數據前不可見,所以不能直接刪除a,而是設置刪除標記。這就需要額外的空間和操作。

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