解決哈希衝突的三種方法(拉鍊法、開放地址法、再散列法)

什麼是哈希衝突,其實就是再採用哈希函數對輸入域進行映射到哈希表的時候,因爲哈希表的位桶的數目遠小於輸入域的關鍵字的個數,所以,對於輸入域的關鍵字來說,很可能會產生這樣一種情況,也就是,一個關鍵字會映射到同一個位桶中的情況,這種情況就就叫做哈希衝突,解決哈希衝突的有三種方案,一種叫做拉鍊法(也叫作鏈接法、鏈地址法,一個意思),另外三種分別爲開發地址法和再散列法。

一、拉鍊法

上篇博文我們舉的例子,HashMap,HashSet其實都是採用的拉鍊法來解決哈希衝突的,就是在每個位桶實現的時候,我們採用鏈表(jdk1.8之後採用鏈表+紅黑樹)的數據結構來去存取發生哈希衝突的輸入域的關鍵字(也就是被哈希函數映射到同一個位桶上的關鍵字)。首先來看使用拉鍊法解決哈希衝突的幾個操作:

①插入操作:在發生哈希衝突的時候,我們輸入域的關鍵字去映射到位桶(實際上是實現位桶的這個數據結構,鏈表或者紅黑樹)中去的時候,我們先檢查帶插入元素x是否出現在表中,很明顯,這個查找所用的次數不會超過裝載因子(n/m:n爲輸入域的關鍵字個數,m爲位桶的數目),它是個常數,所以插入操作的最壞時間複雜度爲O(1)的。

②查詢操作:和①一樣,在發生哈希衝突的時候,我們去檢索的時間複雜度不會超過裝載因子,也就是檢索數據的時間複雜度也是O(1)的

③刪除操作:如果在拉鍊法中我們想要使用鏈表這種數據結構來實現位桶,那麼這個鏈表一定是雙向鏈表,因爲在刪除一個元素x的時候,需要更改x的前驅元素的next指針的屬性,把x從鏈表中刪除。這個操作的時間複雜度也是O(1)的。

拉鍊法的優點

與開放定址法相比,拉鍊法有如下幾個優點:

①拉鍊法處理衝突簡單,且無堆積現象,即非同義詞決不會發生衝突,因此平均查找長度較短;

②由於拉鍊法中各鏈表上的結點空間是動態申請的,故它更適合於造表前無法確定表長的情況;

③開放定址法爲減少衝突,要求裝填因子α較小,故當結點規模較大時會浪費很多空間。而拉鍊法中可取α≥1,且結點較大時,拉鍊法中增加的指針域可忽略不計,因此節省空間;

④在用拉鍊法構造的散列表中,刪除結點的操作易於實現。只要簡單地刪去鏈表上相應的結點即可。

拉鍊法的缺點

指針需要額外的空間,故當結點規模較小時,開放定址法較爲節省空間,而若將節省的指針空間用來擴大散列表的規模,可使裝填因子變小,這又減少了開放定址法中的衝突,從而提高平均查找速度。

使用例子:

HashMap

二、開放地址法

開放地址法有個非常關鍵的特徵,就是所有輸入的元素全部存放在哈希表裏,也就是說,位桶的實現是不需要任何的鏈表來實現的,換句話說,也就是這個哈希表的裝載因子不會超過1。它的實現是在插入一個元素的時候,先通過哈希函數進行判斷,若是發生哈希衝突,就以當前地址爲基準,根據再尋址的方法(探查序列),去尋找下一個地址,若發生衝突再去尋找,直至找到一個爲空的地址爲止。所以這種方法又稱爲再散列法。

有幾種常用的探查序列的方法:

①線性探查

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

(使用例子:ThreadLocal裏面的ThreadLocalMap)

②二次探查

di=12,-12,22,-22,…,k2,-k2 ( k<=m/2 );這種方法的特點是:衝突發生時,在表的左右進行跳躍式探測,比較靈活。

③ 僞隨機探測

di=僞隨機數序列;具體實現時,應建立一個僞隨機數發生器,(如i=(i+p) % m),生成一個位隨機序列,並給定一個隨機數做起點,每次去加上這個僞隨機數++就可以了。

三、再散列法

再散列法其實很簡單,就是再使用哈希函數去散列一個輸入的時候,輸出是同一個位置就再次散列,直至不發生衝突位置

缺點:每次衝突都要重新散列,計算時間增加。

發佈了53 篇原創文章 · 獲贊 20 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章