一、哈希表
1、拉链法 (加一个单链表)
#include<bits/stdc++.h>
//哈希
//模数一般取质数,距离2的整数次幂尽量远
//拉链法有多个槽,每个槽连接一个单链表
using namespace std;
const int N=1e5+3;
int h[N],e[N],ne[N],idx=0;
//h数组为每一个槽的头结点,和ne性质一样都是存的下一个结点的下标
//e存每一个结点的数值
//ne存某个idx结点的下一个结点
void insert(int x)
{
int k=(x%N+N)%N;//C++负数取模余负数
e[idx]=x;
ne[idx]=h[k];
h[k]=idx++;
}
bool find(int x)
{
int k=(x%N+N)%N;
for(int i=h[k];i!=-1;i=ne[i])//i是下标
{
if(e[i]==x)
return true;
}
return false;
}
int main()
{
int n,x;
cin>>n;
memset(h,-1,sizeof(h));//一开始所有的槽都是空的
while(n--)
{
char op[2];
cin>>op>>x;
if(*op=='I')insert(x);
else
{
if(find(x))puts("Yes");
else puts("No");
}
}
}
2、开放寻址法 (数组长度是题目数据个数的2~3倍)没有位置向后找
#include<bits/stdc++.h>
//840 开放寻址法模拟散列表
using namespace std;
const int N=2e5+3,null=0x3f3f3f3f;//取一个无穷大的数
int h[N];
//x在表中存在的话返回位置,没有就返回应该在这个位置
//也就是一定会找到一个位置(在表没有满的情况下)
int find(int x)
{
int k=(x%N+N)%N;
//下面是一个巧妙的循环
//如果x在哈希表中一定会在找到一个空位置前找到。
//所以开放寻址法不支持通过设置null值的方法来删除数
while(h[k]!=null&&h[k]!=x)
{
k++;
if(k==N)k=0;//再从头去找
}
return k;
}
int main()
{
int n;
memset(h,0x3f,sizeof(h));//memset按照字节来赋值
//为-1的时候,四个字节合起来还是-1,为0,四个合起来都是0;
cin>>n;
while(n--)
{
char op[2];
int x;
cin>>op>>x;
// scanf("%s%d",op,&x);
int k=find(x);
if(*op=='I') h[k]=x;
else
{
if(h[k]!=null) puts("Yes");
else puts("No");
}
}
return 0;
}
3、字符串哈希(字符串前缀哈希法)
可以利用前缀哈希计算出所有子串的哈希
字符串前缀哈希:将字符串当做一个p进制的数字,计算得到10进制的结果。然后进行哈希
(1)不能映射为0,因为多个0组合到一起还是0,导致多个不同的字符串映射到同一个位置。
(2)不存在冲突的条件
(3)字符串哈希求出所有前缀和的哈希求子串的哈希的计算公式:
(4)
#include<bits/stdc++.h>
//841 判断某两个子串是否相同
using namespace std;
typedef unsigned long long int ULL;
const int N=2e5+10,P=131;
int n,m;
char str[N];//存储字符串
ULL h[N],p[N];//p用于方便存p的几次方h用于保存某一个前缀的哈希值
ULL get(int l ,int r)
{
return h[r]-h[l-1]*p[r-l+1];
}
int main()
{
//注意从str的第一位开始存储的方法
scanf("%d%d%s",&n,&m,str+1);
p[0]=1;
for(int i=1;i<=n;i++)
{
p[i]=p[i-1]*P;//
h[i]=h[i-1]*P+str[i];//将字符串转化为十进制数字
}
while(m--)
{
int l1,r1,l2,r2;
scanf("%d%d%d%d",&l1,&r1,&l2,&r2);
if(get(l1,r1)==get(l2,r2))puts("Yes");
else puts("No");
}
}