數據結構查找-散列查找(哈希查找)


通常我們查找數據都是通過一個一個地比較來進行,有一種方法,要尋找的數據與其在數據集中的位置存在一種對應的關系,通過這種關系就能找到數據的位置。這個對應關系成為散列函數(哈希函數),因此建立的表為散列表(哈希表)。

散列查找是關鍵字與在數據集中的位置一一對應,通過這種對應關系能快速地找到數據,散列查找中散列函數的構造和處理沖突的方法尤為重要

=======================================================

散列函數的構造

構造哈希表的前提是要有哈希函數,並且這個函數盡可能地減小沖突

(1)直接定址法

可以取關鍵字的某個線性函數值為散列地址,即

f(key) = a*key + b

這樣的哈希函數簡單均勻,不會產生沖突,但問題是這需要事先知道關鍵字的分布情況,適合查找表較小且連續的情況。

(2)數字分析法

該方法在知道關鍵字的情況下,取關鍵字的盡量不重復的幾位值組成散列地址。

(3)平方取中法

取關鍵字平方后的中間幾位為散列地址

(4)折疊法

將關鍵字分為位數相等的幾部分,最后一部分的位數可以不等,然后把這幾部分的值(舍去進位)相加作為散列地址。

(5)除留余數法

該方法為最常用的構造哈希函數方法,對於散列表長為m的散列函數公式為

f(key) = key mod p (p<=m)

使用除留余數法的一個經驗是,若散列表表長為m,通常p為小於或等於表長的最小質數或不包含小於20質因子的合數。

實踐證明,當p取小於散列表長的最大質數時,函數較好。

(6)隨機數法

選擇一個隨機函數,取關鍵字的隨機函數值作為散列地址。

=======================================================

處理沖突

哈希函數的構造可能導致散列地址會產生沖突,通常處理沖突的方法有下面幾種:

(1)開發定址法

一旦發生沖突,就去尋找下一個空的散列地址,只要散列表足夠大,空的散列地址總能找到,並將記錄存入,公式:

fi(key) = (f(key)+di) mod m (di=1,2,3...m-1)

用開放定址法解決沖突的做法是:當沖突發生時,使用某種探測技術在散列表中形成一個探測序列,沿此序列逐個單元地查找,直到找到給定的關鍵字,或者碰到一個開放的地址(該地址單元為空)為止(若要插入,在探查到開放的地址,則可將帶插入的新節點存入該地址單元)。查找時探測到開放的地址則表明表中無待查的關鍵字,即查找失敗。

比如說,我們的關鍵字集合為{12,67,56,16,25,37,22,29,15,47,48,34},表長為12。 我們用散列函數f(key) = key mod l2。

當計算前S個數{12,67,56,16,25}時,都是沒有沖突的散列地址,直接存入:

計算key = 37時,發現f(37) = 1,此時就與25所在的位置沖突。

於是我們應用上面的公式f(37) = (f(37)+1) mod 12 = 2。於是將37存入下標為2的位置。這其實就是房子被人買了於是買下一間的作法:。

接下來22,29,15,47都沒有沖突,正常的存入:

到了 key=48,我們計算得到f(48) = 0,與12所在的0位置沖突了,不要緊,我們f(48) = (f(48)+1) mod 12 = 1,此時又與25所在的位置沖突。於是f(48) = (f(48)+2) mod 12=2,還是沖突……一直到 f(48) = (f(48)+6) mod 12 = 6時,才有空位,機不可失,趕快存入:

我們把這種解決沖突的開放定址法稱為線性探測法

二次探測法

考慮深一步,如果發生這樣的情況,當最后一個key=34,f(key)=10,與22所在的位置沖突,可是22后面沒有空位置了,反而它的前面有一個空位置,盡管可以不斷地求余數后得到結果,但效率很差。

因此我們可以改進di = 12, -12, 22, -22,……, q2, -q2 (q <= m/2),這樣就等於是可以雙向尋找到可能的空位置。

對於34來說,我們取di即可找到空位置了。另外增加平方運算的目的是為了不讓關鍵字都聚集在某一塊區域。我們稱這種方法為二次探測法。

fi(key) = (f(key)+di) MOD m (di = 12, -12, 22, -22,……, q2, -q2, q <= m/2)

隨機探測法

還有一種方法,是在沖突時,對於位移量di采用隨機函數計算得到,我們稱之為隨機探測法。

此時一定會有人問,既然是隨機,那么查找的時候不也隨機生成嗎?如何可以獲得相同的地址呢?這是個問題。這里的隨機其實是偽隨機數。

偽隨機數是說,如果我們設置隨機種子相同,則不斷調用隨機函數可以生成不會重復的數列,我們在査找時,用同樣的隨機種子,它每次得到的數列是相同的,相同的di當然可以得到相同的散列地址。

fi(key) = (f(key)+di) MOD m (di是一個隨機數列)

總之,開放定址法只要在散列表未填滿時,總是能找到不發生沖突的地址,是我們常用的解決沖突的辦法。

(2) 再哈希法

再哈希法是當散列地址沖突時,用另外一個散列函數再計算一次,這種方法減少了沖突,但增加了計算的時間。

(3) 鏈地址法

前面我們談到了散列沖突處理的開放定址法,它的思路就是一旦發生了沖突,就去尋找下一個空的散列地址。那么,有沖突就非要換地方嗎?我們直接就在原地處理行不行呢?

可以的,於是我們就有了鏈地址法。

將所有關鍵字散列地址相同的記錄存儲在一個單鏈表中,我們稱這種表為同義詞子表,在散列表中只存儲所有同義詞子表的頭指針。

對於關鍵字集合{12,67,56,16,25,37, 22,29,15,47,48,34},我們用12為除數,進行除留余數法:

此時,已經不存在什么沖突換址的問題,無論有多少個沖突,都只是在當前位置給單鏈表增加結點的問題。鏈地址法解決沖突的做法是:將所有關鍵字散列地址相同的結點鏈接在同一個單鏈表中。若選定的散列表長度為m,則可將散列表定義為一個由m個頭指針組成的指針數組T[0..m-1]。凡是散列地址為i的結點,均插入到以T[i]為頭指針的單鏈表中。T中各分量的初值均應為空指針。在拉鏈法中,裝填因子α可以大於1,但一般均取α≤1。

鏈地址法的優勢是對於可能會造成很多沖突的散列函數來說,提供了絕不會出現找不到地址的保障。當然,這也就帶來了査找時需要遍歷單鏈表的性能損耗,不過性能損耗在很多場合下也不是什么大問題。

(4) 建立公共溢出區

這種方法的基本思想是:將散列表分為基本表和溢出表兩部分,凡是和基本表發生沖突的元素,一律填入溢出表。

 


免責聲明!

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



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