hash 哈希表

哈希表是一种期望算法。

一般情况下,哈希表的时间复杂度是 O(1)。

作用

将一个复杂数据结构映射到一个较小的空间 0~N(10^5~10^6),最常用的情景:将 0~10^9 映射到 0~10^5。

离散化是一种及其特殊的哈希方式。离散化是需要保序的,是要求单调递增的。

映射的方法叫做哈希函数。

哈希函数

取模

x mod 10^5将 x 映射到 0~10^5 中。

模数的选择

模的数一般取质数,即 mod 后面的数一般取质数,并且距离2的整数次幂远,这样取冲突的概率最小【由此可以确定映射数组的长度】。

做题时,选择超过数据范围的第一个质数。

存在冲突

因为 x 的定义域比较大,而映射到的值域比较小,就可能将若干不同的数映射为一个相同的数,产生冲突。

处理方式

  1. 开放寻址法
  2. 拉链法:每个哈希表中的元素都是一个链表的头节点。每当将一个值映射到某个空间,那就将这个值放在该空间所代表的链表中。

存储结构

拉链法

处理冲突

h(x)∈(0,10^5),开一个宽度为10^5的数组,数组下标是0~10^5-1。{ 将映射的数组当成一个槽,在槽上拉链,如果两个数是冲突的,那么就用一条链将其相连,该链就是单链表 }h(a)=b【将a映射为b】,就在数组下标为b的元素上拉一条链,用于存储a;h(c)=b【将c映射为b】,在数组下标为b的元素的链上再存上c【在该链的末尾和开头增加都可以】。

哈希表是一种期望算法, 从平均情况上看,每条链的长度可以看成一个常数,一般情况下,哈希表的时间复杂度是 O(1)。

操作

一般只有添加、查找操作,没有删除操作。

数据

h,映射得到的数组,数组中每个元素作为该槽对应的单链表的头指针存在

e,单链表的数据域

ne,单链表的指针域

idx,存储当前已经用到哪个点

添加

添加 x,先求 h(x),根据 h(x) 确定 x 对应的槽【将 h(x) 作为下标查找哈希表中对应的链表】,然后将 x 插到该槽对应的链【单链表】上。

为什么不直接int k=x%N?

如果这样,正数取模结果是一个正数,负数取模结果是一个负数。先取模再加N,使得结果一定是一个正数,然后再取模,这样无论是正数还是负数取模的结果都是正数。

void insert(int x){
    int k=(x%N+N)%N;
    e[idx]=k;
    ne[idx]=h[k];
    h[k]=idx;
    idx++;
}
查找

查找 x,先求h(x),确定 x 对应的槽,然后遍历该槽对应的链表,查看链表中是否存在 x 即可。

bool find(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;
}
删除

算法中要实现删除操作,不会真的将该点删除。一般情况是开一个数组,在每个点上做一个标记,比如使用 bool 变量。

开放寻址法

只开一个一维数组,该数组的长度要达到题目要求的 2~3 倍【经验值】,此时冲突的概率比较低。这样会导致在查找时在遍历完数组一遍之前一定会停止,不会一直找下去。

处理冲突

h(x)=k,先查看数组中下标为 k 个位置是否被占用,若无,自己占用,若有,去下一个位置,直到找到一个未被占用的位置将其占用。

操作

memset(h,0x3f,sizeof h);为什么不初始化为0x3f3f3f3f?

按字节memset,不是按数memset,h是int型数组,有四个字节,每一个字节都是0x3f,一共有4个字节,就是0x3f3f3f3f。

寻找位置

若x在哈希表中存在,返回其所在位置,不存在返回其应该存储的位置。

若是查找途中越界,则应该从数组开头继续找,直到整个数组遍历了一遍。

int find(int x){//若x在哈希表中存在,返回其所在位置,不存在返回其应该存储的位置
    int k=(x%N+N)%N;
    while(h[k]!=null&&h[k]!=x){
        k++;
        if(k==N)//看完最后一个位置,回到数组开头
            k=0;
    }
    return k;
}
添加

添加 x,x 通过哈希函数计算得到哈希值,将其作为我们所期望的下标,若存在冲突,向后查看,找到后面第一个没有被占用的位置,将数据 x 放入。

int k=find(x);

h[k]=x;

查找

查找 x,x 通过哈希函数计算得到哈希值,将其作为我们所期望的下标进行查找,若当前位置没有数据,则数据不存在,若该位置有数据且是所要查找的值,则查找成功,若该位置有数据但不是所要查找的值,则继续向后进行遍历查找,直到没数据或找到数据为止。若是查找途中越界,则应该从数组开头继续找,直到整个数组遍历了一遍。

