哈希表/散列表:
1.什么是哈希表:
- 哈希表不同于线性表数表之处在于其查找关键字(key)时不需要遍历表,哈希表中的每一个元素都可以根据元素值计算出其存放的地址,从而达到查找时长为O(1)。
- 这就好比" 字典",我们查找字典中的某个关键字(key)时,只需要查找目录即可找到该关键字(key)存放的页码(位置/地址),而查找的目录是有限的,且是线性的(常数阶/线性阶),所以无论字典存放的元素、关键字再多,我们也能通过目录快速定位查找到某个元素。
- python中的 “字典(dict)” 就是如此,利用哈希表的存储结构。
2.基本概念和术语:
- 散列表:有限的、连续的存储空间,一般是数组。
- 散列函数:散列函数是函数,这个函数构建了与
关键字(key)
一 一对应的地址(address)
,不同的散列方法需要构造不同的散列函数,Hash(key)
就是散列函数。 - 散列地址:由散列函数的到的地址就是散列地址,
address=Hash(key)
- 冲突:
key1≠key2; Hash(key1)==Hash(key2)
,即两个不同关键字,具有相同地址时,就叫发生冲突。 - 同义词:
key1==key2
,则key1
与key2
互称为同义词。
散列法与散列函数的构造
1.散列函数的要求:
- 每一个 关键字(key) 只能有一个 地址(address) 与之对应。
- 函数值域必须在表长范围之内。
- 散列地址尽量均匀分布,尽可能的减少冲突。
2.散列法:
- 数字分析法:参考<数据结构p221>
- 平均取中法:参考<数据结构p222>
- 折叠法:参考<数据结构p222>
- 直接地址法:
Hash(key)=key
或Hash(key)=a·kay+b
- 除留余数法:取关键值被某个不大于散列表长m的数p除后的所得的余数为散列地址。
Hash(key)=key % P
处理冲突的方法:
1.开放地址法(闭散列法):
- 核心思想是,把发生冲突的元素放到哈希表中的另外一个位置。
- 线性探测法:发生冲突时,逐位往后挪动,寻找合适位置,只要哈希表没满,就一定能找到一个不发生冲突的位置。
addressi=( Hash(key) + di ),其中 di = 1,2,3···
关键字序列:(44、35、14、54)
散列函数:
H(Key) = key MOD 10
处理冲突:线性探测
![](https://i-blog.csdnimg.cn/blog_migrate/10227d758890e767be4c18f5d6436ecb.png)
- 二次探测法:发生冲突时,每次向后挪动k2个单位(k为挪动次数)。
addressi=( Hash(key) + di ),其中 di = 12,22,32···
关键字序列:(44、35、14、54)
散列函数:
H(Key) = key MOD 10
处理冲突:二次探测
![](https://i-blog.csdnimg.cn/blog_migrate/5d27c44141db9d736b56cff198c915f8.png)
- 伪随机探测法:发生冲突时,每次向后挪动k个单位(k为伪随机生成数)。
- 线性探测法的优点是:只要散列表未填满,总能找到一个不发生冲突的地址。缺点是:会产生 ”二次聚集“ 现象。而二次探测法和伪随机探测法的优点是:可以避免 “二次聚集“ 现象。缺点也很显然:不能保证一定找到不发生冲突的地址。
2.链地址法(开散列法):
- 链地址法的基本思想是:把具有相同散列地址的记录放在同一个单链表中,称为同义词链表。有 m个散列地址就有 m个单链表,同时用数组 HT[0…m-1]存放各个链表的头指针,凡是散列地址为 i 的记录都以结点方式插入到以HT[i]为头结点的单链表中。
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/3cc1f49d5f7026f2d8609c81555d50d1.png)
散列表的平均查找长度(ASL):
1. 闭散列(开放地址):
关键字序列:(7、8、30、11、18、9、14)
散列函数:
H(Key) = (key x 3) MOD 7
装载因子:
0.7
处理冲突:线性探测
- 数据7个,填装因子0.7,所以散列表长度=7/0.7=10,即数组下标0~9
① 查找成功:
第一个元素7 -> 0 ; (查找1次)
第二个元素8 -> 3 ; (查找1次)
第三个元素30 -> 6 ; (查找1次)
第四个元素11 -> 5 ; (查找1次)
第五个元素18 -> 5 -> 6 -> 7 ; (查找3次)
第六个元素9 -> 6 -> 7 -> 8 ; (查找3次)
第七个元素14 -> 0 -> 1 ; (查找2次)
- 有7个数据元素,故有7种情况。
- 所以ASLsucc=(1+1+1+1+3+3+2)/ 7 = 12/7
② 查找失败:
- 由散列函数获取首地址之后,再探测到空位置,即查找失败
- 探测完整个表,仍未找到,即查找失败。
注:add表示哈希表地址
add[0] -> add[1] -> add[2](空) 比较3次
add[1]-> add[2](空) 比较2次
add[2](空) 比较1次
add[3] -> add[4](空) 比较2次
add[4] (空) 比较1次
add[5] -> add[6] -> add[7] -> add[8] -> add[9](空) 比较5次
add[6] -> add[7] -> add[8] -> add[9](空) 比较4次
只需看add[0] ~ add[6]
无需看add[7] ~ add[9]
- 因为散列函数
Hash(key)=(key x 3) % 7
,中有个mod7,所以由散列函数求出来的初地址只能是0~6 ,故只有7种情况。
- 所以ASLunsucc=(3+2+1+2+1+5+4)/ 7 = 18/7
2.开散列(链地址):
关键字序列:(19、14、23、1、68、20、84、27、55、11、10、79)
散列函数:
H(Key) = key MOD 13
处理冲突:链地址法
① 查找成功:
- 每个单链表中的第 1 个结点的关键字(如 14、 68、 19、 20、 23、 11), 查找成功时只需比较 1 次,而对千第 2个结点的关键字(如 1、 55、 84、 10), 查找成功时需比较 2 次, 第 3 个结点的关键字 27 需比较 3 次, 第4个结点的关键字 79 则需比较4次才能查找成功。这时,查找成功时的平均查找长度为:ASLsucc=(1·6 + 2·4+3+4)/ 12
② 查找失败:
- 采用链地址法处理冲突时,待查的关键字不在表中,若计算散列函数 H(key)= 0, HT[0]的指 针域为空,比较1 次即确定查找失败。若H(key)= 1, HT[l]所指的单链表包括4个结点,所以需要比较5次才能确定失败。类似地, 对H(key)= 2, 3, …,12 进行分析,可得查找失败的平均查找 长度为:ASLunsucc= (1+5 +1+3 +1+1+3+2+1+1+3+2+1)/ 13
3.ASL计算总结:
① 查找成功:
![](https://i-blog.csdnimg.cn/blog_migrate/8145bfce9c35afad5d4489c4a1cdfc82.jpeg)
② 查找失败:
![](https://i-blog.csdnimg.cn/blog_migrate/3f5557897b54067374bd527fafa0b658.jpeg)
完整源代码
#include <stdio.h>
#include <stdlib.h>
#define dataType int
#define HashSize 12
#define Empty -32767
typedef struct
{
dataType *value;
int hashsize;
}HashMap;
void IntiHashMap(HashMap *H)
{
int i;
H->hashsize=HashSize;
H->value=(dataType *)malloc(HashSize*sizeof(dataType));
for(i=0;i<HashSize;i++)
{
H->value[i]=Empty;
}
}
int Hash(dataType value)
{
return value%HashSize;
}
void Insert(HashMap *H,int data)
{
int InsertAddress;
InsertAddress=Hash(data);
while(H->value[InsertAddress]!=Empty)
{
InsertAddress=(InsertAddress+1)%HashSize;
}
H->value[InsertAddress]=data;
}
int Search(HashMap *H,int SeartData)
{
int HashAddress;
HashAddress=Hash(SeartData);
while(H->value[HashAddress]!=SeartData)
{
HashAddress=(HashAddress+1)%HashSize;
if(H->value[HashAddress]==Empty||HashAddress==Hash(SeartData))
{
return -1;
}
}
return HashAddress;
}
int main()
{
HashMap H;
int i;
int a[12]={12,67,56,16,25,37,22,29,15,47,48,34};
IntiHashMap(&H);
for(i=0;i<HashSize;i++)
{
Insert(&H,a[i]);
}
printf("address ");
for(i=0;i<HashSize;i++)
printf("%d\t",i);
printf("\n\n");
printf("a[i] ");
for(i=0;i<HashSize;i++)
printf("%d\t",a[i]);
printf("\n\n");
printf("Hash[i] ");
for(i=0;i<HashSize;i++)
printf("%d\t",H.value[i]);
printf("\n\n");
printf("a[6]的地址是:");
printf(" %d",Search(&H,a[6]));
return 0;
}
执行结果:
address 0 1 2 3 4 5 6 7 8 9 10 11
a[i] 12 67 56 16 25 37 22 29 15 47 48 34
Hash[i] 12 25 37 15 16 29 48 67 56 34 22 47
a[0]的地址是: 0
a[1]的地址是: 7
a[2]的地址是: 8
a[3]的地址是: 4
a[4]的地址是: 1
a[5]的地址是: 2
a[6]的地址是: 10
a[7]的地址是: 5
a[8]的地址是: 3
a[9]的地址是: 11
a[10]的地址是: 6
a[11]的地址是: 9
--------------------------------
Process exited after 0.01566 seconds with return value 0
请按任意键继续. . .
参考:
- 数据结构C语言第二版—严蔚敏
- 哈希表(散列表)详解
- Hash表的平均查找长度ASL计算方法