hash函式的構造方法
雜湊函式的構造方法
本文闡述了雜湊函式的構造方法有很多,但應注意兩個原則:第一,函式值應在1至記錄總數之間;第二,儘可能避免衝突。
設要存放的資料元素有n個,存放資料元素的記憶體單元有m個,設計雜湊函式的目標就是要使通過雜湊函式得到的n個數據元素的雜湊地址儘可能均勻地分佈在m個連續記憶體單元上,同時使計算過程儘可能簡單以達到儘可能高的時間效率。
引 言
構造雜湊函式的方法很多。如何構造一個“好”的雜湊函式是很強的技術性和實踐性問題,這裡的“好”指的是雜湊函式構造比較簡單,並且用此雜湊函式產生的對映所發生的衝突可能性最小,換句話說一個好的雜湊函式能將給定資料集合均勻地對映到給定的地址區間中。
Hash的原意是“弄亂,切碎”,這裡的含義是“雜湊”。基本做法是,根據集合元素值的分佈情況,設計一個雜湊函式h(ki),儲存之素ki時,計算ki的雜湊函式值,元素ki儲存在a(h)中。
如果“幸運”,所設計的雜湊函式很均勻,即任何ki≠kj,都有h(ki)≠h(kj),那麼在查詢ki時(再計算ki的雜湊函式函式值h),就能在a[h]中找到元素ki。
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項即可。這種雜湊函式簡單,並且對於不同的關鍵字不會產生衝突,但可以看出這是一種較為特殊的雜湊函式,實際生活中,關鍵字的元素很少是連續的。用該方法產生的雜湊表會造成空間大量的浪費,因此這種方法適應性並不強。[2]↑
2.數字分析法
2.1數字分析法是取資料元素關鍵字中某些取值較均勻的數字位作為雜湊地址的方法。即當關鍵字的位數很多時,可以通過對關鍵字的各位進行分析,丟掉分佈不均勻的位,作為雜湊值。它只適合於所有關鍵字值已知的情況。通過分析分佈情況把關鍵字取值區間轉化為一個較小的關鍵字取值區間。
例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。[1]↑
2. 2設有n個d 位數,每一位可能有r種不同的符號,這r種不同的符號在各位上出現的頻率不一定相同,可能在某位上分佈均勻些,每種符號出現的機會均等;在某位上分佈不均勻,只有某幾種符號經常出現。可根據雜湊表的大小,選取其中各種符號分佈均勻的若干位作為雜湊地址。計算各位數字中符號分佈均勻度rk的公式為:rk=其中,aki表示第i個符號k位上出現的的期望值。計算出rk值越小,
i=1
表明在該位(第k位)各種符號分佈越不均勻。
例3,有一組關鍵字,對其各位編碼如下:
9 2 1 4 8
9 1 2 6 9
9 0 5 2 7
9 1 6 3 0
9 1 8 0 5
9 1 5 5 8
9 2 0 4 7
9 0 0 0 1
① ② ③ ④ ⑤
①位僅“9”出現8次r1=(8-8/10)2*1+(0-8/10)2*9=57.60
②位“0,2”各出現2次,“1”出現4次r2=(2-8/10)2*2+(4-8/10)2*1+(0-8/10)2*7=17.60
③位“0,5”各出現2次,“1,2,6,8”各出現1次r3=(2-8/10)2*2+(1-8/10)2*4+(0-8/10)2*4=5.60
④位“0,4”各出現2次,“2,3,5,6”各出現1次
⑤位“7,8”各出現2次,“0,1,5,9”各出現1次
r3 =r4 =r5 =5.60
若雜湊表地址範圍有3位數字,取各關鍵字的③④⑤位作為記錄的雜湊地址。也可以把第①②和第⑤位想加,捨去進位,變成一位數,再與第③④位合起來雜湊地址等。顯然數字分析法僅適用於事先知道表中所有關鍵字每一位數值的分佈情況,它完全依賴於關鍵字集合。如果換一個關鍵字集合,選擇哪幾位重新決定。
3.摺疊法
所謂摺疊法是將關鍵字分割成位數相同的幾部分(最後一部分的位數可以不同),然後取這幾部分的疊加和(捨去進位),這方法稱為摺疊法。這種方法適用於關鍵字位數較多,而且關鍵字中每一位上數字分佈大致均勻的情況。
摺疊法中數位摺疊又分為移位疊加和邊界疊加兩種方法,移位疊加是將分割後是每一部分的最低位對齊,然後相加;邊界疊加是從一端向另一端沿分割界來回摺疊,然後對齊相加。
例4,當雜湊表長為1000時,關鍵字key=110108331119891,允許的地址空間為三位十進位制數,則這兩種疊加情況如圖(2):
移位疊加 邊界疊加
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Ⅱ字元或字元的次序值。[3]↑
4.平方取中法
這是一種常用的雜湊函式構造方法。這個方法是先取關鍵字的平方,然後根據可使用空間的大小,選取平方數是中間幾位為雜湊地址。
雜湊函式 H(key)=“key2的中間幾位”因為這種方法的原理是通過取平方擴大差別,平方值的中間幾位和這個數的每一位都相關,則對不同的關鍵字得到的雜湊函式值不易產生衝突,由此產生的雜湊地址也較為均勻。
例5,若設雜湊表長為1000則可取關鍵字平方值的中間三位,如圖(3)所示:
關鍵字 |
關鍵字的平方 |
雜湊函式值 |
1234 |
1522756 |
227 |
2143 |
4592449 |
924 |
4132 |
17073424 |
734 |
3214 |
10329796 |
297 |
圖(3)平方取中雜湊函式示例 [4] ↑
有人曾用“輪盤賭”的統計分析方法對它們進行了模擬分析,結論是平方取中法最接近“隨機化”。
例6,設有一組關鍵字值為ABC,BCD,CDE,DEF其相應的機內碼分別為010203,020304,030405,040506。假設可利用地址空間大小為103,平方後取平方數的中間三位作為相當記錄的儲存地址。如圖(4)所示:
關鍵字 |
機內碼 |
機內碼的平方 |
雜湊地址 |
ABC |
010203 |
0104101209 |
101 |
BCD |
020304 |
0412252416 |
252 |
CDE |
030405 |
0924464025 |
464 |
DEF |
040506 |
1640736036 |
736 |
圖(4)平方取中法關鍵字及其儲存地址[6]↑
下面給出平方取中法的雜湊函式
//平方取中法雜湊函式,結設關鍵字值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的雜湊值。一般取大於原來基數的數作為轉換的基數,並且兩個基數應該是互素的。
例8,Hash(80127429)=(80127429)13=8*137+0*136+1*135+2*134+7*133+4*132+2*131+9=(502432641)10如果取中間三位作為雜湊值,得Hash(80127429)=432
為了獲得良好的雜湊函式,可以將幾種方法聯合起來使用,比如先變基,再摺疊或平方取中等等,只要雜湊均勻,就可以隨意拼湊。[5] ↑
7.除留餘數法
取關鍵字被某個不大於雜湊表表長m的數p除後所得餘數為雜湊地址,即設定雜湊函式為 Hash(key)=key mod p (p≤m),其中,除數p稱作模。
除留餘數法不僅可以對關鍵字直接取模,也可以在摺疊、平方取中等運算後取模。對於除留餘數法求雜湊地址,關鍵在於模p的選擇。使得資料元素集合中每一個關鍵字通過該雜湊函式對映到記憶體單元的任意地址上的概率相等,從而儘可能減少發生雜湊衝突的可能性。
理論研究表明,除留餘數法的模p取不大於表長且最接近表長m素數時效果最好,且p最好取1.1n~1.7n之間的一個素數(n為存在的資料元素個數)。例如:當n=7時,p最好取11、13等素數。 又例圖(5):
表長m |
8 |
16 |
32 |
64 |
128 |
256 |
512 |
1000 |
模p |
7 |
13 |
31 |
61 |
127 |
251 |
503 |
997 |
由於除留餘數法的地址計算方法簡單,而且在許多情況下效果較好。[2]↑
例9,公司有236個員工,而員工編號介於1000到9999,除留餘數法就是員工編號除以資料個數236後,去餘數即為資料的位置。編號5428員工的資料(編號5428除以236取餘數得0)放資料中的第一筆,編號3512員工資料(編號3512除以236取餘數得8)放資料中的第九筆…依次類推。
8.隨機乘數法
亦稱為“乘餘取整法”。隨機乘數法使用一個隨機實數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」[5] ↑
例10,對下列關鍵字值集合採用隨機乘數法計算雜湊值,隨機數f=0.103149002 雜湊表長度n=100得圖(6):
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...比較理想。[8] ↑
9.字串數值雜湊法
在很都情況下關鍵字是字串,因此這樣對字串設計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,可執行連結格式)函式。它把一個字串的絕對長度作為輸入,並通過一種方式把字元的十進位制值結合起來,對長字串和短字串都有效,這種方式產生的位置不可能不均勻分佈。[7] ↑
10.旋轉法
旋轉法是將資料的鍵值中進行旋轉。旋轉法通常並不直接使用在雜湊函式上,而是搭配其他雜湊函式使用。
例11,某學校同一個系的新生(小於100人)的學號前5位數是相同的,只有最後2位數不同,我們將最後一位數,旋轉放置到第一位,其餘的往右移。
新生學號 |
旋轉過程 |
旋轉後的新鍵值 |
5062101 |
5062101 |
1506210 |
5062102 |
5062102 |
2506210 |
5062103 |
5062103 |
3506210 |
5062104 |
5062104 |
4506210 |
5062105 |
5062105 |
5506210 |
如圖(7)
運用這種方法可以只輸入一個數值從而快速地查到有關學生的資訊。[9] ↑
11.偽隨機數法
偽隨機數法是將利用資料的鍵值經過隨機數法的運算後的結果作為資料儲存的位置。其公式如下(a和c為質數):
Y=(a * Key + c)mod 陣列的大小
例12,某公司的某女員工的編號是321547,現該公司共有107個女職工,我們取a=13,c=5則
Y=(13*321547+5)%107
=(4180111+5)%107
=54
則取54當作該員工資料儲存的位置。[10] ↑
小 結
有許多種不同的雜湊函式設計方法,這裡主要討論幾種常用的不同型別關鍵字的希函式設計方法:直接定址法、數字分析法、摺疊法、平方取中法、減去法、基數轉換法、除留餘數法、隨機乘數法、字串數值雜湊法、偽隨機數法、旋轉法。
儘管雜湊函式的構造方法有很多,但不同的方法適用於不同的情況。如:當鍵字是字串時可以用字串數值雜湊法構造雜湊函式;當關鍵字是整數型別時就可以用除留餘數法、直接定址法和數字分析法等設計雜湊函式;而關鍵字是小數型別常用偽隨機數法來構造雜湊函式等。
參 考 文 獻
[1]朱戰立編著.資料結構(C++語言描述) 北京:高等教育出版社,2004
[2]陳明編著.實用資料結構基礎 北京:清華大學出版社,2002
[3]嚴蔚敏等編著.資料結構及應用演算法教程 北京:清華大學出版社,2000
[4]殷人昆等編著.資料結構:面向物件方法與C++描述 北京:清華大學出版社,1999
[5]熊嶽山等編著.資料結構:C++語言描述, 長沙:國防科技大學出版社,2002.2
[6]蘇光奎等編著. 資料結構導學。 北京:清華大學出版社,2002
[7]陳松喬等編著.演算法與資料結構(C與C++描述) 北京:北方交通大學出版社 2002.8
[8]卓滋德克著.陳曙暉譯 資料結構與演算法——C++ 北京:清華大學出版社, 2003
[9]王慶瑞編著.資料結構教程(C++語言描述) 北京:高等教育出版社,2002.8
[10]黃國瑜等編著.資料結構(Java語言版) 北京:清華大學出版社,2002
轉自http://wenku.baidu.com/view/61b121c06137ee06eff918c1.html