關(guān)于哈希表里面的這些個(gè)定址和解決沖突的方法名詞我一直記不住,今天閑下來(lái)就花點(diǎn)時(shí)間來(lái)學(xué)習(xí)之、記錄之、分享之。
哈希函數(shù)構(gòu)造方法
構(gòu)造哈希函數(shù)的目標(biāo)是使得到的哈希地址盡可能均勻地分布在n個(gè)連續(xù)內(nèi)存單元地址上,同時(shí)使計(jì)算過(guò)程盡可能簡(jiǎn)單以達(dá)到盡可能高的時(shí)間效率。根據(jù)關(guān)鍵字的結(jié)構(gòu)和分布的不同,可構(gòu)造出許多不同的哈希函數(shù)。Java中的超級(jí)父類Object中就有得到哈希值的方法,以下是截取Java api 1.6中Object類說(shuō)明的一段
public int hashCode()返回該對(duì)象的哈希碼值。支持此方法是為了提高哈希表(例如 java.util.Hashtable 提供的哈希表)的性能。 hashCode 的常規(guī)協(xié)定是:
- 在 Java 應(yīng)用程序執(zhí)行期間,在對(duì)同一對(duì)象多次調(diào)用 hashCode 方法時(shí),必須一致地返回相同的整數(shù),前提是將對(duì)象進(jìn)行 equals 比較時(shí)所用的信息沒(méi)有被修改。從某一應(yīng)用程序的一次執(zhí)行到同一應(yīng)用程序的另一次執(zhí)行,該整數(shù)無(wú)需保持一致。
- 如果根據(jù) equals(Object) 方法,兩個(gè)對(duì)象是相等的,那么對(duì)這兩個(gè)對(duì)象中的每個(gè)對(duì)象調(diào)用 hashCode 方法都必須生成相同的整數(shù)結(jié)果。
- 如果根據(jù) equals(java.lang.Object) 方法,兩個(gè)對(duì)象不相等,那么對(duì)這兩個(gè)對(duì)象中的任一對(duì)象上調(diào)用 hashCode 方法不 要求一定生成不同的整數(shù)結(jié)果。但是,程序員應(yīng)該意識(shí)到,為不相等的對(duì)象生成不同整數(shù)結(jié)果可以提高哈希表的性能。
- 實(shí)際上,由 Object 類定義的 hashCode 方法確實(shí)會(huì)針對(duì)不同的對(duì)象返回不同的整數(shù)。(這一般是通過(guò)將該對(duì)象的內(nèi)部地址轉(zhuǎn)換成一個(gè)整數(shù)來(lái)實(shí)現(xiàn)的,但是 JavaTM 編程語(yǔ)言不需要這種實(shí)現(xiàn)技巧。)
下面具體說(shuō)說(shuō)構(gòu)造方法。可能在不同書(shū)籍上看到的方法名字不一樣,主要是理解思想。(定義關(guān)鍵字為k,哈希函數(shù)為h(k),表長(zhǎng)為m)
1.直接定址法
直接以關(guān)鍵字k或者k加上某個(gè)常數(shù)(k+c)作為哈希地址,即:h(k) = k + c這種哈希函數(shù)計(jì)算簡(jiǎn)單。當(dāng)關(guān)鍵字基本連續(xù)時(shí)用這種方法十分方便,若關(guān)鍵字不連續(xù)的話將造成內(nèi)存單元大量浪費(fèi)。
2.數(shù)字分析法
提取關(guān)鍵字中取值比較均勻的數(shù)字作為哈希地址。它適用于關(guān)鍵字都已知的情況,并需要對(duì)關(guān)鍵字中每一位的取值進(jìn)行分析。比如有80個(gè)記錄,關(guān)鍵字是一個(gè)8位的十進(jìn)制整數(shù):m1m2m3...m7m8,如哈希表長(zhǎng)度為100,則哈希表地址空間為0-99。進(jìn)過(guò)分析各關(guān)鍵字m1m2m3取值比較集中(多個(gè)關(guān)鍵字重復(fù)或相似)就不宜作為哈希地址;相反,門m4m5m7m8取值比較分散,則可根據(jù)需要選取若干位作為哈希地址,即:h(k) = m4m5m7 etc.
3.除留余數(shù)法
用關(guān)鍵字k除以某個(gè)不大于哈希表長(zhǎng)度m的數(shù)p,將所得余數(shù)作為哈希表地址。即:h(k) = k mod p;這種方法計(jì)算比較簡(jiǎn)單,適用范圍廣,是最經(jīng)常使用的一種哈希函數(shù)。這種方法的關(guān)鍵是選好p,使得元素集合中每一個(gè)關(guān)鍵字通過(guò)該函數(shù)轉(zhuǎn)換后映射到哈希表范圍的任意地址上的概率相等,從而盡可能減少?zèng)_突的可能性。
4.分段疊加法
按照哈希表地址位數(shù)將關(guān)鍵字分成位數(shù)相等的幾部分,其中最后一部分可以比較短。然后將這幾部分相加,舍棄最高進(jìn)位后的結(jié)果就是該關(guān)鍵字的哈希地址。分段疊加又可以分成折疊法和位移法兩種。位移法是將分割后的每部分低位對(duì)齊相加;折疊法是將奇數(shù)段正序偶數(shù)段逆序然后相加。
5.平方取中法
如果關(guān)鍵字各個(gè)部分分布都不均勻的話,可以先求出它的平方值,然后按照需求取中間的幾位作為哈希地址。因?yàn)槠椒街档闹虚g部分跟關(guān)鍵字的每一位都有相關(guān)性,所以產(chǎn)生隨機(jī)數(shù)的概率比較高。
6.偽隨機(jī)數(shù)法
插個(gè)嘴,最近看到這樣一句話:計(jì)算機(jī)中沒(méi)有正真的隨機(jī)數(shù),都是偽隨機(jī)數(shù),得到隨機(jī)數(shù)的方法都是程序員寫的代碼,當(dāng)然這里面的細(xì)節(jié)我就不是很清楚了。偽隨機(jī)數(shù)法是指采用一個(gè)偽隨機(jī)數(shù)當(dāng)作哈希函數(shù),即h(k) = random(k);
在判斷性能時(shí)通常要考慮4個(gè)因素:
- 計(jì)算哈希函數(shù)所需要的時(shí)間。
- 關(guān)鍵字的長(zhǎng)度
- 關(guān)鍵字分布情況
- 查找頻率
性能好的哈希函數(shù)能減少?zèng)_突,通常不可能完全避免沖突,所以解決沖突也是哈希表的另一個(gè)關(guān)鍵問(wèn)題。解決沖突在創(chuàng)建哈希表和查找時(shí)應(yīng)該保持一致。
處理哈希沖突
1.開(kāi)放定址法(再散列法)
在開(kāi)法定址法中,哈希表中的空閑單元(記為d)不僅允許哈希地址為d的同義詞關(guān)鍵字使用,而且也允許發(fā)生沖突的其他關(guān)鍵字使用。開(kāi)法定址法的名字就是來(lái)自于此方法的哈希表空閑單元既向同義詞開(kāi)放,也向發(fā)生沖突的非同義詞關(guān)鍵字開(kāi)放。誰(shuí)先找到這個(gè)單元誰(shuí)先占用,這和哈希表的元素排列次序有關(guān)。開(kāi)放定址法以發(fā)生沖突的地址d作為自變量來(lái)得到一個(gè)新的空閑單元,下面介紹常用的幾種。(d加下標(biāo)i記為d[i],小i打不出來(lái)==)
1.線性探查法
發(fā)生沖突時(shí),線性遍歷后續(xù)單元直到找到空閑單元。即d[i] = (d[i-1] + 1) mod m線性探查容易產(chǎn)生堆積的問(wèn)題。因?yàn)槿羰浅霈F(xiàn)了若干個(gè)同意詞會(huì)堆積在第一個(gè)同義詞的地址單元附近。
2.平方探查法
發(fā)生沖突時(shí),用平方探查法的探查序列為d[i] + 12,d[i] + 22, d[i] + 32...直到找到空閑單元。平方探查法是一種比較好的處理沖突的方法,可以避免堆積問(wèn)題。它的缺點(diǎn)是不能探查到哈希表上的所有單元,不過(guò)至少也能探查到一半單元。etc
2.鏈地址法(拉鏈法)
鏈地址法的思想是將哈希表的每個(gè)單元作為鏈表的頭結(jié)點(diǎn),所有哈希地址為i的元素構(gòu)成一個(gè)同義詞鏈表。即發(fā)生沖突時(shí)就把該關(guān)鍵字鏈在以該單元為頭結(jié)點(diǎn)的鏈表的尾部。(圖得靠自己腦補(bǔ))鏈地址法適用于經(jīng)常插入刪除的情況,其中查找、插入和刪除操作主要在同義詞鏈中進(jìn)行。
3.再哈希法
在構(gòu)造函數(shù)時(shí)同時(shí)構(gòu)造多個(gè)不同的哈希函數(shù)。當(dāng)哈希地址發(fā)生沖突用其他的函數(shù)計(jì)算另一個(gè)哈希函數(shù)地址,直到?jīng)_突不再產(chǎn)生為止。這種方法不易產(chǎn)生聚集,但增加了計(jì)算時(shí)間。
4.建立公共溢出區(qū)
建立公共溢出區(qū)的基本思想是將哈希表分為基本表和溢出表2部分,發(fā)生沖突的元素都放入溢出表中。