【雜湊查詢】hashtable
雜湊概述
線性表,二叉搜尋樹、AVL樹、B樹中,元素在儲存結構中的位置與元素的關鍵碼之間不存在直接的對應關係。在資料結構中搜索一個元素需要進行一系列的關鍵碼比較。搜尋的效率取決於搜尋過程進行的比較次數。
理想的搜尋方法是可以不經過任何比較,一次直接從表中得到要搜尋的元素。如果構造一種儲存結構,使元素的儲存位置與它的關鍵碼之間建立一個確定的對應函式關係Hash(),那麼每個元素關鍵碼與結構中的一個唯一的儲存位置相對應:Address = HashFunction(Key)
該方式即雜湊方法(Hash Method),在雜湊方法中使用的轉換函式叫著雜湊函式(Hash function),構造出來的表或者結構叫散列表(HashTable)。
雜湊函式
構造雜湊函式需要注意以下幾點:
1、雜湊函式的定義域必須包括需要儲存的全部關鍵碼,而如果散列表允許有m個地址時,其值域必須在0到m-1之間
2、雜湊函式計算出來的地址能均勻分佈在整個空間中
3、雜湊函式應該比較簡單
(1)直接定址法
取關鍵字的某個線性函式為雜湊地址,Hash(Key)= Key 或 Hash(Key)= A*Key +B,A、B為常數。這樣的雜湊函式優點是簡單、均勻,也不會產生衝突,但問題是需要事先知道關鍵字的分佈情況,適合查詢表較小且連續的情況。
(2)除留餘數法
設散列表中允許的地址數為m,取一個不大於m,但最接近或者等於m的質數p作為除數,按照雜湊函式:Hash(key) = key % p p<=m,將關鍵碼轉換成雜湊地址。
(3)平方取中法
假設關鍵字是1234,那麼它的平方就是1522756,再抽取中間的3位就是227作為雜湊地址;再比如關鍵字是4321,那麼它的平方就是18671041,抽取中間的3位就可以是671或者710用作雜湊地址。平方取中法比較適合:不知道關鍵字的分佈,而位數又不是很大的情況。
(4)摺疊法
摺疊法是將關鍵字從左到右分割成位數相等的幾部分(注意:最後一部分位數不夠時可以短些),然後將這幾部分疊加求和,並按散列表表長,取後幾位作為雜湊地址。
比如:關鍵字是9876543210,散列表表長為三位,我們將它分成四組987|654|321|0|,然後將它們疊加求和987+654+321+0=1962,再求後3位得到雜湊地址為962。有時可能這還不能夠保證分佈均勻,不妨從一段向另一端來回摺疊後對齊相加。比如將987和321反轉,再與654和0相加,程式設計789+654+123+0=1566,此時的雜湊地址為566。摺疊法適合事先不需要知道關鍵字的分佈,適合關鍵字位數比較多的情況。
(5)隨機數法
選擇一個隨機函式,取關鍵字的隨機函式值為它的雜湊地址,即H(key) = random(key),其中random為隨機數函式,通常應用於關鍵字長度不能採用此法。
(6)數學分析法
設有n個d位數,每一位可能有r中不同的符號,這r中不同的符號在個位上出現的頻率不一定相同,可能在某些位置上分佈均勻寫,每種符號出現的機會均等;在某些位上分佈不均勻只有某幾種符號經常出現。可根據散列表的大小,選擇其中各種符號分佈均勻的若干位作為雜湊地址。比如:
假設要儲存某家公司員工登記表,如果用手機號作為關鍵字,那麼極有可能前7位都是相同的,那麼我們可以選擇後面的四位作為雜湊地址,如果這樣的抽取工作還容易出現衝突,還可以對出去出來的數字進行反轉(如1234改成4321)、右環位移(如1234改成4123)、左環移位、前兩數與後兩數疊加(如1234改成12+34=46)等方法,總之:為了提供一個雜湊函式,能夠合理的將關鍵字分配到散列表的個位置。數字分析法通常適合處理關鍵字位數比較大的情況,如果事先知道關鍵字的分佈且關鍵字的若干位分佈較均勻的情況。
雜湊衝突
使用hash function會帶來一個問題:可能有不同的元素被對映到相同的位置(亦有相同的索引)。這無法避免,因為元素個數大於array的容量。這便是雜湊衝突問題。解決碰撞問題的方法有兩大類,一類是閉雜湊法:線性探測,二次探測,雙雜湊法;另一類是開雜湊法;
(1)線性探測
線性探測就是當插入一個元素時,根據雜湊函式計算出插入位置,檢視該位置是否有元素,如果該位置被佔用的話,檢視下一個位置,是否被佔用,直到找一個空的位置。如果檢視到末尾的話,轉到頭部繼續檢視。
線性探查方法容易產生“堆積”問題,即不同探查序列的關鍵碼佔據了可利用的空桶,使得為尋找某一關鍵碼需要經歷不同的探查序列的元素,導致搜尋時間增加。因此,當散列表經常變動時,最好不要用閉雜湊法處理衝突,可以利用二次探查法可以改善上述的“堆積”問題,減少為完成搜尋所需的平均探查次數。
二次探測
二次探測的命名是因為其解決碰撞問題的方程式:F(i) = i^2,是個二次方程式。
如果hash function計算出來的位置為H,而該位置被佔用,那麼就一次嘗試 H+1,H+2^2,H+3^2 ,,,H+i^2。
研究表明當表的長度TableSize為質數且表裝載因子a不超過0.5時,新的表項一定能夠插入,而且任何一個位置都不會被探查兩次。因此只要表中有一半的空的,就不會有表滿的問題。在搜尋時可以不考慮表裝滿的情況,但在插入時必須確保表的裝載因子a不超過0.5;如果超出必須考慮增容。
其中有個小技巧,去實現二次探測方程式。
(3)雙雜湊法
使用雙雜湊方法,需要兩個雜湊函式。第一個雜湊函式Hash()按表項的關鍵碼key計算表項所在的桶號H0 = Hash(key)。一旦發生衝突,利用第二個雜湊函式ReHash()計算該表項到達“下一個”桶的位移,它的取值與key的值有關,要求它的取值應該是小於地址空間大小TableSize,且與TableSize互質的正整數。
若設表的長度為m = TableSize,則在表中尋找“下一個”桶的公式為:
j = H0 = Hash(key),p = ReHash(key); j = (j+p)%m; p是小於m且與m互質的整數。
(4)開雜湊法
開鏈法的做法就是在每一個表格元素中維護一個list;雜湊函式為我們分配某一個list,我們在list上執行插入,搜尋,刪除操作。
使用開鏈手法,表格的負載係數將大於1。
素數表
使用素數表對其做雜湊表的容量,降低雜湊衝突。
const int _PrimeSize = 28;
static const unsigned long _PrimeList [_PrimeSize] =
{
53ul, 97ul, 193ul, 389ul, 769ul,
1543ul, 3079ul, 6151ul, 12289ul, 24593ul,
49157ul, 98317ul, 196613ul, 393241ul, 786433ul,
1572869ul, 3145739ul, 6291469ul, 12582917ul, 25165843ul,
50331653ul, 100663319ul, 201326611ul, 402653189ul, 805306457ul,
1610612741ul, 3221225473ul, 4294967291ul
}
字串雜湊函式