今天学了哈希表查找
哈希表
哈希算法:将一个值或一个数组,通过一定的规则转换成另一个值
哈希表的实现和应用
hash表又叫散列表,是一种用来存放数据的数据结构。用于快速查询
hash表就是一种数组,输入关键字,通过hash函数得到,对应数据的下标。(hash值就是下标)
散列函数构造方法
1.直接定址法
2.数字分析法
3.平方取中法
4.折叠法
5.除留余数法
最简单的哈希思想(与桶的思想有点类似):
如在长度为10的s数组里面存储着1~100范围的数,先开辟一个大小为100的t数组,初始化为0,遍历一遍数组s,借助t数组统计s数组中的各个数出现的次数,然后查找,比如说查找数3是否在数组s里面,只要看t[3]的值是否大于0即可
对于整数数据直接取余表长得到对应的哈希值,字符串需要专门的哈希函数进行转换
将转换后得到的值作为下标,将哈希表中对应下标的存值加1,哈希函数可能将不同的数据映射到同一个数组下标上,这样就发生了冲突导致查询时出现错误
哈希冲突:
哈希冲突:不同的关键字通过同一个哈希函数可能得到同一哈希地址,即 key1 ≠ key2,而
Hash(key1) = Hash(key2),这种现象称为哈希冲突。
解决:
1.开放地址法
指的是将哈希表中的「空地址」向处理冲突开放。当哈希表未满时,处理冲突时需要尝试另外的
单元,直到找到空的单元为止
H(i) = (Hash(key) + F(i)) \% m,i = 1, 2, 3, ..., n (n ≤ m - 1)
2.链地址法
(用一个二维数组第一行来存储哈希值,当某列的哈希值有冲突的话就将冲突的哈希值存放到该列的下一行????)
将具有相同哈希地址的元素(或记录)存储在同一个线性链表中。 链地址法是一种更加常用的哈希冲突解决方法。相比于开放地址法,链地址法更加简单。 假设哈希函数产生的哈希地址区间为 [0, m - 1],哈希表的表长为 m。则可以将哈希表定义为一个有 m 个头节点组成的链表指针数组 T。
这样在插入关键字的时候,只需要通过哈希函数 Hash(key) 计算出对应的哈希地址 i,然后将其以链表节点的形式插入到以 T[i] 为头节点的单链表中。在链表中插入位置可以在表头或表尾,也可以在中间。如果每次插入位置为表头,则插入操作的时间复杂度为 O(1)。
而在在查询关键字的时候,只需要通过哈希函数 Hash(key) 计算出对应的哈希地址 i,然后将对应位置上的链表整个扫描一遍,比较链表中每个链节点的键值与查询的键值是否一致。查询操作的时间复杂度跟链表的长度 k 成正比,也就是 O(k)。对于哈希地址比较均匀的哈希函数来说,理论上讲,k= n//m,其中 n 为关键字的个数,m 为哈希表的表长。
代码实现:
#include<bits/stdc++.h>
#define Max 11
#define N 8
int hashtable[Max];//已知hash表的长度为11
int func(int value) { //哈希函数返回哈希值
return value % Max;
}
int search(int key) { //哈希查找
int pos, t;
pos = func(key); //确定储存位置
t = pos; //用t存放pos
while (hashtable[t] != key && hashtable[t] != -1) {
t = (t + 1) % Max; //线性探测法求出下一个位置
if (pos == t) { //经过多次探测后又回到原位置,说明要查找的随机数不存在
return -1;
}
}
if (hashtable[t] == -1) { //该位置没有随机数,查找失败
return -1;
} else {
return t;//查找成功
}
}
void creathash(int key) { //自定义函数实现哈希表的创建
int pos, t;
pos = func(key); //查找随机数和哈希表的创建都都利用了哈希函数
t = pos;
while (hashtable[t] != -1) { //该位置右随机数,利用线性探测寻找下一个位置
t = (t + 1) % Max;
if (pos == t) { //经过多次探测后又回到原来位置,说明位置已满,即位置上都有随机数
printf("哈希表已满!\n");
return;
}
}
hashtable[t] = key; //将创建的随机数放入哈希表中
}
int main() {
int flag[50] = {0}; //定义标记变量并初始为0
int i, j, t;
memset(hashtable, -1, sizeof(hashtable));
srand((unsigned long)time(0));//以系统时间为种子产生随机数
i = 0; //i从0累加到7,代表着要随机产生8个小于50的随机数赋给t
while (i != N) {
t = rand() % 50; //产生一个50以内的随机数
if (flag[t] == 0) { //说明t是50以内的随机数且是由上一步产生的
creathash(t);//调用函数创建哈希表且将t存在表中,表长为11
printf("%2d:", t); //输出随机数
for (int j = 0; j < Max; j++) { //输出哈希表内容
printf("%2d ", hashtable[j]);
}
printf("\n");
flag[t] = 1; //产生一个随机数就将flag改为1,flag[t]和t是对应的
i++;//准备产生下一个随机数
}
}//将8个随机数存入哈希表
printf("开始查找:\n");
scanf("%d", &t);
if (search(t) != -1) {
printf("该数在%d\n", search(t));
} else {
printf("该数不存在\n");
}
}