哈希表(模拟散列表 字符串哈希)

news2024/12/28 4:18:03

目录

  • 一、哈希表的概念
  • 二、模拟散列表
    • 题目
    • 代码实现
      • ①拉链法
      • ②开放寻址法
  • 三、字符串哈希
    • 题目
    • 思路
    • 注意点
    • 代码实现


一、哈希表的概念

简介

哈希表(又称为散列表),将一个比较大的值域映射到一个小的范围。

例如有哈希函数 h(x),将区间 [ − 1 0 9 , 1 0 9 ] [-10^9,10^9] [109,109]的数字映射到 [ 0 , 1 0 5 ] [0,10^5] [0,105]中。

方法:直接将 x x x m o d mod mod 1 0 5 10^5 105,但是这样会存在哈希冲突。

冲突:两个数映射成了同一个数。)

( 取 模 的 数 尽 可 能 是 质 数 且 与 2 的 整 次 幂 尽 量 远 ) \color{red}{(取模的数尽可能是质数且与2的整次幂尽量远)} 2

(取质数是发生冲突概率最小的方法)

解决哈希冲突的方法:①开放寻址法拉链法

类似于离散化,离散化保序,而哈希表不保序。离散化是一种极其特殊的Hash方式。

一般的操作有:

  • 插入
  • 查找
  • 删除(算法题中一般不用)(通过给对应数打标记来实现)

哈希表的时间复杂度如下:

  1. 插入(Insertion) 操作的平均时间复杂度是 O ( 1 ) O(1) O(1)。在理想情况下,插入一个元素到哈希表中只需要常数时间。然而,在发生哈希冲突(Hash Collision)时,需要处理冲突,可能会导致插入操作的时间复杂度略微增加,但仍然是常数时间的。

  2. 查找(Lookup) 操作的平均时间复杂度是 O ( 1 ) O(1) O(1)。通过哈希函数计算出元素的哈希值,然后在哈希表中进行查找。在理想情况下,查找操作只需要常数时间。然而,如果存在哈希冲突,可能需要遍历哈希表中的某个桶(Bucket)来寻找目标元素,但由于哈希表的设计,这个遍历的代价也是常数时间的。

  3. 删除(Deletion) 操作的平均时间复杂度是 O ( 1 ) O(1) O(1)。类似于插入和查找操作,在理想情况下,删除操作只需要常数时间。即使存在哈希冲突,也可以通过哈希函数计算出目标元素的位置,并进行删除。

需要注意的是,以上时间复杂度是基于平均情况的估计。在极端情况下,例如哈希函数设计不当或者存在大量的哈希冲突,哈希表的性能可能会下降,导致插入、查找和删除操作的时间复杂度接近O(n),其中n是哈希表中存储的元素数量。


二、模拟散列表

题目

题目描述:
维护一个集合,支持如下几种操作:

  1. I x,插入一个数 x
  2. Q x,询问数 x 是否在集合中出现过;

现在要进行 n n n 次操作,对于每个询问操作输出对应的结果。

输入格式:
第一行包含整数 n n n,表示操作数量。
接下来 n n n 行,每行包含一个操作指令,操作指令为I xQ x中的一种。

输出格式:
对于每个询问指令Q x,输出一个询问结果,如果 x 在集合中出现过,则输出 Yes,否则输出 No
每个结果占一行。

数据范围:
1 ≤ n ≤ 1 0 5 1≤n≤10^5 1n105
− 1 0 9 ≤ a ≤ 1 0 9 -10^9≤a≤10^9 109a109

输入样例:

5
I 1
I 2
I 3
Q 2
Q 5

输出样例:

Yes
No

代码实现

①拉链法

拉链法

每个下列吊着的链表可以看作常数个,因此查询的时间复杂度大大降低。

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;

