1、哈希表相關概念數組
1、哈希函數的基本概念函數
哈希表又稱散列表。spa
哈希表存儲的基本思想是:以數據表中的每一個記錄的關鍵字 k爲自變量,經過一種函數H(k)計算出函數值。把這個值解釋爲一塊連續存儲空間(即數組空間)的單元地址(即下標),將該記錄存儲到這個單元中。在此稱該函數H爲哈希函數或散列函數。按這種方法創建的表稱爲哈希表或散列表。.net
理想狀況下,哈希函數在關鍵字和地址之間創建了一個一一對應關係,從而使得查找只需一次計算便可完成。因爲關鍵字值的某種隨機性,使得這種一一對應關係難以發現或構造。於是可能會出現不一樣的關鍵字對應一個存儲地址。即k1≠k2,但H(k1)=H(k2),這種現象稱爲衝突。把這種具備不一樣關鍵字值而具備相同哈希地址的對象稱「同義詞」。設計
在大多數狀況下,衝突是不能徹底避免的。這是由於全部可能的關鍵字的集合可能比較大,而對應的地址數則可能比較少。指針
對於哈希技術,主要研究兩個問題:code
(1)如何設計哈希函數以使衝突儘量少地發生。對象
(2)發生衝突後如何解決。blog
2、哈希函數的構造方法get
常見的構造方法有不少種,如直接定址法,數字分析法,平方取中法等。接下來,咱們介紹其中的幾種:
(1)除留餘數法
取關鍵字k被某個不大於表長m的數p除後所得餘數做爲哈希函數地址的方法。即:
H(k)=k mod p
這種方法的關鍵是選擇好p。使得數據集合中的每個關鍵字經過該函數轉化後映射到哈希表的任意地址上的機率相等。理論研究代表,通常取p爲小於m的最大質數或不包含小於20的質因素的合數。
(2)平方取中法
先將關鍵字平方,而後取其中間幾位做爲散列地址。所取位數由地址空間範圍決定。若地址空間小於所取位數值決定的範圍,可經過乘以一比例因子來解決。
(3)摺疊法
把關鍵字分割成位數相等(最後一部分的位數能夠不一樣)的幾部分,而後經過摺疊後將幾部分進行相加,丟掉進位位,所得值即爲散列地址。散列的位數由地址空間的位數而定。
分割方法:從右至左
相加方法有兩種:
移位疊加:將分割後的各部分低位對齊相加。
界間疊加:將某些部分倒置後再相加。至關於把關鍵字當作一張紙,從一端向另外一端沿間界逐層摺疊,再把相應位數相加。
3、哈希函數的衝突檢測方法
假設哈希表的地址範圍爲0~m-l,當對給定的關鍵字k,由哈希函數H(k)算出的哈希地址爲i(0≤i≤m-1)的位置上已存有記錄,這種狀況就是衝突現象。
處理衝突就是爲該關鍵字的記錄找到另外一個「空」的哈希地址。即經過一個新的哈希函數獲得一個新的哈希地址。若是仍然發生衝突,則再求下一個,依次類推。直至新的哈希地址再也不發生衝突爲止。
經常使用的處理衝突的方法有開放地址法、鏈地址法等幾類。
(1)開放地址法
當發生衝突時,將依次探測「下一個位置」,直到找到其關鍵字相匹配的元素或找到一個空位插入。設哈希空間長度爲m,「下一個位置」由下式肯定:
Hi=(H(key)+di) mod m
H(key):哈希函數
m:哈希表長度
di:求「下一個位置」的增量
di的肯定方法
a) 線性探測再散列
di=1,2,…,m-1。
這種di的取法稱爲線性探測再散列。即「下一個位置」爲哈希表的直接後繼。若當di=m-1時仍未查到,則說明表滿,還要查找另外的溢出表。缺點:容易產生「二次彙集」
b)二次探測再散列
di=12,-12,22,-22,…,±k2 (k≤m/2)
c)僞隨機探測再散列
di由一個僞隨機函數發生器產生的一個僞隨機數序列來肯定。
(2)鏈地址法
將全部關鍵字爲同義詞的記錄存儲在同一鏈表中。設哈希地址在區間[0..m-1]上,設置一個指針向量:
Chain chainhash[m];
每一個份量的初始狀態爲空,凡哈希地址爲i的的記錄則插入到chainhash[i]的鏈表中。插入的位置能夠在表頭、表尾,也可在中間。爲了查找的方便,可使同一鏈表中記錄的關鍵字有序。如
K={19,14,23,01,68,20,84,27,55,11,10,79}
H(key)=key mod 13,存儲鏈表如圖中所示:
2、哈希表C語言描述
3、哈希表C語言實現
1 #include "stdio.h" 2 3 #include "stdlib.h" 4 5 #define SUCCESS 1 6 7 #define UNSUCCESS 0 8 9 #define DUPLICATE -1 10 11 #define OK 1 12 13 #define ERROR -1 14 15 #define EQ(a,b) ((a)==(b)) 16 17 #define LT(a,b) ((a)< (b)) 18 19 #define LQ(a,b) ((a)<=(b)) 20 21 #define BT(a,b) ((a)> (b)) 22 23 #define NULLKEY -111 24 25 int hashsize[]={11,19,29,37}; // 哈希表容量遞增表, 26 27 //一個合適的素數序列 28 29 int m=0; // 哈希表表長,全局變量 30 31 typedef int KeyType; 32 33 typedef int info; 34 35 typedef struct 36 37 { 38 39 KeyType key; 40 41 //info otherinfo; 42 43 }ElemType; 44 45 typedef struct 46 47 { 48 49 ElemType *elem; 50 51 int count; 52 53 int sizeindex; 54 55 }HashTable; 56 57 58 59 int InitHashTable(HashTable &H) 60 61 { // 操做結果: 構造一個空的哈希表 62 63 int i; 64 65 H.count=0; // 當前元素個數爲0 66 67 H.sizeindex=0; // 初始存儲容量爲hashsize[0] 68 69 m=hashsize[0]; 70 71 H.elem=(ElemType*)malloc(m*sizeof(ElemType)); 72 73 if(!H.elem) 74 75 exit(0); // 存儲分配失敗 76 77 for(i=0;i<m;i++) 78 79 H.elem[i].key=NULLKEY; // 未填記錄的標誌 80 81 return OK; 82 83 } 84 85 void DestroyHashTable(HashTable &H) 86 87 { // 初始條件: 哈希表H存在。操做結果: 銷燬哈希表H 88 89 free(H.elem); 90 91 H.elem=NULL; 92 93 H.count=0; 94 95 H.sizeindex=0; 96 97 }//DestroyHashTable 98 99 int Hash(KeyType K) 100 101 { // 一個簡單的哈希函數(m爲表長,全局變量) 102 103 //除留餘數法 104 105 return K%m; 106 107 }//Hash 108 109 void collision(int &p,int d) // 線性探測再散列 110 111 { // 開放定址法處理衝突 112 113 p=(p+d)%m; 114 115 }//collision 116 117 118 119 int SearchHash(HashTable H,KeyType K,int &p,int &c) 120 121 { 122 123 p=Hash(K); //構造哈希函數 124 125 while(H.elem[p].key!=NULLKEY&&!EQ(K,H.elem[p].key)) 126 127 { 128 129 collision(p,++c); //衝突檢測 130 131 if(c>=m) break; 132 133 } 134 135 if(EQ(K,H.elem[p].key)) 136 137 return SUCCESS; 138 139 else return UNSUCCESS; 140 141 }//SearchHash 142 143 int InsertHash(HashTable &H,ElemType e); 144 145 void RecreateHashTable(HashTable &H) // 重建哈希表 146 147 { // 重建哈希表 148 149 int i,count=H.count; 150 151 ElemType *p,*elem=(ElemType*)malloc(count*sizeof(ElemType)); 152 153 p=elem; 154 155 printf("重建哈希表\n"); 156 157 for(i=0;i<m;i++) // 保存原有的數據到elem中 158 159 if((H.elem+i)->key!=NULLKEY) // 該單元有數據 160 161 *p++=*(H.elem+i); 162 163 H.count=0; 164 165 H.sizeindex++; // 增大存儲容量 166 167 m=hashsize[H.sizeindex]; 168 169 p=(ElemType*)realloc(H.elem,m*sizeof(ElemType)); 170 171 if(!p) 172 173 exit(-1); // 存儲分配失敗 174 175 H.elem=p; 176 177 for(i=0;i<m;i++) 178 179 H.elem[i].key=NULLKEY; // 未填記錄的標誌(初始化) 180 181 for(p=elem;p<elem+count;p++) // 將原有的數據按照新的表長插入到重建的哈希表中 182 183 InsertHash(H,*p); 184 185 }//RecreateHashTable 186 187 188 189 int InsertHash(HashTable &H,ElemType e) 190 191 { // 查找不成功時插入數據元素e到開放定址哈希表H中,並返回OK; 192 193 // 若衝突次數過大,則重建哈希表 194 195 int c,p; 196 197 c=0; 198 199 if(SearchHash(H,e.key,p,c)) // 表中已有與e有相同關鍵字的元素 200 201 return DUPLICATE; 202 203 else if(c<hashsize[H.sizeindex]/2) // 衝突次數c未達到上限,(c的閥值可調) 204 205 { // 插入e 206 207 H.elem[p]=e; 208 209 ++H.count; 210 211 return OK; 212 213 } 214 215 else 216 217 RecreateHashTable(H); // 重建哈希表 218 219 return ERROR; 220 221 } 222 223 int InsertHashD(HashTable &H) 224 225 { 226 227 ElemType e; 228 229 printf("input the data until -1\n"); 230 231 scanf("%d",&e.key); 232 233 while(e.key!=-1) 234 235 { 236 237 InsertHash(H,e); 238 239 printf("input the data until -1\n"); 240 241 scanf("%d",&e.key); 242 243 }//while 244 245 return 1; 246 247 }//InsertHashD 248 249 int SearchHashD(HashTable &H) 250 251 { 252 253 KeyType key; 254 255 int p=0,c=0; 256 257 printf("input the data you want to search:\n"); 258 259 scanf("%d",&key); 260 261 if(SearchHash(H,key,p,c)) 262 263 printf("the location is %d,%d\n",p,H.elem[p].key); 264 265 else printf("Search Failed!\n"); 266 267 return 1; 268 269 }//SearchHashD 270 271 void print(int p,ElemType r) 272 273 { 274 275 printf("address=%d (%d)\n",p,r.key); 276 277 }//print 278 279 void TraverseHash(HashTable H,void(*Vi)(int,ElemType)) 280 281 { // 按哈希地址的順序遍歷哈希表 282 283 printf("哈希地址0~%d\n",m-1); 284 285 for(int i=0;i<m;i++) 286 287 if(H.elem[i].key!=NULLKEY) // 有數據 288 289 Vi(i,H.elem[i]); 290 291 }//TraverseHash 292 293 void TraverseHashD(HashTable &H) 294 295 { 296 297 TraverseHash(H,print); 298 299 }//TraverseHashD 300 301 int main() 302 303 { 304 305 HashTable H; 306 307 InitHashTable(H); 308 309 InsertHashD(H); 310 311 SearchHashD(H); 312 313 TraverseHashD(H); 314 315 DestroyHashTable(H); 316 317 return 1; 318 319 }
4、複雜度分析
從哈希表的查找過程可見:
1、雖然哈希表在關鍵字與記錄的存儲位置之間創建了直接映象,但因爲衝突的產生,使得哈希表的查找過程仍然是一個給定值和關鍵字進行比較的過程。所以,仍需以平均查找長度做爲衡量哈希表的查找效率的度量。
2、查找過程當中需與給定值進行比較的關鍵字的個數取決於下面三種因素:
哈希函數
處理衝突的方法
哈希表的裝填因子
哈希函數的好壞首先影響出現衝突的頻繁程度
假定哈希函數是「均勻的」,即不一樣的哈希函數對同一組隨機的關鍵字,產生衝突的可能性相同。
對同一組關鍵字,設定相同的哈希函數,則不一樣的處理衝突的方法獲得的哈希表不一樣,它的平均查找長度也不一樣。
若處理衝突的方法相同,其平均查找長度依賴於哈希表的裝填因子。
衝突的多少與表的填滿程度有關,填滿程度用α表示:
α=表中記錄數/哈希表的長度
α標誌哈希表的裝滿程度。
α越小,發生衝突的可能性越小,反之,α越大,表中已填入的記錄越多,再填記錄時,發生衝突的可能性越大。查找時,給定值需與之進行比較的關鍵字個數就越多,檢索越慢。