哈希表的查找

一、哈希表相关概念

1、哈希函数的基本概念

哈希表又称散列表。

哈希表存储的基本思想是:以数据表中的每个记录的关键字 k为自变量,通过一种函数H(k)计算出函数值。把这个值解释为一块连续存储空间(即数组空间)的单元地址(即下标),将该记录存储到这个单元中。在此称该函数H为哈希函数或散列函数。按这种方法建立的表称为哈希表或散列表。

理想情况下,哈希函数在关键字和地址之间建立了一个一一对应关系,从而使得查找只需一次计算即可完成。由于关键字值的某种随机性,使得这种一一对应关系难以发现或构造。因而可能会出现不同的关键字对应一个存储地址。即k1k2,但H(k1)=H(k2),这种现象称为冲突。把这种具有不同关键字值而具有相同哈希地址的对象称“同义词”。

在大多数情况下,冲突是不能完全避免的。这是因为所有可能的关键字的集合可能比较大,而对应的地址数则可能比较少。

对于哈希技术,主要研究两个问题:

1)如何设计哈希函数以使冲突尽可能少地发生。

2)发生冲突后如何解决。

2、哈希函数的构造方法

常见的构造方法有很多种,如直接定址法,数字分析法,平方取中法等。接下来,我们介绍其中的几种:

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,存储链表如图中所示:

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

二、哈希表C语言描述

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

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

三、哈希表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("重建哈希表
");
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
");
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
");
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:
");
258 
259 scanf("%d",&key);
260 
261 if(SearchHash(H,key,p,c))
262 
263        printf("the location is %d,%d
",p,H.elem[p].key);
264 
265 else printf("Search Failed!
");
266 
267 return 1;
268 
269 }//SearchHashD
270 
271 void print(int p,ElemType r)
272 
273  {
274 
275    printf("address=%d (%d)
",p,r.key);
276 
277  }//print
278 
279  void TraverseHash(HashTable H,void(*Vi)(int,ElemType))
280 
281  { // 按哈希地址的顺序遍历哈希表
282 
283    printf("哈希地址0~%d
",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 }

四、复杂度分析

从哈希表的查找过程可见:

1、虽然哈希表在关键字与记录的存储位置之间建立了直接映象,但由于冲突的产生,使得哈希表的查找过程仍然是一个给定值和关键字进行比较的过程。因此,仍需以平均查找长度作为衡量哈希表的查找效率的度量。

2、查找过程中需与给定值进行比较的关键字的个数取决于下面三种因素:

    哈希函数

    处理冲突的方法

    哈希表的装填因子

    哈希函数的好坏首先影响出现冲突的频繁程度

假定哈希函数是“均匀的”,即不同的哈希函数对同一组随机的关键字,产生冲突的可能性相同。

对同一组关键字,设定相同的哈希函数,则不同的处理冲突的方法得到的哈希表不同,它的平均查找长度也不同。

若处理冲突的方法相同,其平均查找长度依赖于哈希表的装填因子。

冲突的多少与表的填满程度有关,填满程度用α表示:

        α=表中记录数/哈希表的长度

α标志哈希表的装满程度。

α越小,发生冲突的可能性越小,反之,α越大,表中已填入的记录越多,再填记录时,发生冲突的可能性越大。查找时,给定值需与之进行比较的关键字个数就越多,检索越慢。

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

这世界上有一种鸟是没有脚的,它只能够一直的飞呀飞呀,飞累了就在风里面睡觉,这种鸟一辈子只能下地一次,那一次就是它死亡的时候。
原文地址:https://www.cnblogs.com/xuyinghui/p/4593254.html