const int N = 1e5 + 3;
int h[N], idx, e[N], ne[N];
void insert(int x)
{
	int k = (x % N + N) % N;
	e[idx] = x;
	ne[idx] = h[k];
	h[k] = 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()
{
	cin.tie(0);
	ios::sync_with_stdio(false);
	memset(h, -1, sizeof(h)); // 注意memset是以字节为单位来设置值
	int n;
	cin >> n;
	while (n--)
	{
		char op;
		int x;
		cin >> op >> x;
		switch (op)
		{
		case 'I':
			insert(x);
			break;
		case 'Q':
			if (query(x)) cout << "Yes" << endl;
			else cout << "No" << endl;
			break;
		default:
			cout << "error" << endl;
		}
	}
	return 0;
}

②开放寻址法

数组通常开到题目要求的数量的2~3倍(依旧找到对应的质数)
其原理相当于用空间换时间:要解决大量冲突会很费时间,开2~3倍可以减少冲突。

开放寻址法
找到数组上对应的位置后,检测有没有被占用,有的话往后找位置放。

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;

const int N = 2e5 + 3;
int h[N], def = 0x3f3f3f3f; // 因为题目的数据范围是1e9,而0x3f3f3f3f大于1e9,所以可以用来做特殊值判断
int find(int x)
{
	int k = (x % N + N) % N;
	while (h[k] != def && h[k] != x)
	{
		k++;
		if (k == N) k = 0; // 如果找到了最后一个位置,那么就回到0
	}
	// 如果存在,返回x存储的位置
	// 如果不存在,返回x应该存储的位置
	return k;
}
int main()
{
	cin.tie(0);
	ios::sync_with_stdio(false);
	int n;
	cin >> n;
	memset(h, 0x3f, sizeof(h));
	while (n--)
	{
		int x, k;
		char op;
		cin >> op >> x;
		k = find(x);
		switch (op)
		{
		case 'I':
			h[k] = x;
			break;
		case 'Q':
			if (h[k] != def) cout << "Yes" << endl;
			else cout << "No" << endl;
			break;
		default:
			cout << "error" << endl;
		}
	}
	return 0;
}

三、字符串哈希

题目

题目描述:
给定一个长度为 n n n 的字符串,再给定 m m m 个询问,每个询问包含四个整数 l 1 , r 1 , l 2 , r 2 l_1,r_1,l_2,r_2 l1,r1,l2,r2,请你判断 [ l 1 , r 1 ] [l_1,r_1] [l1,r1] [ l 2 , r 2 ] [l_2,r_2] [l2,r2] 这两个区间所包含的字符串子串是否完全相同。

字符串中只包含大小写英文字母和数字。

输入格式:
第一行包含整数 n n n m m m,表示字符串长度和询问次数。

第二行包含一个长度为 n n n 的字符串,字符串中只包含大小写英文字母和数字。

接下来 m m m 行,每行包含四个整数 l 1 , r 1 , l 2 , r 2 l_1,r_1,l_2,r_2 l1,r1,l2,r2,表示一次询问所涉及的两个区间。

注意,字符串的位置从 1 1 1 开始编号。

输出格式:
对于每个询问输出一个结果,如果两个字符串子串完全相同则输出 Yes,否则输出 No

每个结果占一行。

数据范围:
1 ≤ n ≤ 1 0 5 1≤n≤10^5 1n105
1 ≤ m ≤ 1 0 5 1≤m≤10^5 1m105

输入样例:

8 3
aabbaabb
1 3 5 7
1 3 6 8
1 2 1 2

输出样例:

Yes
No
Yes

思路

字符串哈希 O ( n + m ) O(n+m) O(n+m)

全称 字符串前缀哈希法,把字符串变成一个 p p p 进制数字**(哈希值)**,实现不同的字符串映射到不同的数字。并且,用 h [ N ] h[N] h[N] 记录字符串前 N N N 个字符的 hash 值,类似于前缀和。

作用就是把 O ( N ) O(N) O(N) 的时间复杂度降为 O ( 1 ) O(1) O(1)。比如本题就是对比任意两段内字符串是不是相同,正常就是类似于一个循环长度次的substr,其实用hash 差就能一步搞定。

例如:

str = "ABCABCDEYXCACWING";
h[0] = 0;
h[1] = "A"的Hash值;
h[2] = "AB"的Hash值;
h[3] = "ABC"的Hash值;
h[4] = "ABCA"的Hash值;

对形如 X 1 , X 2 , X 3 , . . . , X n − 1 , X n X_1,X_2,X_3,...,X_{n−1},X_n X1,X2,X3,...,Xn1,Xn的字符串,采用字符 A S C I I ASCII ASCII码乘上 P P P 次方来计算哈希值。

映射公式: ( X 1 × P n − 1 + X 2 × P n − 2 + . . . + X n − 1 × P 1 + X n × P 0 ) (X_1×P^{n−1} + X_2 × P^{n−2}+...+X_{n−1} × P^1+X_n×P^0) (X1×Pn1+X2×Pn2+...+Xn1×P1+Xn×P0) m o d mod mod Q Q Q

例如:
字符串 A B C D ABCD ABCD P = 131 P=131 P=131

那么 h [ 4 ] = 65 ∗ 13 1 3 + 66 ∗ 13 1 2 + 67 ∗ 13 1 1 + 68 ∗ 13 1 0 h[4]=65∗131^3+66∗131^2+67∗131^1+68∗131^0 h[4]=651313+661312+671311+681310

A B AB AB P = 131 P=131 P=131

说是 h [ 2 ] = 65 ∗ 13 1 1 + 66 ∗ 13 1 0 h[2]=65∗131^1+66∗131^0 h[2]=651311+661310

我们想要求 C D CD CD h a s h hash hash值,怎么求呢?

就是 h [ 4 ] − h [ 2 ] ∗ 13 1 2 h[4]−h[2]∗131^2 h[4]h[2]1312(意义在于将 h [ 4 h[4 h[4] 与 h [ 2 ] h[2] h[2] 的字符串对齐再相减)

构建: h [ i ] = h [ i − 1 ] × P + s [ i − 1 ] , i ∈ [ 1 , n ] h[i]=h[i−1]×P+s[i−1],i∈[1,n] h[i]=h[i1]×P+s[i1],i[1,n],其中 h h h为前缀和数组, s [ i − 1 ] s[i−1] s[i1]为字符串数组此位置字符对应的ASCII码。

应用: 查询 l , r l,r l,r 之间部分字符串的 h a s h = h [ r ] − h [ l − 1 ] × P r − l + 1 hash=h[r]−h[l−1]×P^{r−l+1} hash=h[r]h[l1]×Prl+1


注意点

  • 任意字符不可以映射成 0 0 0,否则会出现不同的字符串都映射成0的情况,比如: A A A A A AA AA A A A AAA AAA 皆为 0 0 0
  • 冲突问题:通过巧妙(经验)设置 P = 131 或 13331 P = 131 或 13331 P=13113331 Q = 2 64 Q = 2^{64} Q=264,一般可以理解为不产生冲突(99.99%概率不冲突)。
  • unsigned long long 的数值范围正好为 0 0 0 ~ 2 64 − 1 2^{64}-1 2641,所以可以直接采用unsigned来接收数字,由于二进制的溢出特性,当 unsigned long long 下的最高位的进位 1 1 1 溢出之后相当于除以 2 64 2^{64} 264

代码实现

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;

typedef unsigned long long ULL;

const int N = 1e5 + 10;
const int P = 131;
char str[N];
ULL h[N], p[N];

ULL get(int l, int r)
{
	return h[r] - h[l - 1] * p[r - l + 1];
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	int n, m;
	cin >> n >> m;
	cin >> str;

	p[0] = 1;
	for (int i = 1; i <= n; ++i)
	{
		p[i] = p[i - 1] * P;
		h[i] = h[i - 1] * P + str[i - 1]; // 由于str是从0开始的,所以读取时往后一格
	}

	while (m--)
	{
		int l1, r1, l2, r2;
		cin >> l1 >> r1 >> l2 >> r2;
		if (get(l1, r1) == get(l2, r2)) cout << "Yes" << endl;
		else cout << "No" << endl;
	}
	return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/581604.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

点到直线距离

点到直线距离最小二乘解释 推倒部分 形象描述是C到AB距离最短&#xff0c;也就是CD最短用数学语言描述是 m i n ∣ ∣ ( B − A ) λ A − C ∣ ∣ min||(B-A) \lambda A - C || min∣∣(B−A)λA−C∣∣ 其中 D ( B − A ) λ A D (B-A) \lambda A D(B−A)λA,其实本质…

c++—断言、异常

一、 断言&#xff0c;主要用于在函数入口处进行参数检查&#xff0c;是否符合参数设置要求&#xff1b; &#xff08;1&#xff09;true&#xff1a;继续执行&#xff1b;false&#xff1a;终止运行&#xff1b; &#xff08;2&#xff09;特点&#xff1a;在程序运行时才能起…

chatgpt赋能python:Python中Decimal模块的使用

Python中Decimal模块的使用 在Python中&#xff0c;用来处理浮点数的内置浮点数类型float&#xff0c;其精度受限于机器上的位数和操作系统的规范。当需要比float类型更高精度的计算时&#xff0c;Python提供了decimal模块。 Decimal模块的导入 from decimal import Decimal…

chatgpt赋能python:Python中的Curdir:介绍与使用

Python中的Curdir: 介绍与使用 Python中的Curdir是一个重要的概念&#xff0c;它表示当前工作目录。Curdir是操作系统中的概念&#xff0c;它在Python中也有着广泛的应用。Curdir不仅仅是一个字符串常量&#xff0c;还是一个有用的属性&#xff0c;通过它我们可以更方便地进行…

34.Mybatis-plus延续

一、Mybatis-Plus。 &#xff08;1&#xff09;ActiveRecord。 简介&#xff1a;ActiveRecord 是一种常见的设计模式之一。ActiveRecord 是一种面向对象的数据库操作模式&#xff0c;它将数据库表映射为类&#xff0c;将表中的行映射为对象。在 ActiveRecord 模式中&#xff…

LAMP安装部署

文章目录 一、LAMP平台与编译安装二、安装部署apache服务三、安装部署MySQL四、安装部署php 一、LAMP平台与编译安装 &#xff08;一&#xff09;、LAMP平台概述 LAMP架构是目前成熟的企业网站应用模式之一&#xff0c;指的是协同工作的一整台系统和相关软件&#xff0c;能够提…

第3章“程序的机器级表示”:访问信息

文章目录 3.4 访问信息3.4.1 操作数指示符3.4.2 数据传送指令3.4.3 数据传送示例 3.4 访问信息 一个 IA32 中央处理单元&#xff08;CPU&#xff09;包含一组八个存储 32 位值的寄存器&#xff0c;这些寄存器用来存储整数数据和指针。 下图显示了这八个寄存器。它们的名字都是…

Python - 批量下载ts文件并合并为mp4

&#xff08;一&#xff09;ts文件下载 网页文件下载其实都可以通过requests.get以文件流的形式获取&#xff0c;并以字节的形式写入本地文件即可。 代码如下&#xff1a; import os import requests def download(url, filenames, dirname):session requests.Session()for …

uniapp水文【uniapp】

文章目录 1、前言2、历史3、发展4、功能5、优缺点6、总结7、附录7.1、高频使用7.2、使用注意 1、前言 Uniapp是一种跨平台的移动应用开发框架&#xff0c;它允许开发者使用一套代码库&#xff0c;同时生成iOS、Android等多个平台的应用程序。这种技术方案可以大大降低开发成本…

NET框架程序设计-第1章.NET框架开发平台体系架构

1.1 .NET 框架基本组成 .NET 框架的核心便是通用语言运行时&#xff08;Commomn Language Runtime&#xff0c;简称 CLR&#xff09;&#xff0c;CLR 是一个可被各种不同的编程语言所使用的运行时。 托管模块(mangaed module)&#xff1a; 一个需要 CLR 才能执行的标准 Window…

实验 3:图形数据结构的实现与应用

东莞理工学院的同学可以借鉴&#xff0c;请勿抄袭 1.实验目的 通过实验达到&#xff1a; 理解和掌握图的基本概念、基本逻辑结构&#xff1b; 理解和掌握图的邻接矩阵存储结构、邻接链表存储结构&#xff1b; 理解和掌握图的 DFS、BFS 遍历操作的思想及其实现&#xff1b; …

威胁情报如何改进 DDoS 保护

分布式拒绝服务 (DDoS) 攻击已成为各种企业的主要威胁&#xff0c;从最小的跨国公司到最大的跨国公司。 根据 2022年全球威胁分析报告&#xff0c;恶意DDoS攻击较2021年增长了150%。此外&#xff0c;DDoS攻击的频率也出现显着上升&#xff0c;令人担忧。 在全球范围内&#x…

港大、南大、清华抢先开源「复刻」版DragGAN,开箱即用!

来源 |新智元 还记得前几天发布的DragGAN吗&#xff1f; 没错&#xff0c;就是那个「轻轻点两下」1秒修图的工具。 ▲拍的照片表情不好&#xff1f;修&#xff01;脸型不够瘦&#xff1f;修&#xff01;脸冲镜头的角度不对&#xff1f;修&#xff01; ▲搞不好&#xff0c;「让…

https 建立连接过程分析

从真实的抓包开始 根据抓包结果可以看到 从客户端发起请求开始&#xff0c;主要经过以下几个过程&#xff1a; 1、TCP 三次握手 2、浏览器发送 Client Hello 到服务器 3、服务器发送Server Hello 、证书、证书状态、服务端密钥交换&#xff0c;到浏览器 4、浏览器发送 客户端密…

【Linux】搭建SFTP文件服务器

一、协议介绍1.1 FTP 协议1.11 特点1.12 基本工作原理 1.2 SFTP协议1.21 特点1.22 基本工作原理 1.3 ssh协议1.31 特点1.32 基本工作原理 1.4 其他常见文件传输协议 二、搭建Linux的SFTP文件服务器三、连接测试3.1 电脑连接3.2 手机连接 一、协议介绍 1.1 FTP 协议 1.11 特点…

chatgpt赋能python:Python如何实现不换行

Python如何实现不换行 Python是一种高级编程语言&#xff0c;它的应用领域非常广泛&#xff0c;尤其是在数据分析、人工智能、网络爬虫等领域中拥有广泛的应用。而在Python中&#xff0c;有时候需要控制输出内容的样式&#xff0c;比如在输出时避免出现换行&#xff0c;这个需…

Bootstrap中的js插件使用

1. 标签页 1.1 init <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport" content"widthdevic…

sqli_labs21-23

less21 这题是cookie注入&#xff0c;如果不登录进去是看不到cookie信息的&#xff0c;所以我们要先登录进去 用户admin&#xff0c;密码admin 抓包后发现cookie字段有base64编码信息&#xff0c;选中后burp自动检测翻译 既然是cookie注入并且字段是被加密了的&#xff0c;说明…

chatgpt赋能python:Python中5/2问题引发的思考

Python中5/2问题引发的思考 在Python中&#xff0c;我们常常遇到数字计算的问题&#xff0c;比如5/2。当我们在Python中执行如下代码时&#xff1a; print(5/2)输出结果为2.5。 但是在其他编程语言中&#xff0c;比如C和Java&#xff0c;同样的计算结果是2&#xff0c;而不是…

chatgpt赋能python:Pythonnumpy库下载教程:学习数据分析必备工具

Python numpy库下载教程&#xff1a;学习数据分析必备工具 介绍 Python是一种优秀的脚本语言&#xff0c;常用于数据分析、机器学习等领域&#xff0c;而Numpy是Python中最基础的科学计算库&#xff0c;提供了大量针对数组及矩阵操作的函数和方法。然而&#xff0c;对于初学者…