1.哈希表的定义
这里先说一下哈希表的定义:哈希表是一种根据关键码去寻找值的数据映射结构,该结构通过把关键码映射的位置去寻找存放值的地方,说起来可能感觉有点复杂,我想我举个例子你就会明白了,最典型的的例子就是字典,大家估计小学的时候也用过不少新华字典吧,如果我想要获取“按”字详细信息,我肯定会去根据拼音an去查找 拼音索引(当然也可以是偏旁索引),我们首先去查an在字典的位置,查了一下得到“安”,结果如下。这过程就是键码映射,在公式里面,就是通过key去查找f(key)。其中,按就是关键字(key),f()就是字典索引,也就是哈希函数,查到的页码4就是哈希值。
2.哈希冲突
但是问题又来了,我们要查的是“按”,而不是“安,但是他们的拼音都是一样的。也就是通过关键字按和关键字安可以映射到一样的字典页码4的位置,这就是哈希冲突(也叫哈希碰撞),在公式上表达就是key1≠key2,但f(key1)=f(key2)。冲突会给查找带来麻烦,你想想,你本来查找的是“按”,但是却找到“安”字,你又得向后翻一两页,在计算机里面也是一样道理的。
但哈希冲突是无可避免的,为什么这么说呢,因为你如果要完全避开这种情况,你只能每个字典去新开一个页,然后每个字在索引里面都有对应的页码,这就可以避免冲突。但是会导致空间增大(每个字都有一页)。
既然无法避免,就只能尽量减少冲突带来的损失,而一个好的哈希函数需要有以下特点:
1.尽量使关键字对应的记录均匀分配在哈希表里面(比如说某厂商卖30栋房子,均匀划分ABC3个区域,如果你划分A区域1个房子,B区域1个房子,C区域28个房子,有人来查找C区域的某个房子最坏的情况就是要找28次)。
2.关键字极小的变化可以引起哈希值极大的变化。
比较好的哈希函数是time33算法。PHP的数组就是把这个作为哈希函数。
核心的算法就是如下:
unsigned long hash(const char* key){
unsigned long hash=0;
for(int i=0;i<strlen(key);i++){
hash = hash*33+str[i];
}
return hash;
}
3.哈希冲突解决办法
如果遇到冲突,哈希表一般是怎么解决的呢?具体方法有很多,百度也会有一堆,最常用的就是开发定址法和链地址法。
1.开发定址法
如果遇到冲突的时候怎么办呢?就找hash表剩下空余的空间,找到空余的空间然后插入。就像你去商店买东西,发现东西卖光了,怎么办呢?找下一家有东西卖的商家买呗。
由于我没有深入试验过,所以贴上在书上的解释:
** 2.链地址法
上面所说的开发定址法的原理是遇到冲突的时候查找顺着原来哈希地址查找下一个空闲地址然后插入,但是也有一个问题就是如果空间不足,那他无法处理冲突也无法插入数据,因此需要装填因子(空间/插入数据)>=1。
那有没有一种方法可以解决这种问题呢?链地址法可以,链地址法的原理时如果遇到冲突,他就会在原地址新建一个空间,然后以链表结点的形式插入到该空间。我感觉业界上用的最多的就是链地址法。下面从百度上截取来一张图片,可以很清晰明了反应下面的结构。比如说我有一堆数据{1,12,26,337,353…},而我的哈希算法是H(key)=key mod 16,第一个数据1的哈希值f(1)=1,插入到1结点的后面,第二个数据12的哈希值f(12)=12,插入到12结点,第三个数据26的哈希值f(26)=10,插入到10结点后面,第4个数据337,计算得到哈希值是1,遇到冲突,但是依然只需要找到该1结点的最后链结点插入即可,同理353。
**
下面用c语言实现哈希表:
哈希表(散列表)是直接通过关键字key得到要查找的记录的内存存储位置。
散列技术是在记录的存储位置和它的关键字之间建立一个确定的对应关系f,使得每个关键字key对应一个存储位置f(key)。
采用散列技术将记录存储在一块连续的存储空间中,这块连续的存储空间称为散列表或者哈希表。
整个散列过程分为两步:
1.在存储时,通过散列函数计算记录的散列地址,并按此地址存储该记录。
2.当查找记录时,通过同样的散列函数计算记录的散列地址,按此地址方位记录。
因此散列技术既是一种存储方法也是一种查找方法,数据元素之间不存在逻辑关系。
散列技术最合适的问题是查找与给定值相等的记录。
散列函数设计的两个原则是计算简单、散列地址分布均匀。
最长用的方法是除留余数法,f(key)=key mod p(p小于等于散列表的长度m)。
但散列函数可能会造成冲突,即两个不同的记录求得的散列地址一样,解决散列冲突最常用的方法为开放定址法。一旦发生冲突,就去寻找下一个散列地址,只要散列表足够大,空的散列地址总能找到。
以下程序在DEV C++中调试运行通过。
1. #include<stdio.h>
2. #include<stdlib.h>
3.
4. #define HASHSIZE 12
5. #define NULLKEY -32768
6. typedef struct
7. {
8. int *elem;
9. int count;
10. }HashTable;
11. int m=0;
12.
13. //初始化散列表
14. int InitHashTable(HashTable *H)
15. {
16. int i;
17. m=HASHSIZE;
18. H->count=m;
19. H->elem=(int*)malloc(m*sizeof(int));
20. for(i=0;i<m;i++)
21. H->elem[i]=NULLKEY;
22. return 1;
23. }
24. //散列函数
25. int Hash(int key)
26. {
27. return key%m;
28. }
29. //插入关键字进入散列表
30. void InsertHash(HashTable *H,int key)
31. {
32. int addr=Hash(key);
33. while(H->elem[addr]!=NULLKEY)
34. addr=(addr+1)%m;
35. H->elem[addr]=key;
36. }
37.
38. //散列表查找关键字
39. int SearchHash(HashTable H,int key,int *addr)
40. {
41. *addr=Hash(key);
42. while(H.elem[*addr]!=key)
43. {
44. *addr=(*addr+1)%m;
45. if(H.elem[*addr]==NULLKEY||*addr==Hash(key))
46. {
47. return -1;
48. }
49. }
50. return *addr;
51. }
52.
53. int main()
54. {
55. int a[12]={12,67,56,16,25,37,22,29,15,47,48,34};
56. HashTable H;
57. int i;
58. InitHashTable(&H);
59. for(i=0;i<m;i++)
60. InsertHash(&H,a[i]);
61. printf("插入之后的哈希表为:");
62. for(i=0;i<m;i++)
63. printf("%d,",H.elem[i]);
64. int addr,j;
65. j=SearchHash(H,a[5],&addr);
66. printf("搜索到a[5]的地址是:%d",j);
67. }
运行结果如图所示。
如果没有冲突,哈希表查找算法的时间复杂度为O(1)。