哈希表的查找

1、哈希表相關概念數組

1、哈希函數的基本概念函數

哈希表又稱散列表。spa

哈希表存儲的基本思想是:以數據表中的每一個記錄的關鍵字 k爲自變量,經過一種函數H(k)計算出函數值。把這個值解釋爲一塊連續存儲空間(即數組空間)的單元地址(即下標),將該記錄存儲到這個單元中。在此稱該函數H爲哈希函數或散列函數。按這種方法創建的表稱爲哈希表或散列表。.net

理想狀況下,哈希函數在關鍵字和地址之間創建了一個一一對應關係,從而使得查找只需一次計算便可完成。因爲關鍵字值的某種隨機性,使得這種一一對應關係難以發現或構造。於是可能會出現不一樣的關鍵字對應一個存儲地址。即k1k2,但H(k1)=H(k2),這種現象稱爲衝突。把這種具備不一樣關鍵字值而具備相同哈希地址的對象稱「同義詞」。設計

在大多數狀況下,衝突是不能徹底避免的。這是由於全部可能的關鍵字的集合可能比較大,而對應的地址數則可能比較少。指針

對於哈希技術,主要研究兩個問題:code

1)如何設計哈希函數以使衝突儘量少地發生。對象

2)發生衝突後如何解決。blog

2、哈希函數的構造方法get

常見的構造方法有不少種,如直接定址法,數字分析法,平方取中法等。接下來,咱們介紹其中的幾種:

1)除留餘數法

取關鍵字k被某個不大於表長m的數p除後所得餘數做爲哈希函數地址的方法。即:

           Hk)=k  mod        

這種方法的關鍵是選擇好p。使得數據集合中的每個關鍵字經過該函數轉化後映射到哈希表的任意地址上的機率相等。理論研究代表,通常取p爲小於m的最大質數或不包含小於20的質因素的合數。  

2)平方取中法

先將關鍵字平方,而後取其中間幾位做爲散列地址。所取位數由地址空間範圍決定。若地址空間小於所取位數值決定的範圍,可經過乘以一比例因子來解決。

3)摺疊法

    把關鍵字分割成位數相等(最後一部分的位數能夠不一樣)的幾部分,而後經過摺疊後將幾部分進行相加,丟掉進位位,所得值即爲散列地址。散列的位數由地址空間的位數而定。

   分割方法:從右至左

   相加方法有兩種:

   移位疊加:將分割後的各部分低位對齊相加。

   界間疊加:將某些部分倒置後再相加。至關於把關鍵字當作一張紙,從一端向另外一端沿間界逐層摺疊,再把相應位數相加。

3、哈希函數的衝突檢測方法

假設哈希表的地址範圍爲0m-l,當對給定的關鍵字k,由哈希函數H(k)算出的哈希地址爲i0im-1)的位置上已存有記錄,這種狀況就是衝突現象。

    處理衝突就是爲該關鍵字的記錄找到另外一個「空」的哈希地址。即經過一個新的哈希函數獲得一個新的哈希地址。若是仍然發生衝突,則再求下一個,依次類推。直至新的哈希地址再也不發生衝突爲止。

經常使用的處理衝突的方法有開放地址法、鏈地址法等幾類。

1)開放地址法

當發生衝突時,將依次探測「下一個位置」,直到找到其關鍵字相匹配的元素或找到一個空位插入。設哈希空間長度爲m,「下一個位置」由下式肯定:

    Hi=(H(key)+di) mod m

    H(key):哈希函數

    m:哈希表長度

    di:求「下一個位置」的增量

 di的肯定方法

a) 線性探測再散列

di=12,…,m-1

  這種di的取法稱爲線性探測再散列。即「下一個位置」爲哈希表的直接後繼。若當di=m-1時仍未查到,則說明表滿,還要查找另外的溢出表。缺點:容易產生「二次彙集」 

b)二次探測再散列

       di=12,-1222-22,…,±k2                              (km/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八、哈希表(Hash)的查找 - 墨涵 - 墨涵天地

2、哈希表C語言描述

2八、哈希表(Hash)的查找 - 墨涵 - 墨涵天地

2八、哈希表(Hash)的查找 - 墨涵 - 墨涵天地2八、哈希表(Hash)的查找 - 墨涵 - 墨涵天地

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、查找過程當中需與給定值進行比較的關鍵字的個數取決於下面三種因素:

    哈希函數

    處理衝突的方法

    哈希表的裝填因子

    哈希函數的好壞首先影響出現衝突的頻繁程度

假定哈希函數是「均勻的」,即不一樣的哈希函數對同一組隨機的關鍵字,產生衝突的可能性相同。

對同一組關鍵字,設定相同的哈希函數,則不一樣的處理衝突的方法獲得的哈希表不一樣,它的平均查找長度也不一樣。

若處理衝突的方法相同,其平均查找長度依賴於哈希表的裝填因子。

衝突的多少與表的填滿程度有關,填滿程度用α表示:

        α=表中記錄數/哈希表的長度

α標誌哈希表的裝滿程度。

α越小,發生衝突的可能性越小,反之,α越大,表中已填入的記錄越多,再填記錄時,發生衝突的可能性越大。查找時,給定值需與之進行比較的關鍵字個數就越多,檢索越慢。

2八、哈希表(Hash)的查找 - 墨涵 - 墨涵天地

相關文章
相關標籤/搜索