int k=find(x);

if(h[k]!=null)

puts("Yes");

else

puts("No");

删除

删除 x,与拉链法类似,按查找的方式找 x,找到后做标记即可。

例题——模拟散列表

题目描述

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

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

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

输入格式

第一行包含整数 N ,表示操作数量。

接下来 N 行,每行包含一个操作指令,操作指令为 I xQ 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

拉链法

#include<iostream>
#include<cstring>
using namespace std;
const int N=100003;//是质数且距离2的整数次幂远
int h[N];//开的槽
int e[N],ne[N],idx;//链表
void insert(int x){
    int k=(x%N+N)%N;
    e[idx]=k;
    ne[idx]=h[k];
    h[k]=idx;
    idx++;
}
bool find(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--){
        char op[2];
        int x;
        scanf("%s%d",op,&x);
        if(op[0]=='I')
            insert(x);
        else{
            if(find(x))
                puts("Yes");
            else
                puts("No");
        }
    }
}

开放寻址法

#include<iostream>
#include<cstring>
using namespace std;
const int N=200003;//是质数且距离2的整数次幂远
const int null=0x3f3f3f3f;//约定:数组元素值为0x3f3f3f3f时,该位置为空
int h[N];
//若x在哈希表中存在,返回其所在位置,不存在返回其应该存储的位置
int find(int x){
    int k=(x%N+N)%N;
    while(h[k]!=null&&h[k]!=x){
        k++;
        if(k==N)//看完最后一个位置,回到数组开头
            k=0;
    }
    return k;
}
int main(){
    int n;
    scanf("%d",&n);
    memset(h,0x3f,sizeof h);//按字节memset
    while(n--){
        char op[2];
        int x;
        scanf("%s%d",op,&x);
        int k=find(x);
        if(op[0]=='I'){
            h[k]=x;
        }
        else{
            if(h[k]!=null)
                puts("Yes");
            else
                puts("No");
        }
    }
    return 0;
}

字符串哈希方式

字符串前缀哈希法

先预处理出字符串所有前缀的哈希值,h[0] 具有特殊含义,表示前0个字符的哈希值,h[n] 表示前n个字符的哈希值。之后可以使用前缀的哈希值使用 O(1) 的时间复杂度算出任意一个子串的哈希值。

字符串前缀哈希值的定义方式

将字符串看成 p 进制的数,字符串的每一个字符当作 p 进制数的每一位数字,再将该 p 进制的数换算为一个十进制的数,因此可将整个字符串转化为一个特别大的数,然后再将该数模上一个比较小的数 Q,通过取模就可将该数映射到 0~Q-1 之间的一个数。

注意
  1. 一般情况下,不将要字母映射为 0,避免产生冲突【例如,将‘A’映射为0,那么字符串“A”的 p 进制是 0,转化为十进制也是 0;字符串“AA”的 p 进制是 0,转化为十进制也是 0,会产生冲突】。

原因:若该字母映射为0,那一个完全由该字母组成的字符串,无论有多少位,他的hash值始终是0,会冲突。

  1. 假定 Rp 足够好,不存在冲突

当 P=131 或 P=13331,Q=264 时,一般不会存在冲突。

  1. 其实字母映射的值不重要

其实字母映射出来的值具体是什么其实不重要,进制数主要是用于求取hash值的。所以无论是字母还是数字,只要保证两个不同的字母或数字,二者对应的整型值不同,那将该值对应的整型直接利用即可。·

求前缀子串的哈希值

h[0]=0;

h(i)=h(i-1)*P+str[i];//字母从下标为1开始存储

作用

可以利用前缀哈希,利用一个公式算出任意一个子串的哈希值。

如何求区间[L,R]子串的哈希值?

字符串下标从1开始,已知 1~L-1【h[L-1]】和 1~R【h[R]】的哈希值。

左边是高位,右边是低位。在 h[R] 中,R是第0位,1是第R-1位;在 h[L-1] 中,L-1是第0位,1是第L-2位。

将 h[L-1] 这一段向左移,移到和 h[R] 对齐为止,即 h[L-1]*pR-L+1 ,则区间 [L,R] 子串的哈希值是 h[R]-h[L-1]*pR-L+1 。

直接使用 unsigned long long 存储所有哈希值,就不需要取模了,溢出就相当于取模【溢出等价于模上264】。

预处理完所有前缀的哈希值之后,就可以使用 O(1) 的时间复杂度算出任意一个子串的哈希值。

为什么h[R] - h[L-1]*pR-L+1可以求出[L,R]子串的hash?

因为(a - b) % c == (a%c - b%c) % c

