hash演算法原理詳解
一.概念
雜湊表就是一種以 鍵-值(key-indexed) 儲存資料的結構,我們只要輸入待查詢的值即key,即可查詢到其對應的值。
雜湊的思路很簡單,如果所有的鍵都是整數,那麼就可以使用一個簡單的無序陣列來實現:將鍵作為索引,值即為其對應的值,這樣就可以快速訪問任意鍵的值。這是對於簡單的鍵的情況,我們將其擴充套件到可以處理更加複雜的型別的鍵。
使用雜湊查詢有兩個步驟:
1. 使用雜湊函式將被查詢的鍵轉換為陣列的索引。在理想的情況下,不同的鍵會被轉換為不同的索引值,但是在有些情況下我們需要處理多個鍵被雜湊到同一個索引值的情況。所以雜湊查詢的第二個步驟就是處理衝突
2. 處理雜湊碰撞衝突。有很多處理雜湊碰撞衝突的方法,本文後面會介紹拉鍊法和線性探測法。
雜湊表是一個在時間和空間上做出權衡的經典例子。如果沒有記憶體限制,那麼可以直接將鍵作為陣列的索引。那麼所有的查詢時間複雜度為O(1);如果沒有時間限制,那麼我們可以使用無序陣列並進行順序查詢,這樣只需要很少的記憶體。雜湊表使用了適度的時間和空間來在這兩個極端之間找到了平衡。只需要調整雜湊函式演算法即可在時間和空間上做出取捨。
在Hash表中,記錄在表中的位置和其關鍵字之間存在著一種確定的關係。這樣我們就能預先知道所查關鍵字在表中的位置,從而直接通過下標找到記錄。使ASL趨近與0.
1) 雜湊(Hash)函式是一個映象,即: 將關鍵字的集合對映到某個地址集合上,它的設定很靈活,只要這個地 址集合的大小不超出允許範圍即可;
2) 由於雜湊函式是一個壓縮映象,因此,在一般情況下,很容易產生“衝突”現象,即: key1!=key2,而 f (key1) = f(key2)。
3). 只能儘量減少衝突而不能完全避免衝突,這是因為通常關鍵字集合比較大,其元素包括所有可能的關鍵字, 而地址集合的元素僅為雜湊表中的地址值
在構造這種特殊的“查詢表” 時,除了需要選擇一個“好”(儘可能少產生衝突)的雜湊函式之外;還需要找到一 種“處理衝突” 的方法。
二.Hash建構函式的方法
1.直接定址法:
直接定址法是以資料元素關鍵字k本身或它的線性函式作為它的雜湊地址,即:H(k)=k 或 H(k)=a×k+b ; (其中a,b為常數)
例1,有一個人口統計表,記錄了從1歲到100歲的人口數目,其中年齡作為關鍵字,雜湊函式取關鍵字本身,如圖(1):
地址 |
A1 |
A2 |
…… |
A99 |
A100 |
年齡 |
1 |
2 |
…… |
99 |
100 |
人數 |
980 |
800 |
…… |
495 |
107 |
可以看到,當需要查詢某一年齡的人數時,直接查詢相應的項即可。如查詢99歲的老人數,則直接讀出第99項即可。
地址 |
A0 |
A1 |
…… |
A99 |
A100 |
年齡 |
1980 |
1981 |
…… |
1999 |
2000 |
人數 |
980 |
800 |
…… |
495 |
107 |
如果我們要統計的是80後出生的人口數,如上表所示,那麼我們隊出生年份這個關鍵字可以用年份減去1980來作為地址,此時f(key)=key-1980
這種雜湊函式簡單,並且對於不同的關鍵字不會產生衝突,但可以看出這是一種較為特殊的雜湊函式,實際生活中,關鍵字的元素很少是連續的。用該方法產生的雜湊表會造成空間大量的浪費,因此這種方法適應性並不強。[2]↑
此法僅適合於:地址集合的大小 = = 關鍵字集合的大小,其中a和b為常數。
2.數字分析法:
假設關鍵字集合中的每個關鍵字都是由 s 位數字組成 (u1, u2, …, us),分析關鍵字集中的全體,並從中提取分佈均勻的若干位或它們的組合作為地址。
數字分析法是取資料元素關鍵字中某些取值較均勻的數字位作為雜湊地址的方法。即當關鍵字的位數很多時,可以通過對關鍵字的各位進行分析,丟掉分佈不均勻的位,作為雜湊值。它只適合於所有關鍵字值已知的情況。通過分析分佈情況把關鍵字取值區間轉化為一個較小的關鍵字取值區間。
例2,要構造一個數據元素個數n=80,雜湊長度m=100的雜湊表。不失一般性,我們這裡只給出其中8個關鍵字進行分析,8個關鍵字如下所示:
K1=61317602 K2=61326875 K3=62739628 K4=61343634
K5=62706815 K6=62774638 K7=61381262 K8=61394220
分析上述8個關鍵字可知,關鍵字從左到右的第1、2、3、6位取值比較集中,不宜作為雜湊地址,剩餘的第4、5、7、8位取值較均勻,可選取其中的兩位作為雜湊地址。設選取最後兩位作為雜湊地址,則這8個關鍵字的雜湊地址分別為:2,75,28,34,15,38,62,20。
此法適於:能預先估計出全體關鍵字的每一位上各種數字出現的頻度。
3.摺疊法:
將關鍵字分割成若干部分,然後取它們的疊加和為雜湊地址。兩種疊加處理的方法:移位疊加:將分 割後的幾部分低位對齊相加;邊界疊加:從一端沿分割界來回摺疊,然後對齊相加。
所謂摺疊法是將關鍵字分割成位數相同的幾部分(最後一部分的位數可以不同),然後取這幾部分的疊加和(捨去進位),這方法稱為摺疊法。這種方法適用於關鍵字位數較多,而且關鍵字中每一位上數字分佈大致均勻的情況。
摺疊法中數位摺疊又分為移位疊加和邊界疊加兩種方法,移位疊加是將分割後是每一部分的最低位對齊,然後相加;邊界疊加是從一端向另一端沿分割界來回摺疊,然後對齊相加。
例4,當雜湊表長為1000時,關鍵字key=110108331119891,允許的地址空間為三位十進位制數,則這兩種疊加情況如圖:
移位疊加 邊界疊加
8 9 1 8 9 1
1 1 9 9 1 1
3 3 1 3 3 1
1 0 8 8 0 1
+ 1 1 0 + 1 1 0
(1) 5 5 9 (3)0 4 4
圖(2)由摺疊法求雜湊地址
用移位疊加得到的雜湊地址是559,而用邊界疊加所得到的雜湊地址是44。如果關鍵字不是數值而是字串,則可先轉化為數。轉化的辦法可以用ASCⅡ字元或字元的次序值。
此法適於:關鍵字的數字位數特別多。
4.平方取中法
這是一種常用的雜湊函式構造方法。這個方法是先取關鍵字的平方,然後根據可使用空間的大小,選取平方數是中間幾位為雜湊地址。
雜湊函式 H(key)=“key2的中間幾位”因為這種方法的原理是通過取平方擴大差別,平方值的中間幾位和這個數的每一位都相關,則對不同的關鍵字得到的雜湊函式值不易產生衝突,由此產生的雜湊地址也較為均勻。
例5,若設雜湊表長為1000則可取關鍵字平方值的中間三位,如圖所示:
關鍵字 |
關鍵字的平方 |
雜湊函式值 |
1234 |
1522756 |
227 |
2143 |
4592449 |
924 |
4132 |
17073424 |
734 |
3214 |
10329796 |
297 |
下面給出平方取中法的雜湊函式
//平方取中法雜湊函式,結設關鍵字值32位的整數
//雜湊函式將返回key * key的中間10位
Int Hash (int key)
{
//計算key的平方
Key * = key ;
//去掉低11位
Key>>=11;
// 返回低10位(即key * key的中間10位)
Return key %1024;
}
此法適於:關鍵字中的每一位都有某些數字重複出現頻度很高的現象
5.減去法
減去法是資料的鍵值減去一個特定的數值以求得資料儲存的位置。
例7,公司有一百個員工,而員工的編號介於1001到1100,減去法就是員工編號減去1000後即為資料的位置。編號1001員工的資料在資料中的第一筆。編號1002員工的資料在資料中的第二筆…依次類推。從而獲得有關員工的所有資訊,因為編號1000以前並沒有資料,所有員工編號都從1001開始編號。
6.基數轉換法
將十進位制數X看作其他進位制,比如十三進位制,再按照十三進位制數轉換成十進位制數,提取其中若干為作為X的雜湊值。一般取大於原來基數的數作為轉換的基數,並且兩個基數應該是互素的。
例Hash(80127429)=(80127429)13=8*137+0*136+1*135+2*134+7*133+4*132+2*131+9=(502432641)10如果取中間三位作為雜湊值,得Hash(80127429)=432
為了獲得良好的雜湊函式,可以將幾種方法聯合起來使用,比如先變基,再摺疊或平方取中等等,只要雜湊均勻,就可以隨意拼湊。
7.除留餘數法:
假設雜湊表長為m,p為小於等於m的最大素數,則雜湊函式為
h(k)=k % p ,其中%為模p取餘運算。
例如,已知待雜湊元素為(18,75,60,43,54,90,46),表長m=10,p=7,則有
h(18)=18 % 7=4 h(75)=75 % 7=5 h(60)=60 % 7=4
h(43)=43 % 7=1 h(54)=54 % 7=5 h(90)=90 % 7=6
h(46)=46 % 7=4
此時衝突較多。為減少衝突,可取較大的m值和p值,如m=p=13,結果如下:
h(18)=18 % 13=5 h(75)=75 % 13=10 h(60)=60 % 13=8
h(43)=43 % 13=4 h(54)=54 % 13=2 h(90)=90 % 13=12
h(46)=46 % 13=7
此時沒有衝突,如圖8.25所示。
0 1 2 3 4 5 6 7 8 9 10 11 12
54 |
43 |
18 |
46 |
60 |
75 |
90 |
除留餘數法求雜湊地址
理論研究表明,除留餘數法的模p取不大於表長且最接近表長m素數時效果最好,且p最好取1.1n~1.7n之間的一個素數(n為存在的資料元素個數)
8.隨機數法:
設定雜湊函式為:H(key) = Random(key)其中,Random 為偽隨機函式
此法適於:對長度不等的關鍵字構造雜湊函式。
實際造表時,採用何種構造雜湊函式的方法取決於建表的關鍵字集合的情況(包括關鍵字的範圍和形態),以及雜湊表 長度(雜湊地址範圍),總的原則是使產生衝突的可能性降到儘可能地小。
9.隨機乘數法
亦稱為“乘餘取整法”。隨機乘數法使用一個隨機實數f,0≤f<1,乘積f*k的分數部分在0~1之間,用這個分數部分的值與n(雜湊表的長度)相乘,乘積的整數部分就是對應的雜湊值,顯然這個雜湊值落在0~n-1之間。其表達公式為:Hash(k)=「n*(f*k%1)」其中“f*k%1”表示f*k 的小數部分,即f*k%1=f*k-「f*k」
例10,對下列關鍵字值集合採用隨機乘數法計算雜湊值,隨機數f=0.103149002 雜湊表長度n=100得圖:
k |
f*k |
n*((f*k)的小數部分) |
Hash(k) |
319426 |
32948.47311 |
47.78411 |
47 |
718309 |
74092.85648 |
86.50448 |
86 |
629443 |
64926.41727 |
42.14427 |
42 |
919697 |
84865.82769 |
83.59669 |
83 |
此方法的優點是對n的選擇不很關鍵。通常若地址空間為p位就是選n=2p.Knuth對常數f的取法做了仔細的研究,他認為f取任何值都可以,但某些值效果更好。如f=(-1)/2=0.6180329...比較理想。
10.字串數值雜湊法
在很都情況下關鍵字是字串,因此這樣對字串設計Hash函式是一個需要討論的問題。下列函式是取字串前10個字元來設計的雜湊函式
Int Hash _ char (char *X)
{
int I ,sum
i=0;
while (i 10 && X[i])
Sum +=X[i++];
sum%=N; //N是記錄的條數
}
這種函式把字串的前10個字元的ASCⅡ值之和對N取摸作為Hash地址,只要N較小,Hash地址將較均勻分佈[0,N]區間內,因此這個函式還是可用的。對於N很大的情形,可使用下列函式
int ELFhash (char *key )
{
Unsigned long h=0,g;
whie (*key)
{
h=(h<<4)+ *key;
key++;
g=h & 0 xF0000000L;
if (g) h^=g>>24;
h & =~g;
}
h=h % N
return (h);
}
這個函式稱為ELFHash(Exextable and Linking Format ,ELF,可執行連結格式)函式。它把一個字串的絕對長度作為輸入,並通過一種方式把字元的十進位制值結合起來,對長字串和短字串都有效,這種方式產生的位置不可能不均勻分佈。
11.旋轉法
旋轉法是將資料的鍵值中進行旋轉。旋轉法通常並不直接使用在雜湊函式上,而是搭配其他雜湊函式使用。
例11,某學校同一個系的新生(小於100人)的學號前5位數是相同的,只有最後2位數不同,我們將最後一位數,旋轉放置到第一位,其餘的往右移。
新生學號 |
旋轉過程 |
旋轉後的新鍵值 |
5062101 |
5062101 |
1506210 |
5062102 |
5062102 |
2506210 |
5062103 |
5062103 |
3506210 |
5062104 |
5062104 |
4506210 |
5062105 |
5062105 |
5506210 |
如圖
運用這種方法可以只輸入一個數值從而快速地查到有關學生的資訊。
在實際應用中,應根據具體情況,靈活採用不同的方法,並用實際資料測試它的效能,以便做出正確判定。通常應考慮以下五個因素 :
l 計算雜湊函式所需時間 (簡單)。
l 關鍵字的長度。
l 雜湊表大小。
l 關鍵字分佈情況。
l 記錄查詢頻率
三.Hash處理衝突方法
通過構造效能良好的雜湊函式,可以減少衝突,但一般不可能完全避免衝突,因此解決衝突是雜湊法的另一個關鍵問題。建立雜湊表和查詢雜湊表都會遇到衝突,兩種情況下解決衝突的方法應該一致。下面以建立雜湊表為例,說明解決衝突的方法。常用的解決衝突方法有以下四種:
通過構造效能良好的雜湊函式,可以減少衝突,但一般不可能完全避免衝突,因此解決衝突是雜湊法的另一個關鍵問題。建立雜湊表和查詢雜湊表都會遇到衝突,兩種情況下解決衝突的方法應該一致。下面以建立雜湊表為例,說明解決衝突的方法。常用的解決衝突方法有以下四種:
1. 開放定址法
這種方法也稱再雜湊法,其基本思想是:當關鍵字key的雜湊地址p=H(key)出現衝突時,以p為基礎,產生另一個雜湊地址p1,如果p1仍然衝突,再以p為基礎,產生另一個雜湊地址p2,…,直到找出一個不衝突的雜湊地址pi ,將相應元素存入其中。這種方法有一個通用的再雜湊函式形式:
Hi=(H(key)+di)% m i=1,2,…,n
其中H(key)為雜湊函式,m 為表長,di稱為增量序列。增量序列的取值方式不同,相應的再雜湊方式也不同。主要有以下三種:
l 線性探測再雜湊
dii=1,2,3,…,m-1
這種方法的特點是:衝突發生時,順序查看錶中下一單元,直到找出一個空單元或查遍全表。
l 二次探測再雜湊
di=12,-12,22,-22,…,k2,-k2 ( k<=m/2 )
這種方法的特點是:衝突發生時,在表的左右進行跳躍式探測,比較靈活。
l 偽隨機探測再雜湊
di=偽隨機數序列。
具體實現時,應建立一個偽隨機數發生器,(如i=(i+p) % m),並給定一個隨機數做起點。
例如,已知雜湊表長度m=11,雜湊函式為:H(key)= key % 11,則H(47)=3,H(26)=4,H(60)=5,假設下一個關鍵字為69,則H(69)=3,與47衝突。如果用線性探測再雜湊處理衝突,下一個雜湊地址為H1=(3 + 1)% 11 = 4,仍然衝突,再找下一個雜湊地址為H2=(3 + 2)% 11 = 5,還是衝突,繼續找下一個雜湊地址為H3=(3 + 3)% 11 = 6,此時不再衝突,將69填入5號單元,參圖8.26 (a)。如果用二次探測再雜湊處理衝突,下一個雜湊地址為H1=(3 + 12)% 11 = 4,仍然衝突,再找下一個雜湊地址為H2=(3 - 12)% 11 = 2,此時不再衝突,將69填入2號單元,參圖8.26 (b)。如果用偽隨機探測再雜湊處理衝突,且偽隨機數序列為:2,5,9,……..,則下一個雜湊地址為H1=(3 + 2)% 11 = 5,仍然衝突,再找下一個雜湊地址為H2=(3 + 5)% 11 = 8,此時不再衝突,將69填入8號單元,參圖8.26 (c)。
0 1 2 3 4 5 6 7 8 9 10
47 |
26 |
60 |
69 |
(a) 用線性探測再雜湊處理衝突
0 1 2 3 4 5 6 7 8 9 10
69 |
47 |
26 |
60 |
(b) 用二次探測再雜湊處理衝突
0 1 2 3 4 5 6 7 8 9 10
47 |
26 |
60 |
69 |
(c) 用偽隨機探測再雜湊處理衝突
圖8.26開放地址法處理衝突
從上述例子可以看出,線性探測再雜湊容易產生“二次聚集”,即在處理同義詞的衝突時又導致非同義詞的衝突。例如,當表中i, i+1 ,i+2三個單元已滿時,下一個雜湊地址為i, 或i+1 ,或i+2,或i+3的元素,都將填入i+3這同一個單元,而這四個元素並非同義詞。線性探測再雜湊的優點是:只要雜湊表不滿,就一定能找到一個不衝突的雜湊地址,而二次探測再雜湊和偽隨機探測再雜湊則不一定。
2. 再雜湊法
這種方法是同時構造多個不同的雜湊函式:
Hi=RH1(key) i=1,2,…,k
當雜湊地址Hi=RH1(key)發生衝突時,再計算Hi=RH2(key)……,直到衝突不再產生。這種方法不易產生聚集,但增加了計算時間。
3. 鏈地址法
這種方法的基本思想是將所有雜湊地址為i的元素構成一個稱為同義詞鏈的單鏈表,並將單鏈表的頭指標存在雜湊表的第i個單元中,因而查詢、插入和刪除主要在同義詞鏈中進行。鏈地址法適用於經常進行插入和刪除的情況。
例如,已知一組關鍵字(32,40,36,53,16,46,71,27,42,24,49,64),雜湊表長度為13,雜湊函式為:H(key)= key % 13,則用鏈地址法處理衝突的結果如圖
圖鏈地址法處理衝突時的雜湊表
本例的平均查詢長度 ASL=(1*7+2*4+3*1)=1.5
4.建立公共溢位區
這種方法的基本思想是:將雜湊表分為基本表和溢位表兩部分,凡是和基本表發生衝突的元素,一律填入溢位表