本文的一些基本概念參考了一部分百度百科,當然只保留了最有價值的部分,代碼部分完全是自己實現!
簡介
哈希表(Hash table,也叫散列表),是根據關鍵碼值(Key value)而直接進行訪問的數據結構。它通過把關鍵碼值映射到表中一個位置來訪問記錄,有點類似於數組,並且能在O(1)(沖突情況另算)下查找到元素。
基本概念
設所有可能出現的關鍵字集合記為u(簡稱全集)。實際發生(即實際存儲)的關鍵字集合記為k(|k|比|u|小得多)。|k|是集合k中元素的個數。
散列方法是使用函數hash將u映射到表t[0..m-1]的下標上(m=o(|u|))。這樣以u中關鍵字為自變量,以h為函數的運算結果就是相應結點的存儲地址。從而達到在o(1)時間內就可完成查找。
其中:
① hash:u→{0,1,2,…,m-1} ,通常稱h為散列函數(hash function)。散列函數h的作用是壓縮待處理的下標范圍,使待處理的|u|個值減少到m個值,從而降低空間開銷。
② t為散列表(hash table)。
③ hash(ki)(ki∈u)是關鍵字為ki結點存儲地址(亦稱散列值或散列地址)。
④ 將結點按其關鍵字的散列地址存儲到散列表中的過程稱為散列(hashing).
比如:有一組數據包括用戶名字、電話、住址等,為了快速的檢索,我們可以利用名字作為關鍵碼,hash規則就是把名字中每一個字的拼音的第一個字母拿出來,把該字母在26個字母中的順序值取出來加在一塊作為該記錄的地址。比如張三,就是z+s=26+19=45。就是把張三存在地址為45處。
但是這樣存在一個問題,比如假如有個用戶名字叫做:周四,那么計算它的地址時也是z+s=45,這樣它與張三就有相同的地址,這就是沖突,也叫作碰撞(hash碰撞)。
沖突:兩個不同的關鍵字,由於散列函數值相同,因而被映射到同一表位置上。該現象稱為沖突(collision)或碰撞。發生沖突的兩個關鍵字稱為該散列函數的同義詞(synonym)。
構造方法
散列函數的選擇有兩條標准:簡單和均勻簡單指散列函數的計算簡單快速;
均勻指對於關鍵字集合中的任一關鍵字,散列函數能以等概率將其映射到表空間的任何一個位置上。也就是說,散列函數能將子集k隨機均勻地分布在表的地址集{0,1,…,m-1}上,以使沖突最小化。
常用散列函數
(1)直接定址法:比如在一個0~100歲的年齡統計表,我們就可以把年齡作為地址。
(2)平方取中法
具體方法:先通過求關鍵字的平方值擴大相近數的差別,然后根據表長度取中間的幾位數作為散列函數值。又因為一個乘積的中間幾位數和乘數的每一位都相關,所以由此產生的散列地址較為均勻。
(3)除留余數法
取關鍵字被某個不大於哈希表表長m的數p除后所得余數為哈希地址。該方法的關鍵是選取p。選取的p應使得散列函數值盡可能與關鍵字的各位相關。p最好為素數。
(4)隨機數法
選擇一個隨機函數,取關鍵字的隨機函數值為它的散列地址,即
h(key)=random(key)
其中random為偽隨機函數,但要保證函數值是在0到m-1之間。
處理沖突
處理沖突——假設哈希表的地址集為0~n-1,沖突是指由關鍵字得到的哈希 地址為j(0<=j<=n-1)的位置上已存有記錄,則“處理沖突”就是為該關鍵字的記錄找到另一個“空”的哈希地址.
在處理沖突過程中可能得到一個地址序列Hi (i=1,2,…,k),即在處理哈希地址的沖突時,若得到的另一個哈希地址H1仍然發生沖突,則再求下一個地址 H2,若H2仍然沖突,再求得H3.依次類推,直到Hk不發生沖突為止,則Hk為記錄在表中的地址.
另外,理想的散列函數滿足下面幾點:
1、散列函數的輸出值盡量接近均勻分布;
2、x的微小變化可以使f(x)發生非常大的變化,即所謂“雪崩效應”(Avalanche effect);
處理沖突的方法
(1)開放定址法(2)拉鏈法 (3)建立公共溢出區法
拉鏈法解決沖突的做法是:將所有關鍵字為同義詞的結點鏈接在同一個單鏈表中。若選定的散列表長度為m,則可將散列表定義為一個由m個頭指針組成的指針數組t[0..m-1]。凡是散列地址為i的結點,均插入到以t為頭指針的單鏈表中。t中各分量的初值均應為空指針。在拉鏈法中,裝填因子α可以大於1,但一般均取α≤1。
下面是代碼實現,已經測試過:
#include <string.h> #include <stdio.h> #include <stdlib.h> typedef struct node{ char *name;//字段名 char *desc;//描述 struct node *next; }node; #define HASHSIZE 100 //hash表長度 static node* hashtable[HASHSIZE];//定義一個hash數組,該數組的每個元素是一個hash結點指針,並且由於是全局靜態變量,默認初始化為NULL unsigned int hash(char *s) {//哈希函數 unsigned int h=0; for(;*s;s++) h=*s+h*31;//將整個字符串按照特定關系轉化為一個整數,然后對hash長度取余 return h%HASHSIZE; } node* lookup(char *str) { unsigned int hashvalue = hash(str); node* np = hashtable[hashvalue]; for( ; np!=NULL; np = np->next) {//這里是鏈地址法解決的沖突,返回的是第一個鏈表結點 if(!strcmp(np->name, str))//strcmp相等的時候才返回0 return np; } return NULL; } char* search(char* name) {//對hash表查找特定元素(元素是字符串) node* np=lookup(name); if(np==NULL) return NULL; else return np->desc; } node* malloc_node(char* name, char* desc) {//在堆上為結點分配內存,並填充結點 node *np=(node*)malloc(sizeof(node)); if(np == NULL) return NULL; np->name = name; np->desc = desc; np->next = NULL; return np; } int insert(char* name, char* desc) { unsigned int hashvalue; hashvalue = hash(name); //頭插法,不管該hash位置有沒有其他結點,直接插入結點 node* np = malloc_node(name, desc); if (np == NULL) return 0;//分配結點沒有成功,則直接返回 np->next = hashtable[hashvalue]; hashtable[hashvalue] = np; return 1; } /* A pretty useless but good debugging function, which simply displays the hashtable in (key.value) pairs */ void displayHashTable() {//顯示hash表元素(不包括空) node *np; unsigned int hashvalue; for(int i=0; i < HASHSIZE; ++i) { if(hashtable[i] != NULL) { np = hashtable[i]; printf("\nhashvalue: %d (", i); for(; np != NULL; np=np->next) printf(" (%s.%s) ", np->name, np->desc); printf(")\n"); } } } void cleanUp() {//清空hash表 node *np,*tmp; for(int i=0;i < HASHSIZE; ++i) { if(hashtable[i] != NULL) { np = hashtable[i]; while(np != NULL) { tmp = np->next; free(np->name); free(np->desc); free(np); np = tmp; } } } } int main() { char* names[]={"First Name","Last Name","address","phone","k101","k110"}; char* descs[]={"Kobe","Bryant","USA","26300788","Value1","Value2"}; for(int i=0; i < 6; ++i) insert(names[i], descs[i]); printf("we should see %s\n",search("k110")); insert("phone","9433120451");//這里計算的hash是沖突的,為了測試沖突情況下的插入 printf("we have %s and %s\n",search("k101"),search("phone")); displayHashTable(); cleanUp(); return 0; }
輸出結果:
需要特別注意一下,上面的hash函數計算為29的,用的就是單鏈表的頭插法來解決沖突,不要復雜化了問題!
上面的代碼是我自己實現的拉鏈法版本,后面我還會繼續增加一些其他更好的版本,Waiting!