[L,R]子串就是R子串的hash - L子串hash*(p的R-L差值之幂)

为什么不需要取模?

由于unsigned long long 自动取模,所以可用此公式求出子串hash。

例题——字符串哈希

#include<iostream>
using namespace std;
typedef unsigned long long ULL;
const int N=100010,P=131;
char str[N];
ULL h[N],p[N];//p数组用来存储p的多少次方
int n,m;
ULL get(int l,int r){
    return h[r]-h[l-1]*p[r-l+1];
}
int main(){
    scanf("%d%d%s",&n,&m,str+1);
    p[0]=1;
    for(int i=1;i<=n;i++){
        p[i]=p[i-1]*P;
        h[i]=h[i-1]*P+str[i];//因为映射字母成多少都无所谓,所以可以直接使用ASCII码
    }
    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");
    }
    return 0;
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【SAP-ABAP】--MRKO隐式增强字段步骤

业务需求&#xff1a;给MRKO增加几个增强字段 给标准表进行增强 1.如果标准表或者结构&#xff0c;带CL_***&#xff0c;一般表示SAP预留的增强位置&#xff0c;可以 直接双击这个类型&#xff0c;点击创建&#xff0c;然后直接在预留的结构里面添加自己 需要增加的字段 2.如…

无线物理层安全大作业

