Java集合(八)哈希表及哈希函數的實現方式
一、哈希表
非哈希表的特點:關鍵字在表中的位置和它之間不存在一個確定的關系,查找的過程為給定值一次和各個關鍵字進行比較,查找的效率取決於和給定值進行比較的次數。
哈希表的特點:關鍵字在表中位置和它之間存在一種確定的關系。
哈希函數:一般情況下,需要在關鍵字與它在表中的存儲位置之間建立一個函數關系,以f(key)作為關鍵字為key的記錄在表中的位置,通常稱這個函數f(key)為哈希函數。
哈希(hash) : 翻譯為“散列”,就是把任意長度的輸入,通過hash算法,變成固定長度的輸出,該輸出就是hash值。
這種轉換是一種壓縮映射,hash值的空間通常遠小於輸入的空間,不同的輸入可能會散列成相同的輸出,所以不可能從hash值來唯一的確定輸入值。
簡單的說就是一種將任意長度的消息壓縮到固定長度的消息摘要的函數。
實現哈希函數一般有6種方式:直接定址法、數字分析法、平方取中法、折疊法、除留余數法、 隨機函數法。其中最常用的是除留余數法。
二、直接定址法
取關鍵碼的某個線性函數值作為哈希地址。公式:H(k) = a × k + c (其中a,c為常數)。
優點:以關鍵碼k的某個線性函數值為哈希地址,不會產生沖突。
缺點:會占用連續的地址空間,空間效率低。
當向字典中加入某一新元素時算法自動調用此函數,以確定該元素最終的存儲位置。若某元素關鍵碼key為1,上式中,a=2,b=3則該元素最終會存儲在字典第5個位置中。
例子:關鍵碼集合為{100,200,500,700,900},選取哈希方法為:H(k) = k / 100,則哈希表(存儲結構)如下:
直接定址法的優點是實現方法簡單,算法時間復雜度較小,而且不會產生沖突。但是,直接定址法要求散列地址空間的大小與關鍵碼集合的大小一致,而這種要求是苛刻的,一般很難實現。例如當關鍵碼的范圍為1~1000000時,元素散列地址的個數也要達到1000000。這么大的散列地址是不合實際的。
特點:一般不會發生沖突,但適用面比較窄。
三、數字分析法
取關鍵碼的中的若干位作為哈希地址。選用的原則應當是:各種符號在該位上出現的頻率大致相同。
例子:有一組(例如80個)關鍵碼,如下表:
1、位號為①、②的關鍵碼均是“3和4”,③也是只有“7,8,9”,因此,這幾位不應作為哈希地址,余下的四位,分布較均勻,可作為哈希地址選用。
2、若哈希地址取2位數,則可取④、⑤、⑥和⑦中的任意兩位組成哈希地址,也可取其中兩位和另外兩位疊加求和后,取低的兩位作為哈希地址。
特點:適用於事先明確知道表中所有關鍵碼每一位數值的分布情況。它完全依賴於關鍵碼集合,如果換一個關鍵碼集合,選擇哪幾位作為哈喜歡地址要重新確定。
四、平方取中法
對關鍵碼平方后,按哈希表大小,取中間若干位作為哈希地址。
例子:2589的平方值為6702921,可以取中間的029作為哈希地址。
特點:對數字分析法的改進,哈希地址與關鍵碼的每一位都相關。
五、折疊法
將關鍵碼分割成位數相同的幾部分(最后一部分可以不同),然后取這幾部分的疊加和作為哈希地址。折疊法有兩種,即位移法和分界法。
(一)、位移法:采取的具體方式是把各部分的最后一位對齊相加
(二)、分界法:采用的具體方式是各部分不折斷,而沿各部分的分界來回折疊,然后對齊相加,並將相加的結果當做散列地址
假設關鍵碼key=987654321,散列地址為4位。位移法和分界法計算散列地址的算式如下圖:
由式可見,位移法計算結果為15309,由於散列地址為4位,所以舍去最高位數字1,元素最終的散列地址為5309。分界法結算結果為12222,同樣舍去最高位數字1,元素最終的散列地址為2222。
特點:適用於長關鍵碼,當關鍵碼位數較多時,且關鍵碼每一位的數字上的分布較均勻時,可使用這種方法獲得哈希地址。
六、除留余數法(最常用)
以關鍵碼除以m的余數作為哈希地址,若設計的哈希表長為n,則一般取 m ≤ n 且為質數(也可為合數,但不能包含小於20的質因子)。
公式:H(k) = k mod m(m ≤ n ,m是一個整數,n是表的長度)。
為什么要對“m”加限制?
例子:給定一個關鍵碼集合:{12,39,18,24,33,21},若取m = 9,則他們對應小標如下表:
從上表可見:若m中含質因子3,則所有含質因子3的關鍵碼均映射到“3的倍數”的地址上,從而增加了“哈希沖突”的可能。
如何選取合適的“m”?
若哈希表表長為n,通常m為小於或等於表長(最好接近n)的最小質數或不包含小於20質因子的合數。當P取小於哈希表長的最大質數時,產生的哈希函數較好,因為它是離長度值最近的最大質數。
例如:關鍵碼集合{12,24,36,48,60,72,84,96,108,120,132,144}
情況1:取m=12,根據公式:H(k) = k mod m,可以得到如下表:
從上表可以看出,得到的下標都為“0”,這樣就造成了“哈希沖突”了。
情況2:如果m = 11,可以得到如下表:
從上表可以看出只有12和144沖突了,相比情況1,哈希沖突變少了。
特點:除留余數法有效縮減散列地址空間的大小,是最常用的方法。
七、乘余取整法
公式:H(k)=[b ×(a × k mod 1)] ,(a,b均為常數,且0 < a < 1,b為整數),(a × k mod 1)取的是 a × k 的小數部分。
特點:以關鍵碼k乘以a,取其小數部分,然后再放大b倍再取整,作為哈希地址。
例如,當元素關鍵碼為2020, 小數a為0.6180339,整數b為10000,則散列地址計算為H(2020)=[10000×(0.6180339×2020%1)]=4284。
特點:乘余取整法不但會縮減散列地址空間的大小,還能極大減小沖突情況的發生幾率。Knuth(唐納德·克努特,算法和程序設計的先驅)對常數a的取法做了仔細的研究,發現雖然a取任何值都可以,但一般取黃金分割數0.6180339比較好。
八、隨機函數法
選擇一隨機函數,取關鍵字的隨機值作為散列地址,及H(k) = random(k),(random(k)為偽隨機數方法),通常用於關鍵碼長度不同的場合。
九、總結
(一)、上述五中實現方式中最常用的是除留余數法,而通過哈希函數尋址的過程可能出現“沖突”------即若干個不同的key卻對應相同的哈希地址。解決哈希沖突可查看Java集合(九)哈希沖突及解決哈希沖突的4種方式。
(二)、構造哈希函數的原則
1、執行速度,即計算哈希函數的時間;
2、關鍵字的長度;
3、哈希表的大小;
4、關鍵字的分布情況;
5、查找頻率。