散列表(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)的更多相关文章

  1. 算法与数据结构基础 - 哈希表(Hash Table)

    Hash Table基础 哈希表(Hash Table)是常用的数据结构,其运用哈希函数(hash function)实现映射,内部使用开放定址.拉链法等方式解决哈希冲突,使得读写时间复杂度平均为O( ...

  2. 哈希表(Hash)的应用

    $hs=@() #定义数组 $hs=@{} #定义Hash表,使用哈希表的键可以直接访问对应的值,如 $hs["王五"] 或者 $hs.王五 的值为 75 $hs=@''@ #定义 ...

  3. PHP关联数组和哈希表(hash table) 未指定

    PHP有数据的一个非常重要的一类,就是关联数组.又称为哈希表(hash table),是一种很好用的数据结构. 在程序中.我们可能会遇到须要消重的问题,举一个最简单的模型: 有一份username列表 ...

  4. (四)Redis哈希表Hash操作

    Hash全部命令如下: hset key field value # 将哈希表key中的字段field的值设为value hget key field # 返回哈希表key中的字段field的值val ...

  5. 词典(二) 哈希表(Hash table)

    散列表(hashtable)是一种高效的词典结构,可以在期望的常数时间内实现对词典的所有接口的操作.散列完全摒弃了关键码有序的条件,所以可以突破CBA式算法的复杂度界限. 散列表 逻辑上,有一系列可以 ...

  6. 数据结构,哈希表hash设计实验

    数据结构实验,hash表 采用链地址法处理hash冲突 代码全部自己写,转载请留本文连接, 附上代码 #include<stdlib.h> #include<stdio.h> ...

  7. Redis原理再学习04:数据结构-哈希表hash表(dict字典)

    哈希函数简介 哈希函数(hash function),又叫散列函数,哈希算法.散列函数把数据"压缩"成摘要,有的也叫"指纹",它使数据量变小且数据格式大小也固定 ...

  8. 什么叫哈希表(Hash Table)

    散列表(也叫哈希表),是根据关键码值直接进行访问的数据结构,也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度.这个映射函数叫做散列函数,存放记录的数组叫做散列表. - 数据结构 ...

  9. 数据结构 哈希表(Hash Table)_哈希概述

    哈希表支持一种最有效的检索方法:散列. 从根来上说,一个哈希表包含一个数组,通过特殊的索引值(键)来访问数组中的元素. 哈希表的主要思想是通过一个哈希函数,在所有可能的键与槽位之间建立一张映射表.哈希 ...

  10. golang数据结构之散哈希表(Hash)

    hash.go package hash import ( "fmt" ) type Emp struct { ID int Name string Next *Emp } //第 ...

随机推荐

  1. 微信的 h5 支付和 jsapi 支付

    目录 申请商户号 申请商户证书 设置APIv3密钥 下载 SDK 开发包 下载平台证书 关联 AppID 账号 开通 H5 支付 H5支付流程 开通 JSAPI 支付 JSAPI 支付流程 通用微信支 ...

  2. 角度新奇!第一次看到这样使用MyBatis的,看得我一愣一愣的。

    你好呀,我是歪歪. 这期给大家分享一个读者给我分享的一个关于 MyBatis 的"编程小技巧",说真的,这骚操作,直接把我看得一愣一愣的. 我更情愿叫它:坑你没商量之埋雷大法. D ...

  3. Go Web项目结构 + 基础代码

    Go Web工程 下面是项目的包图,可以通过包图来理清项目包的结构. Go Web工程 下面是项目的包图,可以通过包图来理清项目包的结构. 因为我是从Java转过来的,其实这种包的结构与Java的类似 ...

  4. wget 命令的使用:HTTP文件下载、FTP文件下载--九五小庞

    1. wget 命令简介与安装wget是用于在命令行终端下载网络文件的开源免费的命令工具,支持 HTTP/HTTPS.FTP/FTPS 协议的下载.wget 与 curl 相似,curl 可以理解为是 ...

  5. 原来你是这样的JAVA[06]-反射

    1.JVM为每个加载的class及interface创建了对应的Class实例来保存class及interface的所有信息: 获取一个class对应的Class实例后,就可以获取该class的所有信 ...

  6. 了解JAVA内存模型(JMM)

    1.概述 我们常说的JMM指的是Java内存模型(Java Memory Model,JMM),主要用于控制Java程序解决线程间如何通信和数据同步,JMM规范了多线程访问共享内存时的 可见性.有序性 ...

  7. Domain Admin域名和SSL证书过期监控到期提醒

    基于Python3 + Vue3.js 技术栈实现的域名和SSL证书监测平台 用于解决,不同业务域名SSL证书,申请自不同的平台,到期后不能及时收到通知,导致线上访问异常,被老板责骂的问题 核心功能: ...

  8. 支持JDK19虚拟线程的web框架,之四:看源码,了解quarkus如何支持虚拟线程

    欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos 本篇概览 本篇是<支持JDK19虚拟线程的web ...

  9. Docker Swarm + Harbor + Portainer 打造高可用,高伸缩,集群自动化部署,更新。

    Docker Swarm是Docker官方自带的容器编排工具,Swarm,Compose,Machine合称Docker三剑客.Docker Swarm对于中小型应用来说,还是比较方便,灵活,当然K8 ...

  10. Redis系列23:性能优化指南

    Redis系列1:深刻理解高性能Redis的本质 Redis系列2:数据持久化提高可用性 Redis系列3:高可用之主从架构 Redis系列4:高可用之Sentinel(哨兵模式) Redis系列5: ...