哈希表(hash)
散列表(Hash table,也叫哈希表),是根据键(Key)而直接访问在内存储存位置的数据结构。也就是说,它通过计算一个关于键值的函数,将所需查询的数据映射到表中一个位置来访问记录,这加快了查找速度。这个映射函数称做散列函数,存放记录的数组称做散列表。 (来自维基百科)
其中前边说到的离散化也是一种特殊的哈希方式,只不过离散化注重保序性,因此使用二分查找的方法。
其中存在问题就是:可能会把不同的数映射成相同的数,这就是哈希冲突,则我们处理冲突的方法就是将一组关键字映射到一个有限的连续的地址集(区间)上,到时候我们查找的时候就可以顺着这个地址依次查找。
而处理冲突的两种方法:拉链法和开放寻址法
拉链法:

这种方法就是把映射值相同的点像链表一样挂在同一个地址上,当我们寻找的时候就可以通过地址来直接索引。
而寻找这个地址或者说映射的方法就是取模(mod),而mod的数最好就是大于映射范围的第一个质数,这样会更大的减少冲突(数学推理不清楚,听大佬说的)。
取模的方法: k = (x % N + N) % N (N是映射后的范围,这样取模是为了防止负数)
开放寻址法
这种方法只需要开一个数组,不过这个数组的大小最好是映射后范围的2~3倍,那是因为这种方法再寻找映射后结果如果被占用则它顺着这个结果继续向下找直到找到空位。
说的形象一点就好比上厕所:这个坑位有人,咱就必须取下一个坑位,直到找到一个空的坑位。

例题
模拟散列表
维护一个集合,支持如下几种操作:
“I x”,插入一个数x;
“Q x”,询问数x是否在集合中出现过;
现在要进行N次操作,对于每个询问操作输出对应的结果。
输入格式
第一行包含整数N,表示操作数量。
接下来N行,每行包含一个操作指令,操作指令为”I x”,”Q x”中的一种。
输出格式
对于每个询问指令“Q x”,输出一个询问结果,如果x在集合中出现过,则输出“Yes”,否则输出“No”。
每个结果占一行。
数据范围
1 ≤ N ≤ 10^5
−10^9 ≤ x ≤ 10^9
输入样例:
5
I 1
I 2
I 3
Q 2
Q 5
输出样例:
Yes
No
拉链法Code:
#include <iostream>
#include <cstring>
using namespace std;
const int N = 100003; //寻找一个大于映射范围的第一个质数 最好用质数取模
int e[N], ne[N], idx, h[N];
void insert(int x)
{
int k = (x % N + N) % N;
e[idx] = x;
ne[idx] = h[k];
h[k] = idx;
idx++;
}
bool query(int x)
{
int k = (x % N + N) %N;
for(int i = h[k]; i != -1; i = ne[i])
{
if(e[i] == x)
{
return true;
}
}
return false;
}
int main()
{
int n;
scanf("%d", &n);
memset(h, -1, sizeof(h));
while(n--)
{
int x;
char op[2];
scanf("%s%d", op, &x);
if(*op == 'I') insert(x);
else
{
if(query(x)) printf("Yes\n");
else printf("No\n");
}
}
system("pause");
return 0;
}
开放寻址法Code:
#include <iostream>
#include <cstring>
using namespace std;
const int N = 200003, null = 0x3f3f3f3f; //数组开到个数上限的2~3倍, null表示为空
int h[N];
int finds(int x) //两个作用:1.寻找可以插入的位置 2.寻找哈希表中是否存在要查找的数字
{
int t = (x % N + N) % N;
while(h[t] != null && h[t] != x)
{
t++;
if(t == N) t = 0; //如果找到尾则从头寻找
}
return t;
}
int main()
{
memset(h, 0x3f, sizeof(h)); //寻找一个标志 这个标志大于x的范围
int n;
scanf("%d", &n);
while(n--)
{
char op[2];
int x;
scanf("%s%d", op, &x);
if(*op == 'I') h[finds(x)] = x;
else
{
if(h[finds(x)] != null) puts("Yes");
else puts("No");
}
}
return 0;
}
字符串哈希
字符串哈希就是把一个字符串哈希为整数,具体方法就是把一个字符串具体看成一个P进制数(P不确定),然后我们把他换算成十进制数字,这样就可以直接通过数字来判断两个字符串是否相等。(相当厉害并且好用的一种方法)。
给定一个长度为n的字符串,再给定m个询问,每个询问包含四个整数l1,r1,l2,r2,请你判断[l1,r1]和[l2,r2]这两个区间所包含的字符串子串是否完全相同。
字符串中只包含大小写英文字母和数字。
输入格式
第一行包含整数n和m,表示字符串长度和询问次数。
第二行包含一个长度为n的字符串,字符串中只包含大小写英文字母和数字。
接下来m行,每行包含四个整数l1,r1,l2,r2,表示一次询问所涉及的两个区间。
注意,字符串的位置从1开始编号。
输出格式
对于每个询问输出一个结果,如果两个字符串子串完全相同则输出“Yes”,否则输出“No”。
每个结果占一行。
数据范围
1 ≤ n,m ≤ 10^5
输入样例:
8 3
aabbaabb
1 3 5 7
1 3 6 8
1 2 1 2
输出样例:
Yes
No
Yes
思路:直接把每个字符串的哈希值存入一个数组,然后对数组排序后进行判断即可。
进制P,一般取为131,1331..(同样有数学证明)。
字符串换算为10进制范围会很大,所以我们使用 unsigned long long ,溢出会自动对2^64取模。
代码:
1 #include <iostream>
2 #include <algorithm>
3 #include <cstring>
4 #include <cstdio>
5
6 using namespace std;
7
8 typedef unsigned long long ULL;
9
10 const int N = 10010, P = 131;
11 char str[N];
12 ULL a[N];
13
14 ULL hashGet(char str[])
15 {
16 int len = strlen(str);
17 ULL res = 0;
18 for(int i = 0; i < len; i++) //将P进制换算为10进制
19 {
20 res = res*P + str[i];
21 }
22
23 return res;
24 }
25
26 int main()
27 {
28 int n, res = 1;
29
30 scanf("%d", &n);
31 for(int i = 0; i < n; i++)
32 {
33 scanf("%s", str);
34 a[i] = hashGet(str);
35 }
36
37 sort(a, a+n);
38
39 for(int i = 1; i < n; i++)
40 {
41 if(a[i] != a[i-1]) res++;
42 }
43
44 printf("%d\n", res);
45
46 return 0;
47 }
字符串前缀哈希法
前缀和与字符串哈希相结合,可以直接判断出一个字符串中某两段字符串是否相等,不再需要用kmp。
S = "ABCDEFG"
h[0] = 0
h[1] = "A" 的哈希值
h[2] = "AB" 的哈希值
h[3] = "ABC" 的哈希值
h[4] = "ABCD" 的哈希值
.......
利用前缀哈希,就可以计算出所有字符串字段的哈希值,如图求L~R的哈希值,我们已经有了hash[R],hash[L-1]的哈希值。

