哈希表
一、概述
哈希算法是通过哈希函数,将一种数据转化为能够用变量或数组下标表示的数,通过哈希函数转化得到的值,称之为哈希值。哈希表的查找时间几乎是常数时间,哈希函数是决定哈希表查找效率的关键,本次就讲解其中之一的除余法。
二、例题
通过图书管理这道题,让我们开始学习这个算法。
题目描述
图书管理是一件十分繁杂的工作,在一个图书馆中每天都会有许多新书加入。为了更方便的管理图书(以便于帮助想要借书的客人快速查找他们是否有他们所需要的书),我们需要设计一个图书查找系统。
该系统需要支持 2 种操作:
add(s)
表示新加入一本书名为 s 的图书。find(s)
表示查询是否存在一本书名为 s 的图书。输入格式
第一行包括一个正整数 ,表示操作数。 以下 行,每行给出 2 种操作中的某一个指令条,指令格式为:
add s find s
在书名 s 与指令(
add
,find
)之间有一个隔开,我们保证所有书名的长度都不超过 。可以假设读入数据是准确无误的。输出格式
对于每个
find(s)
指令,我们必须对应的输出一行yes
或no
,表示当前所查询的书是否存在于图书馆内。注意:一开始时图书馆内是没有一本图书的。并且,对于相同字母不同大小写的书名,我们认为它们是不同的。
样例
样例输入
4 add Inside C# find Effective Java add Effective Java find Effective Java
样例输出
no yes
三、题目解析
题目大意就是将一些字符串存储起来,后期查找一下某个字符串存不存,这需要极高的查找效率,非常适合使用哈希表。
将字符串映射成一个数存起来,查找这个字符串存不存的时候,就是查找这个数存不存在,某些不同的字符串会映射成一个数字,可以将这些数存起来。
题目地址:https://loj.ac/problem/10034
方法一:双重哈希hash + 数组模拟邻接表
可以将代码的核心部分记下来,以后直接用便可
#include<bits/stdc++.h> using namespace std; const int mod1 = 1e+6,mod2 = 1e+9,p1 = 47,p2 = 79,N=30000; int tot = 0,nxt[N+5],poi[mod1+5],end1[N+5]; void insert1(int x,int y){///数组模拟邻接表 nxt[++tot] = poi[x];///目的是排除哈希值冲突问题 poi[x] = tot; end1[tot] = y; } int query(int x,int y)///查询操作 { for(int i = poi[x];i;i=nxt[i]) if(end1[i]==y) return 1; return 0; } int main(){ int n; char op[10],s[205]; cin>>n; while(n--){ cin>>op; gets(s); int len=strlen(s),sum1 = 0,sum2 = 0; for(int i=0;i<len;i++){ ///双重哈希 核心部分 sum1 = (sum1*p1+s[i])%mod1;/// %mod1可以缩小这个数的映射范围 sum2 = (sum2*p2+s[i])%mod2; } if(op[0]=='a') insert1(sum1,sum2); else if(query(sum1,sum2)) printf("yes "); else printf("no "); } }
大家可能对数组模拟邻接表那里表示不解,改天单独出一个教程。
方法二:双重哈希hash +multimap
使用邻接表存储冲突数据的时候,我就想使用multimap是否会使代码更简洁,multimap是一个键可以有多个值,正好适用于这里。
#include<bits/stdc++.h> using namespace std; const int mod1 = 1e+6,mod2 = 1e+9,p1 = 47,p2 = 79,N=30000; multimap<int,int> sign; int main(){ int n; char op[10],s[205]; cin>>n; while(n--){ cin>>op; gets(s); int len=strlen(s),sum1 = 0,sum2 = 0; for(int i=0;i<len;i++){ ///双重哈希 核心部分 sum1 = (sum1*p1+s[i])%mod1; sum2 = (sum2*p2+s[i])%mod2; } if(op[0]=='a') sign.insert(make_pair(sum1,sum2)); else{ int flag = 0; multimap<int,int>::iterator it; it = sign.find(sum1); ///找到键值sum1第一次出现的位置 for(int i=0;i<sign.count(sum1);i++,it++) ///挨个判断value是否符合, if(it->second == sum2){ printf("yes "); flag = 1; break; } if(flag==0) printf("no "); } } return 0; }
方法三:BKDRhash+multimap
这也是一种常用的方法
#include<bits/stdc++.h> using namespace std; const int mod1 = 1e+6,mod2 = 1e+9,p1 = 47,p2 = 79,N=30000; multimap<int,string> sign; unsigned int BKDRhash(string s){///求哈希值 unsigned int seed = 31,key = 0,i=0; while(i!=s.length()) key = key * seed + (s[i++]); return key & 0x7fffffff; } int main(){ int n; string op,s; cin>>n; while(n--){ cin>>op; getline(cin,s);///输入带空格的字符串 int key = BKDRhash(s)%N; ///控制哈希值映射范围 if(op[0]=='a') sign.insert(make_pair(key,s)); else{ int flag = 0; multimap<int,string>::iterator it; it = sign.find(key); for(int i=0;i<sign.count(key);i++,it++) if(it->second == s){ printf("yes "); flag = 1; break; } if(flag==0) printf("no "); } } return 0; }
学习是一件挺困难的事情,但绝对不是痛苦的。