P3 解決哈希沖突的常用方法之開放尋址法


       解決哈希沖突的方法一般有:開放尋址法、鏈地址法(拉鏈法)、再哈希法和建立公共溢出區等方法。在 Java中為了解決Hash碰撞,ThreadLocalMap采用線性探測再散列的開放尋址法,LinkedHashMap采用鏈表法。本文介紹其中的開放尋址法。

開放尋址法概念

       開放尋址法:又稱開放定址法,當哈希碰撞發生時,從發生碰撞的那個單元起,按照一定的次序,從哈希表中尋找一個空閑的單元,然后把發生沖突的元素存入到該單元。這個空閑單元又稱為開放單元或者空白單元。

       查找時,如果探查到空白單元,即表中無待查的關鍵字,則查找失敗。開放尋址法需要的表長度要大於等於所需要存放的元素數量,非常適用於裝載因子較小(小於0.5)的散列表。

開放定址法的缺點在於刪除元素的時候不能真的刪除,否則會引起查找錯誤,只能做一個特殊標記,直到有下個元素插入才能真正刪除該元素。

       可以把開放尋址法想象成一個停車問題。若當前車位已經有車,則繼續往前開,直到找到一個空停車位。

        再散列法:Hi=RHi(key), i=1,2,…,k. RHi均是不同的散列函數,即在key產生地址沖突時計算另一個散列函數地址,直到沖突不再發生,這種方法不易產生“聚集”,但增加了計算時間。

開放尋址法函數

       開放尋址法的基本函數是:

Hi(key)=(H(key) + f(i)) MOD m, i=0,1,2,…, k(k<=m-1),

        其中,m 為散列表長度,一般為素數;

       H(key) 為散列函數,用於計算索引,key為關鍵字值;

       f(i) 為增量序列,用於解決沖突,且f(0) = 0,i為已經嘗試計算索引的次數。

       當散列值H0(key)發生沖突時,再計算H1(key)……,直到不沖突為止。

        實現步驟

             * 得到給定的 key;

             * 根據函數計算得 hashValue;

             * 若不沖突,則把關鍵字值存入下標為hashValue的桶;

             * 若沖突,則使 i++ ,也就是往后找,直到找到第一個空桶並填入當前key。若到了尾部則循環到前面。

       下面根據增量序列f(i)的不同構造方法,來選擇另外的空單元,介紹在開放尋址法中解決沖突的三種方法:線行探測再散列、平方探測再散列和雙散列。 

線性探測法

       沖突函數:是i的一次多項式,典型取法為f(i)=i。

       線行探查法(Linear Probing)是開放定址法中最簡單的沖突處理方法,它從發生沖突的單元起,依次判斷下一個單元是否為空,當達到最后一個單元時,再從表首依次判斷。直到碰到空閑的單元或者探查完全部單元為止。

       對於一個散列表,在散列過程中,某些元素形成一些區塊,這種現象稱作一次聚集(primary clustering)。就是說,散列到區塊中的任何關鍵字都需要多次探測才可以解決哈希碰撞,然后,把該關鍵字添加到相應區塊的桶中。

平方探測法

       沖突函數:是i的二次多項式,典型取法為f(i)=i^2。

       平方探測法(Quadratic Probing)即是發生沖突時,用發生沖突的單元H(key), 加上 1²、 2²等,即H(key) + 1²,H(key) + 2²,H(key) + 3²...直到找到空閑單元。f(i)也可以構造為:±i^2,i=1,2,3,...,k。

       在實際操作中,平方探測法不能探查到全部剩余的桶。不過在實際應用中,散列表如果大小是素數,並且至少有一半是空的,那么,總能夠插入一個新的關鍵字。若探查到一半桶仍未找一個空閑的,表明此散列表太滿,應該重哈希。平方探測法是解決線性探測中一次聚集問題的解決方法,但是,她引入了被稱為二次聚集的問題——散列到同一個桶的那些元素將探測到相同的備選桶。下面的技術將會排除這個遺憾,不過要付出計算一個附加的哈希函數的代價。

雙散列

       沖突函數:f(i) = i * hash2(key),典型取法是令hash2(key)=PRIME – (key % PRIME),其中 PRIME 是小於散列表大小的質數。

       雙散列(double hashing)使用兩個散列函數H(key)和hash2(key)。hash2(key)也以關鍵字為自變量,產生一個l至m-1之間的、並和m互素的數(即m不能被該數整除)作為探查序列的地址增量(即步長)。

       無論是線性探測還是二次探測,當裝載因子過高時,哈希表能否動態增長?

       要擴充哈希表,首先必須找下一個新的且夠大(大約2倍)的質數,然后必須考慮重哈希的成本。我們不可能原封不動的拷貝,必須要檢驗舊表格中的每個元素,計算其在新表格中的位置,然后再插入到新表格中。