同样把字符串看作P进制数,则左边就是高位 右边就是低位,要求出L~R的哈希值我们要做的就是把h[L-1]的P进制左移与R的高位对齐,相减即可。
假设进制P为2:两个字符串分别是,求hash

现在要做的就是把2左移两位,然后相减,得出hash,所以这里的P进制数也是这个道理。
L~R计算哈希值计算公式:\(h[R] - h[L-1]*P^{L-R+1}\)
所以再判断两个字串是否相等时可以直接通过两个字符串的左右边界得出两个字符串的哈希值来判断。
例题
给定一个长度为n的字符串,再给定m个询问,每个询问包含四个整数l1,r1,l2,r2,请你判断[l1,r1]和[l2,r2]这两个区间所包含的字符串子串是否完全相同。
字符串中只包含大小写英文字母和数字。
输入格式
第一行包含整数n和m,表示字符串长度和询问次数。
第二行包含一个长度为n的字符串,字符串中只包含大小写英文字母和数字。
接下来m行,每行包含四个整数l1,r1,l2,r2,表示一次询问所涉及的两个区间。
注意,字符串的位置从1开始编号。
输出格式
对于每个询问输出一个结果,如果两个字符串子串完全相同则输出“Yes”,否则输出“No”。
每个结果占一行。
数据范围
1 ≤ n,m ≤ 10^5
输入样例:
8 3
aabbaabb
1 3 5 7
1 3 6 8
1 2 1 2
输出样例:
Yes
No
Yes
#include <iostream>
using namespace std;
typedef unsigned long long ULL;
const int N = 100010, P = 131;
int p[N], h[N];
char str[N];
ULL get(int l, int r)
{
return h[r] - h[l-1]*p[r-l+1];
}
int main()
{
int n, m;
scanf("%d%d", &n, &m);
scanf("%s", 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, r1, l2, r2;
scanf("%d%d%d%d", &l1, &r1, &l2, &r2);
if(get(l1, r1) == get(l2, r2)) puts("Yes");
else puts("No");
}
system("pause");
return 0;
}
哈希表(hash)的更多相关文章
- 算法与数据结构基础 - 哈希表(Hash Table)
Hash Table基础 哈希表(Hash Table)是常用的数据结构,其运用哈希函数(hash function)实现映射,内部使用开放定址.拉链法等方式解决哈希冲突,使得读写时间复杂度平均为O( ...
- 哈希表(Hash)的应用
$hs=@() #定义数组 $hs=@{} #定义Hash表,使用哈希表的键可以直接访问对应的值,如 $hs["王五"] 或者 $hs.王五 的值为 75 $hs=@''@ #定义 ...
- PHP关联数组和哈希表(hash table) 未指定
PHP有数据的一个非常重要的一类,就是关联数组.又称为哈希表(hash table),是一种很好用的数据结构. 在程序中.我们可能会遇到须要消重的问题,举一个最简单的模型: 有一份username列表 ...
- (四)Redis哈希表Hash操作
Hash全部命令如下: hset key field value # 将哈希表key中的字段field的值设为value hget key field # 返回哈希表key中的字段field的值val ...
- 词典(二) 哈希表(Hash table)
散列表(hashtable)是一种高效的词典结构,可以在期望的常数时间内实现对词典的所有接口的操作.散列完全摒弃了关键码有序的条件,所以可以突破CBA式算法的复杂度界限. 散列表 逻辑上,有一系列可以 ...
- 数据结构,哈希表hash设计实验
数据结构实验,hash表 采用链地址法处理hash冲突 代码全部自己写,转载请留本文连接, 附上代码 #include<stdlib.h> #include<stdio.h> ...
- Redis原理再学习04:数据结构-哈希表hash表(dict字典)
哈希函数简介 哈希函数(hash function),又叫散列函数,哈希算法.散列函数把数据"压缩"成摘要,有的也叫"指纹",它使数据量变小且数据格式大小也固定 ...
- 什么叫哈希表(Hash Table)
散列表(也叫哈希表),是根据关键码值直接进行访问的数据结构,也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度.这个映射函数叫做散列函数,存放记录的数组叫做散列表. - 数据结构 ...
- 数据结构 哈希表(Hash Table)_哈希概述
哈希表支持一种最有效的检索方法:散列. 从根来上说,一个哈希表包含一个数组,通过特殊的索引值(键)来访问数组中的元素. 哈希表的主要思想是通过一个哈希函数,在所有可能的键与槽位之间建立一张映射表.哈希 ...
- golang数据结构之散哈希表(Hash)
hash.go package hash import ( "fmt" ) type Emp struct { ID int Name string Next *Emp } //第 ...
随机推荐
- 2022-1-20 Wpf绑定属性
使用UpdateSourceTrigger绑定属性 后台绑定 通过后台代码绑定 UpdateSourceTrigger
- 利用Redis实现向量相似度搜索:解决文本、图像和音频之间的相似度匹配问题
在自然语言处理领域,有一个常见且重要的任务就是文本相似度搜索.文本相似度搜索是指根据用户输入的一段文本,从数据库中找出与之最相似或最相关的一段或多段文本.它可以应用在很多场景中,例如问答系统.推荐系统 ...
- BTC中的数据结构
BTC中的数据结构 普通指针 普通指针存储的是某个结构体在内存中的地址(假如P是指向一结构体的指针,那么P里面存放的就是该结构体在内存中的起始位置) Hash pointer(哈希指针) 对于如下的节 ...
- 12、Spring之基于xml的AOP
阅读本文前,建议先阅读Spring之基于注解的AOP 12.1.环境搭建 创建名为spring_aop_xml的新module,过程参考9.1节 12.1.1.配置打包方式和依赖 <?xml v ...
- 《Kali渗透基础》11. 无线渗透(一)
@ 目录 1:无线技术 2:IEEE 802.11 标准 2.1:无线网络分层 2.2:IEEE 2.3:日常使用标准 2.3.1:802.11 2.3.2:802.11b 2.3.3:802.11a ...
- MySQL数据库触发器讲解 [创建/删除/查询/select into]
刚学习实例完mysql触发器, 前来分享学习经验. 菜鸟装逼, 老鸟勿喷 先来认识一下有关触发器的一些关键词. 在使用触发器时, 这些关键词将被用到, 请记下它们的模样和用途(意思) cre ...
- PYQT5学习(12)Qtabwidget 选项卡及其窗口,Qstackedwidget和Qtabwidget的效果类似,以及系统托盘QsystemtrayIcon
参考博文:https://blog.csdn.net/jia666666/article/details/81669092QTabWidget控件提供一个选项卡和一个页面区域,默认显示第一个选项卡的页 ...
- Solution -「CCPC Winter Camp Day 6 A」Convolution
Description Link. 给定一个数列 \(\sf a_1,a_2,....a_n\),请求出下面这个结果在模 \(\sf 998244353\) 下的答案. \[\sum_{i=1}^{n ...
- Vue源码学习(八):生命周期调用
好家伙, Vue源码学习(七):合并生命周期(混入Vue.Mixin) 书接上回,在上一篇中,我们已经实现了合并生命周期 现在,我们要在我们的初始化过程中,注册生命周期 1.项目目录 红框为本篇 ...
- Python基础—— 模块介绍、模块的使用、包介绍、 包的使用、软件开发目录规范
文章目录 一 模块介绍 二 模块的使用 2.1 import语句 2.2 from-import 语句 2.3 其他导入语法(as) 2.4 循环导入问题 2.5 搜索模块的路径与优先级 2.6 区分 ...