Hash算法的意义在于提供了一种快速存取数据的方法。
通过把任意长度的输入(又称预映射)通过哈希算法(哈希函数)转化成某种固定长度的输出,该输出即为哈希值(又称散列值):一般就将该哈希值作为数组的下标,将该输入存入其所指向的数组空间(该数组称为哈希表)中。那么再次查找该元素不必遍历数组而是通过计算该元素的哈希值进行直接访问。但是这种转换一般是一种压缩映射,即散列的空间远小于输入的空间,可能会有不同的输入散列成相同的映射,即哈希值相同的输入其值不一定相同(哈希冲突),任何的哈希函数都不可能避免这种冲突。
构造哈希表的几种方法
1.直接定址法:取关键字直接通过某个线性函数得到的值作为哈希值
2.除留余数法:取关键字对哈希表长取余得到的值作为哈希值
3.平方取中法
4.随机数法等等
最常使用的是前两种方法
处理哈希冲突的方法
1.闭散列法
线性探测:如果当前关键字产生了与之前关键字相同的哈希值,那么该哈希值循环自增1,探测寻找直至一个未被占有的哈希值。
平方探测:如果当前关键字产生了与之前关键字相同的哈希值,哈希值不是循环增1探测,而是探测寻找距离其偏移量为(1,2,3......)的平方的哈希值是否被占有。
2.拉链法
P3370 【模板】字符串哈希
题目描述
如题,给定N个字符串(第i个字符串长度为Mi,字符串内包含数字、大小写字母,大小写敏感),请求出N个字符串中共有多少个不同的字符串。
输入输出格式
输入格式:
第一行包含一个整数N,为字符串的个数。
接下来N行每行包含一个字符串,为所提供的字符串。
输出格式:
输出包含一行,包含一个整数,为不同的字符串个数。
输入样例#1:
5
abc
aaaa
abc
abcc
12345
输出样例#1:
4
说明
时空限制:1000ms,128M
数据规模:
对于30%的数据:N<=10,Mi≈6,Mmax<=15;
对于70%的数据:N<=1000,Mi≈100,Mmax<=150
对于100%的数据:N<=10000,Mi≈1000,Mmax<=1500
很明显此题如果开string数组遍历查找是否存在,不存在则加入数组的话大数据一定会超时,需要使用Hash算法快速存取查找数据,常用的模板
一般是将字符串看为一个255进制数,将其转为10进制数再对一个大质数(通常1e6+13)取模,将散列空间固定在这个大质数范围内。但是不同的字符串可能会哈希成相同的值,故需要用到邻接表,对该哈希值下的字符串都判断一遍,如果没有重复再添加进去(即为拉链法)。
#include<iostream>
#include<string>
using namespace std;
const int MOD=1e6+13;
int hash[MOD],nexp[10010],p=1;//hash数组的下标是字符串的哈希值(存在这里就将数组的值置为p)
string s[10010]; //需要一个nexp数组来记录哈希值相同的串在s字符串数组中的下标,通过hash与nexp数组完成一个类似于"链表的数组 "(hash模拟基数组地址,nexp模拟每个基地址后的一串链表),s字符串数组中存储字符串值,hash,nexp数组存放字符串在s中的下标地址
int gethash(string x){ //哈希函数
int plus=255,r=x[0],len=x.size();
for(int i=1;i<len;i++){
r=(r*plus)%MOD;
r+=x[i];
}
return r%MOD;
}
bool inhash(string x){
int hashcode=gethash(x);
for(int i=hash[hashcode];i!=0;i=nexp[i]){ //循环判断该哈希值下的所有字符串
if(s[i]==x)
return false; //有重复则不进行插入
}
nexp[p]=hash[hashcode],hash[hashcode]=p; //无重复则插入
s[p]=x;p++;
return true;
}
int main(){
std::ios::sync_with_stdio(false);
int n;cin>>n;
int res=0;
for(int i=0;i<n;i++){
string tmp;cin>>tmp;
if(inhash(tmp)){
res++;
}
}
cout<<res<<endl;
return 0;
}
PAT 1039 Course List for Student (25 分)
Zhejiang University has 40000 students and provides 2500 courses. Now given the student name lists of all the courses, you are supposed to output the registered course list for each student who comes for a query.
Input Specification:
Each input file contains one test case. For each case, the first line contains 2 positive integers: N (≤40,000), the number of students who look for their course lists, and K (≤2,500), the total number of courses. Then the student name lists are given for the courses (numbered from 1 to K) in the following format: for each course i, first the course index i and the number of registered students Ni (≤200) are given in a line. Then in the next line, Ni student names are given. A student name consists of 3 capital English letters plus a one-digit number. Finally the last line contains the N names of students who come for a query. All the names and numbers in a line are separated by a space.
Output Specification:
For each test case, print your results in N lines. Each line corresponds to one student, in the following format: first print the student's name, then the total number of registered courses of that student, and finally the indices of the courses in increasing order. The query results must be printed in the same order as input. All the data in a line must be separated by a space, with no extra space at the end of the line.
Sample Input:
11 5
4 7
BOB5 DON2 FRA8 JAY9 KAT3 LOR6 ZOE1
1 4
ANN0 BOB5 JAY9 LOR6
2 7
ANN0 BOB5 FRA8 JAY9 JOE4 KAT3 LOR6
3 1
BOB5
5 9
AMY7 ANN0 BOB5 DON2 FRA8 JAY9 KAT3 LOR6 ZOE1
ZOE1 ANN0 BOB5 JOE4 JAY9 FRA8 DON2 AMY7 KAT3 LOR6 NON9
Sample Output:
ZOE1 2 4 5
ANN0 3 1 2 5
BOB5 5 1 2 3 4 5
JOE4 1 2
JAY9 4 1 2 4 5
FRA8 3 2 4 5
DON2 2 4 5
AMY7 1 5
KAT3 3 2 4 5
LOR6 4 1 2 4 5
NON9 0
可以将学生名字符串哈希存取,使用动态二维数组 ,一维行数固定为散列空间存放学生姓名,二维列数动态存储该学生所选的课程,为了避免哈希冲突除了使用前面的拉链法,也根据题目要求有其它的方法。
注意到此题中字符串的格式是固定的,只有四个字符,前三个为大写字母,最后一个是数字。之所以产生哈希冲突是取模的原因,因为一般都是输入的空间大于散列空间,但是此题可以预先确定好最大的输入空间是26*26*26*10,这个数并不是很大,故可不进行取模压缩映射
#include<iostream>
#include<string>
#include<vector>
#include<algorithm>
using namespace std;
int const maxx=26*26*26*10;
vector<int> stu[maxx]; //动态二维数组
int n,k;
int gethash(string x){ //在这里每一个哈希值都是唯一的,不会存在不同的字符串映射出相同的哈希值
int res=0;
for(int i=0;i<3;i++){
res*=26;
res+=x[i];
}
res*=10%maxx;
res+=x[3];
return res%maxx;
}
int main(){
cin>>n>>k;
for(int i=0;i<k;i++){
int cno,num;
cin>>cno>>num;
for(int j=0;j<num;j++){
string tmp;cin>>tmp;
int hashcode=gethash(tmp);
stu[hashcode].push_back(cno);
}
}
for(int i=0;i<n;i++){
string tmp;cin>>tmp;
int hashcode=gethash(tmp); //开始查询,通过计算其哈希值对其进行快速访问
sort(stu[hashcode].begin(),stu[hashcode].end()); //注意输出格式要求,需要排序
cout<<tmp<<' '<<stu[hashcode].size();
for(int j=0;j<stu[hashcode].size();j++){
cout<<' '<<stu[hashcode][j];
}
cout<<endl;
}
return 0;
}
7-2 字符串关键字的散列映射 (25 分)
给定一系列由大写英文字母组成的字符串关键字和素数P,用移位法定义的散列函数H(Key)将关键字Key中的最后3个字符映射为整数,每个字符占5位;再用除留余数法将整数映射到长度为P的散列表中。例如将字符串AZDEG插入长度为1009的散列表中,我们首先将26个大写英文字母顺序映射到整数0~25;再通过移位将其映射为3×322+4×32+6=3206;然后根据表长得到,即是该字符串的散列映射位置。
发生冲突时请用平方探测法解决。
输入格式:
输入第一行首先给出两个正整数N(≤500)和P(≥2N的最小素数),分别为待插入的关键字总数、以及散列表的长度。第二行给出N个字符串关键字,每个长度不超过8位,其间以空格分隔。
输出格式:
在一行内输出每个字符串关键字在散列表中的位置。数字间以空格分隔,但行末尾不得有多余空格。
输入样例1:
4 11
HELLO ANNK ZOE LOLI
输出样例1:
3 10 4 0
输入样例2:
6 11
LLO ANNA NNK ZOJ INNK AAA
输出样例2:
3 0 10 9 6 1
闭散列构造哈希表,使用平方探测法解决哈希冲突。
#include<iostream>
#include<string>
#include<map>
using namespace std;
bool flag[10000];
int n,p;
map<string, int> mp; //存放字符串和其对应的唯一哈希值
void fun(string s,int num){
if(mp[s]!=0){ //该字符串已存在,输出其哈希值
cout<<mp[s];
return;
}
int sum;
//哈希函数,计算哈希值
if(s.length()>=3)
sum=(s[s.length()-3]-65)*32*32+(s[s.length()-2]-65)*32+(s[s.length()-1]-65);
else if(s.length()==1)
sum=s[0]-65;
else if(s.length()==2)
sum=32*(s[0]-65)+(s[1]-65);
sum%=p;
int index;
//如果该字符串第一次出现却哈希值已被使用,则进行平方探测
if(mp[s]==0&&flag[sum]){
for(int j=1;;j++){ //循环找j
index=(sum+j*j)%p; //向上探测
if(!flag[index]){
flag[index]=true;
mp[s]=index;
cout<<index;
break;
}
index=(sum-j*j+p)%p; //同时也向下探测
if(!flag[index]){
flag[index]=true;
mp[s]=index;
cout<<index;
break;
}
}
}
//字符串第一次出现,且哈希值可用,则占用该哈希值
else{
mp[s]=sum;
flag[sum]=true;
cout<<sum;
}
if(num!=n)
cout<<' ';
else
cout<<endl;
}
int main(){
cin>>n>>p;
string tmp;
for(int i=1;i<=n;i++){
cin>>tmp;
fun(tmp,i);
}
return 0;
}
本文介绍了哈希算法的重要性和在字符串处理中的应用,包括直接定址法、除留余数法等构造哈希表的方法,以及处理哈希冲突的闭散列法和拉链法。文中通过具体的PAT题目实例解释了如何使用哈希算法解决字符串问题,强调了在特定情况下避免哈希冲突的策略。
3283

被折叠的 条评论
为什么被折叠?



