散列表/哈希表
这个是一个对数组的操作方式 和离散化相似 或者可以称离散化为特殊的哈希表,主要的目的就是通过对数据的一定操作使得原本散列的数据排列在一个具有特殊规律的表上 从而方便查找 减少时间复杂度
哈希表的实现方式主要有两种我们通过一个简单例题来讲解
维护一个集合,支持如下几种操作:
I x
,插入一个数 xx;Q x
,询问数 xx 是否在集合中出现过;
现在要进行 NN 次操作,对于每个询问操作输出对应的结果。
输入格式
第一行包含整数 NN,表示操作数量。
接下来 NN 行,每行包含一个操作指令,操作指令为 I x
,Q x
中的一种。
输出格式
对于每个询问指令 Q x
,输出一个询问结果,如果 xx 在集合中出现过,则输出 Yes
,否则输出 No
。
每个结果占一行。
数据范围
1≤N≤1051≤N≤105
−109≤x≤109−109≤x≤109
输入样例:
5
I 1
I 2
I 3
Q 2
Q 5
输出样例:
Yes
No
题目来源:acwing算法入门课
1.拉链法
拉链法顾名思义肯定要用到链的 想法是这样的 我们一般将输入的数字取模一个数 得到一个 K 然后将这个数放在h[k]中 但是无法避免的就是出现重复 例如 25% m=3 13% m=3
这样子就出现重复了 没有办法 这种重复只能减小但是无法避免 所以 我们在每一个h[k]
下都拉一个链表 讲得到的数字不断插入h[k]下的链表就好了 查找的时候也是去查找所对应的链表即可(ps;m的取值一般为一个质数而且离2的倍数越远越好 一般是数据接收范围的最大的质数 如0<N<1e5那么就是取大于1e5的第一个质数)
code:
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10,P=100003;
int h[N],e[N],ne[N],idex,n;
void add(int x){
int k=(x%P+P)%P;
e[idex]=k;
ne[idex]=h[k];
h[k]=idex++;
}//模拟链表
bool check(int x){
int k=(x%P+P)%P;
for(int i=h[k];i!=-1;i=ne[i])if(e[i]==x)return 1;
return 0;
}//查询操作
int main(){
memset(h,-1,sizeof(h));
char order[2];
int x;
scanf("%d",&n);
while(n--){
scanf("%s%d",order,&x);
if(*order=='I')add(x);
else {
if(check(x))printf("Yes\n");
else printf("No\n");
}
}
}
2.开辟寻址法(蹲坑法)
思路就是先和上面一样的方法求出 K如果h[k]已经存在数字了 那么我们就向下寻找直到寻找到一个为空的地方插入进去即可 如果找到最后一个数字也没有位置那么让k=0在去寻找
(一定能找到一个位置的) 查找的方法相似 求出k从h[k]开始找 如果不是就不断++直到找到或者找到一个空的位置为止开辟寻址法一般要开两倍or三倍的空间 也就是大于2*N的第一个质数
ps:k与拉链法意义相同
#include<bits/stdc++.h>
using namespace std;
const int N=200003;
int h[N],n,null=0x3f3f3f3f;
int find(int x){
int k=(x%N+N)%N;
while(h[k]!=null&&h[k]!=x){
k++;
if(k==N)k=0;//如果在k后面没找到就去前面找
}
return k;
}//寻找是否存在x不存在返回离x最近的空位
int main(){
memset(h,0x3f,sizeof(h));
int x;
char order;
cin>>n;
while(n--){
cin>>order>>x;
int k=find(x);
if(order=='I')h[k]=x;
else{
if(h[k]==x)cout<<"Yes"<<endl;
else cout<<"No"<<endl;
}
}
}
字符串哈希
非常强大的算法 我们把字符串的每一个字符映射到一个数字然后从左到右看为一个类似带权数字的样子 如abab a*p^3+b*p^2+a*p+b这种形式 一般我们将p取为131 or 13331而这样算出的数据会非常大 为了防止溢出我们再将其%2^64 很巧的是unsigned long long 就是2^64超出之后会自动取模 因此我们不需要再有取模操作 这样一个字符串就被完美的变成了一段数子 如果我们想要l~r之间的字符映射的数字只需要 用h[r]-h[l]*p[r-l+1] (其中p[n]为p的n次方)
Ps:我们默认不会有重复
给定一个长度为 nn 的字符串,再给定 mm 个询问,每个询问包含四个整数 l1,r1,l2,r2l1,r1,l2,r2,请你判断 [l1,r1][l1,r1] 和 [l2,r2][l2,r2] 这两个区间所包含的字符串子串是否完全相同。
字符串中只包含大小写英文字母和数字。
输入格式
第一行包含整数 nn 和 mm,表示字符串长度和询问次数。
第二行包含一个长度为 nn 的字符串,字符串中只包含大小写英文字母和数字。
接下来 mm 行,每行包含四个整数 l1,r1,l2,r2l1,r1,l2,r2,表示一次询问所涉及的两个区间。
注意,字符串的位置从 11 开始编号。
输出格式
对于每个询问输出一个结果,如果两个字符串子串完全相同则输出 Yes
,否则输出 No
。
每个结果占一行。
数据范围
1≤n,m≤1051≤n,m≤105
输入样例:
8 3
aabbaabb
1 3 5 7
1 3 6 8
1 2 1 2
输出样例:
Yes
No
Yes
code:
#include<bits/stdc++.h>
using namespace std;
const int N=100010,P=131;
typedef unsigned long long ull;
ull p[N],h[N];
int n,m;
char str[N];
ull Get(int l,int r){
return h[r]-h[l-1]*p[r-l+1];
}//计算子串的哈希值
int main(){
cin>>n>>m;
cin>>str+1;
p[0]=1;
for(int i=1;i<=n;i++){
h[i]=h[i-1]*P+str[i];
p[i]=p[i-1]*P;
}//计算字符串的哈希值
while(m--){
int l1,l2,r1,r2;
cin>>l1>>r1>>l2>>r2;
if(Get(l1,r1)==Get(l2,r2))cout<<"Yes"<<endl;
else cout<<"No"<<endl;
}
}