模拟散列表
目录
介绍:
散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。
1.冲突
存在不同的数,他们的映射是相同的。
2.哈希函数
h(x) = ( x % N + N )% N
(1)N是数据范围
(2)+N再%N可以避免出现负数
(3)N必须是质数,这样可以减少冲突发生的概率
3.存储结构:
根据处理冲突的方法不同,分为拉链法和直接寻址法
4.离散化:
是一种特殊的哈希方法,离散化强调“保序性”即映射之前如果具有小于关系,映射之后仍需要保证小于关系,元素之间的相互关系是没变的,而这里的哈希是一般的哈希,元素之前的相互关系可能改变了
5.基本操作:O(1)
(1)插入一个元素
(2)查找一个元素
(3)修改一个元素
(4)删除一个元素:在执行删除一个元素操作的时候,我们不会真的删除一个元素,而是这只一个布尔类型的数组,标记一下该元素所在的位置就行了。
(5)主要的操作是插入和查找,删除几乎不用
应用:
一.拉链法
解决冲突方式:如果一个元素映射的位置没有存储元素,那么把这个元素放置在该位置下面;如果一个元素映射的位置已经存储了元素,就把这个元素插入到映射位置的后面,这样一个位置下面可能存储了若干个数,像一个拉链,故称为拉链法(其实就是一个邻接表)。
实现方式:邻接表,插入元素和查找元素都和邻接表的插入和查找操作相同。
代码:
#include <iostream>
#include <cstring>
using namespace std;
const int N = 100007;
int h[N], e[N], ne[N], idx; //邻接表
void insert(int k) //邻接表插入操作
{
//将K映射为P,就在p的位置上插入K
int p = (k % N + N) % N; //p是要插入元素的映射位置
e[idx] = k;
ne[idx] = h[p];
h[p] = idx ++;
}
bool query(int k) //查找一个元素
{
int p = (k % N + N) % N; //p是要查找元素的映射位置
for(int i = h[p]; i != -1; i = ne[i]) //遍历邻接表
{
if(e[i] == k) return true;
}
return false;
}
int main()
{
int n;
scanf("%d", &n);
memset(h, -1, sizeof h); //初始化头节点
while(n -- )
{
char op[2];
int x;
scanf("%s%d", op, &x); //scanf读入一个字符串可以避免空格造成的输入错误
if(*op == 'I') insert(x);
else
{
if(query(x)) puts("Yes");
else puts("No");
}
}
return 0;
}
二.直接寻址法
解决冲突方式:如果要插入元素的映射位置没有元素,那么直接将该元素放在当前位置;如果要插入元素的映射位置已经有了元素,那么就以此往后找,如果到了数组末尾还没有找到一个空的位置,就返回数组的头接着找;为了防止整个数组都找了一遍还没有找到一个空的位置,数组大小通常要开数据范围的2~3倍。
实现方式:一个一维数组。区别于链表法的是,直接寻址法每一个位置只存储一个元素,所以我们可以直接通过下标找到某个元素(因此称为直接寻址法),因此我们的插入和查找操作的实现只依靠一个find()函数找到该元素的实际映射位置(哈希函数找到的理论位置未必是该元素的实际存储位置,因为该位置可能已经被其他元素占用了,但一般来说发生冲突的概率是比较小的,即便发生了,也不需要移动态度位置)就可以了。
代码
#include <iostream>
#include <cstring>
using namespace std;
const int N = 200003, INF = 0x3f3f3f3f;
int h[N];
int find(int k) //找到元素K在哈希表中实际应该插入的位置
{
int p = (k % N + N) % N; //理论应该插入的位置
while(h[p] != INF && h[p] != k) //只要当前位置被占用并且占用位置不是我们要存入的值
{
p ++; //线性移动
if(p == N) p = 0; //如果到了末尾就回到起点
}//while一定会结束,因为我们的数组范围是开了2~3倍的,所以整个数组不会被完全使用,因此while不会无限循环
return p;
}
int main()
{
//把哈希表的值设置成一个不可能被映射到的数,以此来判断该位置有没有被使用
memset(h, 0x3f, sizeof h);
int n;
scanf("%d", &n);
while(n -- )
{
char op[2];
int x;
scanf("%s%d", op, &x);
if(*op == 'I') h[find(x)] = x;
else
{
int t = h[find(x)];
if(t == INF) puts("No");
else puts("Yes");
}
}
return 0;
}
find()函数:
find的返回值k有两种含义:
- 如果k存在哈希表当中,k是下标
- 如果k不在哈希表当中,k是要存储的位置
注意:
- 邻接表的存储模式会存储重复的值,如果一个数重复出现了多次,那么这个数就会重复保存到邻接表中
- 例如:当我们重复插入一个数2,假设2的哈希映射为1,那么邻接表当中就存储了两个2
- 直接寻址法不会保存重复的值,如果这个数之前出现过了,那么当下次这个数再出现的时候,find()函数会找到他上一次存储位置的下标并返回,而不再存储一边
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!