算法思想:
哈希表
什么是哈希表
在前面討論的各種結構(線性表、樹等)中,記錄在結構中的相對位置是隨機的,和記錄的關鍵字之間不存在確定的關系,因此,在結構中查找記錄時需進行一系列和關鍵字的比較。這一類查找方法建立在“比較”的基礎上。
在順序查找時,比較的結果為“="與“!=”兩種可能;
在折半查找、二叉排序樹查找和B樹查找時,比較的結果為“<"、"="和“>"3種可能。查找的效率依賴於查找過程中所進行的比較次數。
理想的情況是希望不經過任何比較,一次存取便能得到所查記錄,那就必須在記錄的存儲位置和它的關鍵字之間建立一個確定的對應關系f,使每個關鍵字和結構中一個惟一的存儲位置相對應。因而在查找時,只要根據這個對應關系f找到給定值K的像f(K)。若結構中存在關鍵字和K相等的記錄,則必定在f(K)的存儲位置上,由此,不需要進行比較便可直接取得所查記錄。在此,我們稱這個對應關系f為哈希( Hash)函數,按這個思想建立的表為哈希表。
哈希函數的構造方法
哈希函數是從關鍵字集合到地址集合的映像。通常,關鍵字集合比較大,它的元素包括所有可能的關鍵字,而地址集合的元素僅為哈希表中的地址值。哈希函數其實是一個壓縮映像,那么這種情況就不可避免的產生沖突,那么在建造哈希表時不僅要設定一個好的哈希函數,還要設定一種處理沖突的方法。(設定的哈希函數H(key)和處理沖突的方法將一組關鍵字映像到一個有限的連續的地址集上,並以關鍵字在地址集中的“像”作為記錄在表中的存儲位置,這種表就是哈希表,映像的過程為哈希造表或散列,所得的存儲位置稱哈希地址或散列地址)
(1)直接定址法
取關鍵字或關鍵字的某個線性函數值為哈希地址。即H(key)=key 或 H(key)=a*key+b (a,b為常數)。
舉例1:統計1-100歲的人口,其中年齡作為關鍵字,哈希函數取關鍵字自身。查找年齡25歲的人口有多少,則直接查表中第25項。
地址 01 02 03 ... 25 26 27 ... 100
年齡 1 2 3 ... 25 26 27 ... ....
人數 3000 2000 ............. 1050
...
舉例2:統計解放以后出生人口,其中年份作為關鍵字,哈希函數取關鍵字自身加一個常數H(key)=key+(-1948).查找1970年出生的人數,則直接查(1970-1948)=22項即可。
地址 01 02 03 ... 22 23 24 ...
年份 1949 1950 1951 ... 1970
人數 ............. 15000
...
(2)數字分析法
若關鍵字是以r為基的數(如:以10為基的十進制數),並且哈希表中可能出現的關鍵字都是事先知道的,則可取關鍵字的若干數位組成哈希地址。
舉例:有80個記錄,其關鍵字為8位十進制數,假設哈希表長,則可取兩位十進制數組成哈希地址,為了盡量避免沖突,可先分析關鍵字。
8 1 3 4 6 5 3 2
8 1 3 7 2 2 4 2
8 1 3 8 7 4 2 2
8 1 3 0 1 3 6 7
8 1 3 2 2 8 1 7
8 1 3 3 8 9 6 7
8 1 3 5 4 1 5 7
8 1 3 6 8 5 3 7
8 1 4 1 9 3 5 5 ...........
經分析,發現第一位、第二位都是8,1,第三位只可能取3或4,第八位只可能取2,5或7,所以這四位不可取,那么對於第四、五、六、七位可看成是隨機的,因此,可取其中任意兩位,或取其中兩位與另外兩位的疊加求和舍去進位作為哈希地址。
(3)平方取中法
取關鍵字平方后的中間幾位為哈希地址。(較常用的一種)
舉例:為BASIC源程序中的標識符鍵一個哈希表(假設BASIC語言允許的標識符為一個字母或者一個字母和一個數字兩種情況,在計算機內可用兩位八進制數表示字母和數字),假設表長為512=,則可取關鍵字平方后的中間9位二進制數為哈希地址。(每3個二進制位可表示1位八進制位,即3個八進制位為9個二進制位)
A :01 (A的ASCII碼值為65,65的八進制為101,取后兩位表示關鍵字)
B:02 (B的ASCII碼值為66,66的八進制為102,取后兩位表示關鍵字)
...
Z:32(Z的ASCII碼值為90,90的八進制為132,取后兩位表示關鍵字)
...
0:60(0的ASCII碼值為48,48的八進制為60,取后兩位表示關鍵字)
...
9:71(9的ASCII碼值為57,57的八進制為71,取后兩位表示關鍵字)
記錄 關鍵字 關鍵字的平方 哈希地址(~)
A 0100 0010000 010
I 1100 1210000 210
P1 2061 4310541 310
Q2 2162 4741304 741
(4)折疊法
將關鍵字分割成位數相同的幾部分(最后一部分的位數可不同),然后取這幾部分的疊加和(舍去進位)作為哈希地址。適用於關鍵字位數比較多,且關鍵字中每一位上數字分布大致均勻時。
舉例:根據國際標准圖書編號(ISBN)建立一個哈希表。如一個國際標准圖書編號 0-442-20586-4的哈希地址為:
5864 5864
4220 0224
+ 04 + 04
10088 6092
移位疊加 間接疊加
H(key)=0088(將分割后的每一部分的最低位對齊) H(key)=6092(從一端向另一端沿分割界來回疊加)
(5)除留余數法
取關鍵字被某個不大於哈希表表長m的數p除后所得余數為哈希地址(p為素數)
H(key)=key MOD p,p<=m (最簡單,最常用)p的選取很重要
一般情況,p可以選取為質數或者不包含小於20的質因數的合數(合數指自然數中除了能被1和本身整除外,還能被其他數(0除外)整除的數)。
(6)隨機數法
選擇一個隨機函數,取關鍵字的隨機函數值為它的哈希地址。即H(key)=random(key),其中random為隨機函數。適用於關鍵字長度不等時。
總結:實際工作中根據情況不同選用的哈希函數不同,通常,考慮因素如下:
(1)計算哈希函數所需時間(包括硬件指令的因素)
(2)關鍵字的長度
(3)哈希表的大小
(4)關鍵字的分布情況
(5)記錄的查找頻率
常用沖突處理方法:
1.開放定址法:
方法: fi(key)=(f(key)+di) mod m,(di=1,2,3,4...,m−1)fi(key)=(f(key)+di) mod m,(di=1,2,3,4...,m−1)
線性探測:只要一旦發現沖突,就尋找下一個空的散列地址
二次探測:di=12,−12,22,−22,...,q2,−q2di=12,−12,22,−22,...,q2,−q2,目的是不讓關鍵詞集中在某塊區域,產生堆積
隨機探測:didi是一個隨機數,但查詢時需要設置和插入時相同的隨機種子
2.再散列函數法:(再哈希法)
方法:fi(key)=RHi(key) (i=1,2,...k)fi(key)=RHi(key) (i=1,2,...k)
遇到沖突就重新采用一個散列函數計算新的存儲位置,可以使關鍵字不產生聚集
3.鏈地址法(拉鏈)
方法:將所有關鍵字的同義詞記錄在一個單鏈表中,在散列表中只存儲所有同義詞表的頭指針
4.建立一個公共溢出區法
方法:為所有沖突的關鍵字開辟一個公共的溢出區(表)來存放
適用於相對於基本表來說沖突數據很少的情況
實現方法:(哈希表采用數組存儲,哈希函數構造和處理沖突的方法是除留余數法+開放定址法)
1 /**** 2 * Hash Table 3 * 4 ****/ 5 6 7 //#include "Global.h" 8 #include"stdafx.h" 9 #include <iostream> 10 using namespace std; 11 12 // HashTable Data Structure Definition 13 // array hashtable 14 #define tablesize 10 15 typedef int HashTable[tablesize]; 16 //hash function initialization way 17 void Initial_HashTable(HashTable &ht) 18 { 19 for (int i = 0; i < tablesize; i++) 20 ht[i] = 0; 21 } 22 //search hashtable function 23 int Search_HashTable(HashTable &ht,int key) 24 { 25 int address = key%tablesize; 26 int compare = 0; 27 while (compare < tablesize&&ht[address] != key&&ht[address] != 0) 28 { 29 compare++; 30 address = (address+1)%tablesize; 31 } 32 if (compare == 10 || ht[address] == 0) 33 cout << "can not find elem" << endl; 34 return address; 35 } 36 //insert hashtable function 37 int Insert_HashTable(HashTable &ht,int key) 38 { 39 int res = Search_HashTable(ht,key); 40 if (ht[res] == 0) 41 { 42 ht[res] = key; 43 return 1; 44 } 45 return 0; 46 } 47 //test function 48 int main() 49 { 50 int data[8] = { 25,36,39,47,20,58,16,35 }; 51 HashTable ht; 52 53 //initialization. 54 Initial_HashTable(ht); 55 56 //insert datas. 57 for (int i = 0; i < 8; i++) 58 { 59 cout << Insert_HashTable(ht, data[i]) << " "; 60 } 61 cout << endl; 62 63 //search. 64 cout << "25 : " << Search_HashTable(ht, 25) << endl; 65 cout << "35 : " << Search_HashTable(ht, 35) << endl; 66 cout << "145 : " << Search_HashTable(ht, 145) << endl; 67 system("pause"); 68 return 0; 69 }