開放尋址法實戰

       下面看一個哈希表的考研真題,題目摘自2010年全國碩士研究生入學統一考試計算機科學與技術學科聯考計算機學科專業基礎綜合試題:

       將關鍵字序列(7、8、30、11、18、9、14)散列存儲到散列表中。散列表的存儲空間是一個下標從0開始的一維數組,散列函數為H(key) = (keyx3) MOD 7,處理沖突采用線性探測再散列法,要求裝填(載)因子為0.7。

       (1) 請畫出所構造的散列表。

       (2) 分別計算等概率情況下查找成功和查找不成功的平均查找長度。

 ************************************

       解析:(1) 根據題意,我們可以確定哈希表的長度為 L = 7/0.7 = 10,因此此題需要構建的哈希表是下標為0~9的一維數組,記作array。

       當key=7時,根據散列函數H(key) = (keyx3) MOD 7, 可得 H(7) = (7x3)%7 =0,其他關鍵字的索引可同理計算,故得到如下散列函數值表:

key

7

8

30

11

18

9

14

H(key)

0

3

6

5

5

6

0

                       表1 散列函數值表

       表中有三組關鍵字7和14,30和9, 11和18的H(key)值相同,即在構建散列表時產生了哈希碰撞,所以要通過一定的沖突處理方法來解決這個問題。按關鍵字序列依次采用線性探測再散列法處理沖突,所構造的散列表為:

地址

0

1

2

3

4

5

6

7

8

9

關鍵字

7

14

 

8

 

11

30

18

9

 

                     表2 地址與關鍵字映射關系表

       下面詳細介紹如何構建散列表:

       第一個key 7,它的地址是0,array[0]上沒有關鍵字,因此沒有沖突可以直接填入位置0;

       第二個key 8,它的地址是3,array[3]上沒有關鍵字,因此沒有沖突可以直接填入;

       第三個key 30,它的地址是6,沒有沖突,因此放到散列表的數組下標為6的位置;

       第四個key 11,它的地址是5,沒有沖突,因此放到array[5];

       第五個key 18,它的地址是5,因為array[5]已經有關鍵字11,出現哈希碰撞,此時我們根據線性探測再散列法來處理這個沖突,探測下一個位置6, array[6]上已經存在關鍵字30則繼續增加步長1,因為array[7]上沒有關鍵字,放入即可,至此沖突已經解決;

       第六個key 9,它的地址是6,因此應該放到散列表的數組下標為6的位置,但是這個位置上已經有關鍵字30,遇到了沖突,探測下一個位置7, 而array[7]=18需繼續增加步長1,array[8]上沒有關鍵字,放入即可;

       第七個key 14,它的地址是0,但array[0]=7,探測下一個位置array[1], 桶中沒有關鍵字,放入即可。

       到這一步所有關鍵字均已填入,散列表已經構造完成,如表2所示。

 

       (2)關於等概率情況下查找成功平均查找長度ASL,可以根據第一問的構造過程求解:

       key 7一次就填入了表中,因此查找次數為1,同理8, 30, 11查找次數均為1; key 18 進行了3次放入操作,探測位置分別是5,6,7 ,因此查找次數為3;key 9也是3次;key 14 進行了兩次探測,因此查找次數為2。次數表如表3所示:

key

7

8

30

11

18

9

14

count

1

1

1

1

3

3

2

                                  表3 key-count mapping

       所以ASLsuccess= (1+1+1+1+3+3+2)/ 7 = 12/7。 

       接下來討論概率情況下查找不成功的平均查找長度, 看表2,計算查找不成功的次數就直接找關鍵字到第一個地址上關鍵字為空的距離即可,但根據哈希函數地址為MOD7,因此初始只可能在0~6的位置。等概率情況下,查找0~6位置查找失敗的查找次數為:

地址0,到第一個關鍵字為空的地址2的距離為3,因此查找不成功的次數為3;    

地址1, 到第一個關鍵字為空的地址2的距離為2,因此查找不成功的次數為2;

地址2,  到第一個關鍵字為空的地址2的距離為1,因此查找不成功的次數為1;

地址3,到第一個關鍵字為空的地址4的距離為2,因此查找不成功的次數為2;

地址4,到第一個關鍵字為空的地址4的距離為1,因此查找不成功的次數為1;

地址5,到第一個關鍵字為空的地址2(注意不是地址9,因為初始只可能在0~6之間,因此循環回去)的距離為5,因此查找不成功的次數為5;

地址6,到第一個關鍵字為空的地址2(注意不是地址9,因為初始只可能在0~6之間,因此循環回去)的距離為4,因此查找不成功的次數為4。

因此查找不成功的次數表如下表所示

key

7

8

30

11

18

9

14

count

3

2

1

2

1

5

4

                                  表4 表3 key-count error mapping

 

      所以ASLunsuccess= (3+2+1+2+1+5+4)/ 7 = 18/7。

  

Reference

 https://www.cnblogs.com/ricklz/p/9006424.html

https://www.cnblogs.com/heyanan/p/6912870.html

數據結構與算法分析(Java語言描述) 第三版


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM