解決哈希沖突的三種方法(拉鏈法、開放地址法、再散列法) - 小猛同學的博客 - CSDN博客 https://blog.csdn.net/qq_32595453/article/details/80660676
2018年06月12日 10:16:57
上篇博客我們說到了,什么是哈希沖突,其實就是再采用哈希函數對輸入域進行映射到哈希表的時候,因為哈希表的位桶的數目遠小於輸入域的關鍵字的個數,所以,對於輸入域的關鍵字來說,很可能會產生這樣一種情況,也就是,一個關鍵字會映射到同一個位桶中的情況,這種情況就就叫做哈希沖突,解決哈希沖突的有三種方案,一種叫做拉鏈法(也叫作鏈接法、鏈地址法,一個意思),另外三種分別為開發地址法和再散列法。
一、拉鏈法
上篇博文我們舉的例子,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),生成一個位隨機序列,並給定一個隨機數做起點,每次去加上這個偽隨機數++就可以了。
三、再散列法
再散列法其實很簡單,就是再使用哈希函數去散列一個輸入的時候,輸出是同一個位置就再次散列,直至不發生沖突位置
缺點:每次沖突都要重新散列,計算時間增加。
---------------------
解決hash沖突的三個方法 - 且聽風吟-wuchao - 博客園 https://www.cnblogs.com/wuchaodzxx/p/7396599.html
解決hash沖突的三個方法
通過構造性能良好的哈希函數,可以減少沖突,但一般不可能完全避免沖突,因此解決沖突是哈希法的另一個關鍵問題。創建哈希表和查找哈希表都會遇到沖突,兩種情況下解決沖突的方法應該一致。下面以創建哈希表為例,說明解決沖突的方法。常用的解決沖突方法有以下四種:
開放定址法
這種方法也稱再散列法,其基本思想是:當關鍵字key的哈希地址p=H(key)出現沖突時,以p為基礎,產生另一個哈希地址p1,如果p1仍然沖突,再以p為基礎,產生另一個哈希地址p2,…,直到找出一個不沖突的哈希地址pi ,將相應元素存入其中。這種方法有一個通用的再散列函數形式:
Hi=(H(key)+di)% m i=1,2,…,n
其中H(key)為哈希函數,m 為表長,di稱為增量序列。增量序列的取值方式不同,相應的再散列方式也不同。主要有以下三種:
線性探測再散列
dii=1,2,3,…,m-1
這種方法的特點是:沖突發生時,順序查看表中下一單元,直到找出一個空單元或查遍全表。
二次探測再散列
di=12,-12,22,-22,…,k2,-k2 ( k<=m/2 )
這種方法的特點是:沖突發生時,在表的左右進行跳躍式探測,比較靈活。
偽隨機探測再散列
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個單元中,因而查找、插入和刪除主要在同義詞鏈中進行。鏈地址法適用於經常進行插入和刪除的情況。
建立公共溢出區
這種方法的基本思想是:將哈希表分為基本表和溢出表兩部分,凡是和基本表發生沖突的元素,一律填入溢出表。