这个标题很帅 Beamforming Optimization for Physical Layer Security in MISO Wireless NetworksProblem Stateme![在这里插入图片描述](https://img-blog.csdnimg.cn/58ebb0df787c4e23b0c7be4189ebc322.png) Beamforming Optimization for Physical Layer Security in MISO W…

wpf devexpress Property Grid创建属性定义

WPF Property Grid控件使用属性定义定义如何做和显示 本教程示范如何绑定WP Property Grid控件到数据和创建属性定义。 执行如下步骤 第一步-创建属性定义 添加PropertyGridControl组件到项目。 打开工具箱在vs&#xff0c;定位到DX.23.1: Data 面板&#xff0c;选择Prope…

Spring 如何自己创建一个IOC 容器

IOC(Inversion of Control),意思是控制反转&#xff0c;不是什么技术&#xff0c;而是一种设计思想&#xff0c;IOC意味着将你设计好的对象交给容器控制&#xff0c;而不是传统的在你的对象内部直接控制。 在传统的程序设计中&#xff0c;我们直接在对象内部通过new进行对象创建…

ssrf学习笔记总结

SSRF概述 ​ 服务器会根据用户提交的URL 发送一个HTTP 请求。使用用户指定的URL&#xff0c;Web 应用可以获取图片或者文件资源等。典型的例子是百度识图功能。 ​ 如果没有对用户提交URL 和远端服务器所返回的信息做合适的验证或过滤&#xff0c;就有可能存在“请求伪造”的…

新品首发 | HP1011:高性能双相交错 PFC 数字控制器

随着PFC技术的发展&#xff0c;不断有新型PFC拓扑结构提出&#xff0c;如单相PFC、交错并联 PFC、传统无桥PFC、图腾柱无桥 PFC等。交错Boost PFC系统不仅具有并联系统的所有优点&#xff0c;还能减少输入电流纹波&#xff0c;降低开关管的电流应力。在中大功率场所通常采用工作…

【汇编】栈及栈操作的实现

文章目录 前言一、栈是什么&#xff1f;二、栈的特点三、栈操作四、8086cpu操作栈4.1 汇编指令4.2 汇编代码讲解问题&#xff1a;回答&#xff1a; 4.3 栈的操作4.3 push 指令和pop指令的执行过程执行入栈(push)时&#xff0c;栈顶超出栈空间执行出栈(pop)时&#xff0c;栈顶超…

图像分类系列(四) InceptionV2-V3学习详细记录

前言 上一篇我们介绍了Inception的原始版本和V1版本&#xff1a;经典神经网络论文超详细解读&#xff08;三&#xff09;——GoogLeNet学习笔记&#xff08;翻译&#xff0b;精读代码复现&#xff09; 这个结构在当时获得了第一名&#xff0c;备受关注。但InceptionV1是比较复…

机器学习第4天:模型优化方法—梯度下降

文章目录 前言 梯度下降原理简述 介绍 可能的问题 批量梯度下降 随机梯度下降 基本算法 存在的问题 退火算法 代码演示 小批量梯度下降 前言 若没有机器学习基础&#xff0c;建议先阅读同一系列以下文章 机器学习第1天&#xff1a;概念与体系漫游-CSDN博客 机器学习…

随着大模型中数据局限问题的严峻化,向量数据库应运而生

向量数据库与亚马逊大模型 什么是向量数据库 向量嵌入&#xff08;vector embedding&#xff09;已经无处不在。它们构成了许多机器学习和深度学习算法的基础&#xff0c;被广泛运用于各种应用&#xff0c;从搜索引擎到智能助手再到推荐系统等。通常&#xff0c;机器学习和深度…

解析 Python requests 库 POST 请求中的参数顺序问题

在这篇文章中&#xff0c;我们将探讨一个用户在使用Python的requests库进行POST请求时遇到的问题&#xff0c;即参数顺序的不一致。用户通过Fiddler进行网络抓包&#xff0c;发现请求体中的参数顺序与他设置的顺序不符。我们将深入了解POST请求的工作原理&#xff0c;并提供解决…

使用requests库设置no_proxy选项的方法

问题背景 在使用requests库进行HTTP请求时&#xff0c;如果需要使用爬虫IP服务器&#xff0c;可以通过设置proxies参数来实现。proxies参数是一个字典&#xff0c;其中包含了爬虫IP服务器的地址和端口号。然而&#xff0c;当前的requests库并不支持通过proxies参数来设置no_pr…

DIY私人图床:使用CFimagehost源码自建无需数据库支持的PHP图片托管服务

文章目录 1.前言2. CFImagehost网站搭建2.1 CFImagehost下载和安装2.2 CFImagehost网页测试2.3 cpolar的安装和注册 3.本地网页发布3.1 Cpolar临时数据隧道3.2 Cpolar稳定隧道&#xff08;云端设置&#xff09;3.3.Cpolar稳定隧道&#xff08;本地设置&#xff09; 4.公网访问测…

c题目9:证明1000以内的偶数可以写成两个质数之和

每日小语 心若没有栖息的地方&#xff0c;在哪都是流浪。——三毛 自己敲写 这里需要用到一个联系&#xff1a;oushuprime1prime2 这个问题在于将这个联系变换&#xff0c;用于让我们判断是否是质数&#xff0c;转换后可以方便清晰的理解&#xff0c;并且减掉一个变量。 这…

3.ubuntu20.04环境的ros搭建

ros搭建比较简单&#xff0c;主要步骤如下&#xff1a; 1.配置ros软件源&#xff1a; sudo sh -c echo "deb http://packages.ros.org/ros/ubuntu $(lsb_release -sc) main" > /etc/apt/sources.list.d/ros-latest.list 2.配置密钥 sudo apt-key adv --keyser…

MATLAB 模型预测控制(MPC)控制入门 —— 设计并仿真 MPC 控制器

系列文章目录 文章目录 系列文章目录前言一、使用 MPC Designer 设计控制器1.1 CSTR 模型1.2 导入被控对象并定义 MPC 结构1.3 定义输入和输出通道属性1.4 配置仿真场景1.5 配置控制器水平线1.6 定义输入约束条件1.7 指定控制器调整权重1.8 消除输出超调1.9 测试控制器抗干扰能…

node 第十九天 使用node插件node-jsonwebtoken实现身份令牌jwt认证

实现效果如下 前后端分离token登录身份验证效果演示 node-jsonwebtoken 基于node实现的jwt方案&#xff0c; jwt也就是jsonwebtoken, 是一个web规范可以去了解一下~ 一个标准的jwt由三部分组成 第一部分&#xff1a;头部 第二部分&#xff1a;载荷&#xff0c;比如可以填入加密…

探索Scrapy中间件:自定义Selenium中间件实例解析

简介 Scrapy是一个强大的Python爬虫框架&#xff0c;可用于从网站上抓取数据。本教程将指导你创建自己的Scrapy爬虫。其中&#xff0c;中间件是其重要特性之一&#xff0c;允许开发者在爬取过程中拦截和处理请求与响应&#xff0c;实现个性化的爬虫行为。 本篇博客将深入探讨…

Pytorch torch.dot、torch.mv、torch.mm、torch.norm的用法详解

torch.dot的用法&#xff1a; 使用numpy求点积&#xff0c;对于二维的且一个二维的维数为1 torch.mv的用法&#xff1a; torch.mm的用法 torch.norm 名词解释&#xff1a;L2范数也就是向量的模&#xff0c;L1范数就是各个元素的绝对值之和例如&#xff1a;

Linux环境下C++ 接入OpenSSL

接上一篇&#xff1a;Windows环境下C 安装OpenSSL库 源码编译及使用&#xff08;VS2019&#xff09;_vs2019安装openssl_肥宝Fable的博客-CSDN博客 解决完本地windows环境&#xff0c;想赶紧在外网环境看看是否也正常。毕竟现在只是HelloWorld级别的&#xff0c;等东西多了&am…