摘要:根據設定的哈希函數 H(key) 和所選中的處理沖突的方法,將一組關鍵字映象到一個有限的、地址連續的地址集 (區間) 上,並以關鍵字在地址集中的“象”作為相應記錄在表中的存儲位置,如此構造所得的查找表稱之為“哈希表”。
本文分享自華為雲社區《查找——HASH》,原文作者:ruochen。
對於頻繁使用的查找表,希望 ASL = 0
記錄在表中位置和其關鍵字之間存在一種確定的關系
HASH
定義
根據設定的哈希函數 H(key) 和所選中的處理沖突的方法,將一組關鍵字映象到一個有限的、地址連續的地址集 (區間) 上,並以關鍵字在地址集中的“象”作為相應記錄在表中的存儲位置,如此構造所得的查找表稱之為“哈希表”
HASH函數的構造
- 構造原則
- 函數本身便於計算
- 計算出來的地址分布均勻,即對任一關鍵字k,f(k) 對應不同地址的概率相等,目的是盡可能減少沖突
直接定址法
- 哈希函數為關鍵字的線性函數
- H(key) = key
- H(key) = a * key + b
- 此法僅適合於:
地址集合的大小 = = 關鍵字集合的大小 - 優點:以關鍵碼key的某個線性函數值為哈希地址,不會產生沖突
- 缺點:要占用連續地址空間,空間效率低
數字分析法
- 假設關鍵字集合中的每個關鍵字都是由 s 位數字組成 (u1, u2, …, us),分析關鍵字集中的全體, 並從中提取分布均勻的若干位或它們的組合作為地址
- 此方法僅適合於:
能預先估計出全體關鍵字的每一位上各種數字出現的頻度
平方取中法
- 以關鍵字的平方值的中間幾位作為存儲地址。求“關鍵字的平方值” 的目的是“擴大差別” ,同時平方值的中間各位又能受到整個關鍵字中各位的影響
- 此方法適合於:
關鍵字中的每一位都有某些數字重復出現頻度很高的現象
折疊法
- 將關鍵字分割成若干部分,然后取它們的疊加和為哈希地址。有兩種疊加處理的方法:移位疊加和間界疊加
- 此方法適合於:
關鍵字的數字位數特別多
除留余數法
- Hash(key)=key mod p (p是一個整數)
- p≤m (表長)
- p 應為小於等於 m 的最大素數
為什么要對 p 加限制?
給定一組關鍵字為: 12, 39, 18, 24, 33, 21若取 p=9, 則他們對應的哈希函數值將為:
3, 3, 0, 6, 6, 3
可見,若 p 中含質因子 3, 則所有含質因子 3 的關鍵字均映射到“3 的倍數”的地址上,從而增加了“沖突”的可能
隨機數法
- H(key) = Random(key) (Random 為偽隨機函數)
- 此方法用於對長度不等的關鍵字構造哈希函數
考慮因素
- 執行速度(即計算哈希函數所需時間)
- 關鍵字的長度
- 哈希表的大小
- 關鍵字的分布情況
- 查找頻率
采用何種構造哈希函數的方法取決於建表的關鍵字集合的情況
原則是使產生沖突的可能性降到盡可能地小
處理沖突的方法
1. 開放定址法
基本思想
- 有沖突時就去尋找下一個空的哈希地址,只要哈希表足夠大,空的哈希地址總能找到,並將數據元素存入
線性探測法
- Hi=(Hash(key)+di) mod m ( 1≤i < m )
其中:m為哈希表長度
di 為增量序列 1,2,…m-1,且di=i
一旦沖突,就找下一個空地址存入
- 優點:只要哈希表未被填滿,保證能找到一個空地址單元存放有沖突的元素
- 缺點:能使第i個哈希地址的同義詞存入第i+1個地址,這樣本應存入第i+1個哈希地址的元素變成了第i+2個哈希地址的同義詞,……,產生“聚集”現象,降低查找效率
二次探測法
di = 12, -12, 22, -22, …±k2
偽隨機探測法
Hi=(Hash(key)+di) mod m ( 1≤i < m )
其中:m為哈希表長度
di 為隨機數
開放定址法建立哈希表步驟
- 取數據元素的關鍵字key,計算其哈希函數值(地址)。若該地址對應的存儲 空間還沒有被占用,則將該元素存入;否則執行step2解決沖突
- 根據選擇的沖突處理方法,計算關鍵字key的下一個存儲地址。若下一個存儲地址仍被占用,則繼續執行step2,直到找 到能用的存儲地址為止
開放定址哈希表的存儲結構
/* ------------- 開放定址哈希表的存儲結構 ------------- */ int hashsize[] = {997, ...}; typedef struct{ ElemType* elem; int count; // 當前數據元素個數 int sizeindex; // hashsize[sizeindex]為當前容量 } HashTable; #define SUCCESS 1 #define UNSUCCESS 0 #define DUPLICATE -1 Status SearchHash(HashTable H, KeyType K, int &p, int &c){ // 在開放定址哈希表H中查找關鍵碼為K的記錄 p = Hash(K); // 求得哈希地址 while(H.elem[p].key != NULLKEY && !EQ(K, H.elem[p].key)) collisiion(p, ++c); // 求得下一探測地址p if(EQ(K, H.elem[p].key)) return SUCCESS; // 查找成功,返回待查數據元素位置 p else return UNSUCCESS; // 查找不成功 }
2. 再HASH法
- H2(key) 是另設定的一個哈希函數,它的函數值應和 m 互質
3. 鏈地址法
基本思想
- 相同哈希地址的記錄鏈成一單鏈表,m個哈希地址就設m個單鏈表,然后用用一個數組將m個單鏈表的表頭指針存儲起來,形成一個動態的結構
優點:
- 非同義詞不會沖突,無“聚集”現象
- 鏈表上結點空間動態申請,更適合於表長不確定的情況
哈希表的查找
對於給定值 K,計算哈希地址 i = H(K)
- 若 r[i] = NULL 則查找不成功
- 若 r[i].key = K 則查找成功, 否則 “求下一地址 Hi” ,直至r[Hi] = NULL (查找不成功) 或r[Hi].key = K (查找成功) 為止
案例v01
- 線性探測法解決沖突
案例v02
- 鏈地址法處理沖突
哈希表查找的分析
從查找過程得知,哈希表查找的平均查找長度實際上並不等於零
決定哈希表查找的ASL的因素
- 選用的哈希函數
- 選用的處理沖突的方法
- 哈希表飽和的程度,裝載因子 α=n/m 值的大小(n—記錄數,m—表的長度)
α 越大,表中記錄數越多,說明表裝得越滿,發生沖突的可能性就越大,查找時比較次數就越多
- 對哈希表技術具有很好的平均性能,優於一些傳統的技術
- 鏈地址法優於開地址法
- 除留余數法作哈希函數優於其它類型函數
哈希表應用舉例
編譯器對標識符的管理多是采用哈希表
- 構造哈希函數的方法
- 將標識符中的每個字符轉換為一個非負整數
- 將得到的各個整數組合成一個整數(可以將第一個、中間的和最后一個字符值加在一起,也可以將所有字符的值加起來)
- 將結果數調整到0~M-1范圍內,可以利用取模的方法,Ki%M(M為素數)