AcWing 840. 模拟散列表

news2025/6/8 7:01:25

题目描述


餐前小菜:

在讨论本题目之前先看一个简单的问题:给出 N N N 个正整数 ( a 1 , a 2 , . . . , a n ) (a_1,a_2,...,a_n) (a1,a2,...,an),再给出 M M M 个正整数 ( x 1 , x 2 , . . . , x m ) (x_1,x_2,...,x_m) (x1,x2,...,xm),问这 M M M 个数中的每个数是否在 N N N 个数中出现过,其中 N , M ≤ 1 0 5 N,M ≤ 10^5 N,M105,且所有正整数均不超过 1 0 5 10^5 105。比较容易想到的思路是:对每个欲查询的数 x i x_i xi,遍历 N N N 看看是否存在。该算法时间复杂度为 O ( N M ) O(NM) O(NM),是有很多的优化空间的。
经典思想为用空间换时间,设有一个 hashTable[N],其中 hashTable[ x i x_i xi] == true 表示正整数 x i x_i xi N N N 个正整数 ( a 1 , a 2 , . . . , a n ) (a_1,a_2,...,a_n) (a1,a2,...,an) 中找到了与之相等的数,若为 false 表示没有出现过。我们该怎么做呢?初始时 hashTable 全为 false,对读入的 N N N 个数 ( a 1 , a 2 , . . . , a n ) (a_1,a_2,...,a_n) (a1,a2,...,an),进行预处理将 hashTable[ a i a_i ai] 设置为 true,接着对 M M M 个欲查正整数,直接查看 hashTable[ x i x_i xi] 为 true/false 来判断出现/没出现过。在许多算法里都用到了这样一种方案:把输入的数作为数组下标来进行查询。将查询的时间复杂度降低至 O ( 1 ) O(1) O(1)


分析:

但注意本题中,我们每个数的范围是 [ − 1 0 9 , 1 0 9 ] [-10^9,10^9] [109,109] 这是没有办法作为数组下标进行查询的!因此我们希望能将一些不合适的下标(负数或过大)转换为我们期望的一个范围内。
因此我们引入哈希(散列、hash)的思想,将元素(element, e)通过一个函数转换为整数,使得该整数可以尽量地代表这个元素。该函数称为哈希函数 h ( ) h() h()。即对 e e e,求 h ( e ) h(e) h(e)
看看这道题 − 1 0 9 ≤ e ≤ 1 0 9 -10^9≤e≤10^9 109e109 为整数,可以使用哪些常用的哈希函数呢?

  1. 直接定址法(简单,常用于映射要求不高的题目): h ( e ) = e h(e)=e h(e)=e。餐前小菜中对于需要查找的 x i x_i xi,其实就是使用了该方案,有一个隐式的 h ( x i ) = x i h(x_i)=x_i h(xi)=xi,将要查询的数作为数组下标。
  2. 平方取中法(基本不使用该方法,可忽略):将 e e e 的平方的中间若干位作为 hash 值。
  3. 除留余数法(重要,常用): h ( e ) = e h(e)=e h(e)=e m o d mod mod k k k。通过该哈希函数,可以把很大的数转换为一个不超过 k k k 的数,这样就可以作为可行的数组下标。这里 TableSize(即可 hash 映射的总长度必须大于等于 k k k,不然会产生越界),当 k k k 是一个素数时, h ( e ) h(e) h(e) 能尽可能覆盖 [ 0 , k ) [0,k) [0,k) 范围内的每一个数。这里将 TableSize与 k k k 都设置为相同的素数。但本题远没有这么简单,有两点需要注意:
    ① e 的取值可能为负,在人类世界里负数的模有各种各样的计算方法,但总归会得到一个在现有规则下“正确的”一个非负数解,同理根据高级程序语言的不同计算的方式也不同,但结果却不尽相同,在C/C++中,若对负数求模运算,会得到一个负的解,这显然是与模运算的定义所违背的,因此需要对运算结果进行加工:ans = (e % TableSize + TableSize) % TableSize,这样不论是正数还是负数都能得到正确的解,具体的细节不多做解释,可以将它看作是与加减乘除相类似的算法。
    ② TableSize 该如何选取呢?这是很重要的一点,同时也决定了哈希函数(与 k 有关)的设计。按照上文分析, TableSize 又应该大于等于输入总数 N N N 的质数且根据上文需要大于等于 k k k,而 k 应为一个质数,我们得到一下关系: T a b l e S i z e = k ≥ N TableSize=k≥N TableSize=kN,而 N N N 不是质数,因此不使用它,我们可以使用比 N N N 大的第一个质数。
    // 求比 N 大的第一个质数
    N = 100000
    for (int i = N; ; i ++)
    {
    	bool flag = false;
    	for (int j = 2; j * j <= i; j ++)
    	{
    		if (i % j == 0)
    		{
    			flag = true;
    			break;
    		}
    	}
    	if (flag)
    	{
    		cout << i << endl;
    		break;
    	}
    }
    
    >_: 100003
    

以我们对求余运算的了解,对于两不同的数 e 1 , e 2 e_1,e_2 e1,e2,他们的 hash值可能是相同的,当 e 1 e_1 e1 将表中下标为 h ( e 1 ) h(e_1) h(e1) 的单元占据时, e 2 e_2 e2 便不能再使用这个位置了,此时发生了冲突。

为解决冲突,将介绍一下三种方法,其中第一、二种都计算了新的 hash 值,又称为开放寻址法:

  1. 线性探测法(Linear Probing):当得到 e e e h a s h 值 h ( e ) hash值 h(e) hashh(e) 后,观察到 hashTable 中下标为 h ( e ) h(e) h(e) 的位置已经被其他元素占用,那么就检查下个位置 h ( e ) + 1 h(e)+1 h(e)+1 是否被占用,如果没有,就使用这个位置;如果还是被占用就继续向后检查,当检查长度超出 TableSize 时,就回到表头继续向后查找,知道找到能使用的位置或表中所有位置均被使用过为止。这种做法容易扎堆。同时,由于线性探测法有向后检查的特征,因此 hashTable 的设置至少要为 N 的 2 倍,又根据我们在除留余数法中的分析,TableSize 为 200003。而具体实现中,用一个极大值 ( 0 x 3 f 3 f 3 f 3 f ) (0x3f3f3f3f) (0x3f3f3f3f)来标识一个位置是否被占用,如被占用,则 h a s h T a b l e [ h ( e ) ] = e hashTable[h(e)] =e hashTable[h(e)]=e,否则 h a s h T a b l e [ h ( e ) ] = 0 x 3 f 3 f 3 f 3 f hashTable[h(e)]=0x3f3f3f3f hashTable[h(e)]=0x3f3f3f3f
  2. 平方探测法(Quadratic probing):在平方探测法中,为了尽可能避免扎堆现象,当表中 h ( e ) h(e) h(e) 的位置被占用时,将按下面的顺序检查表中的位置: h ( e ) + 1 2 、 h ( e ) − 1 2 、 h ( e ) + 2 2 、 h ( e ) − 2 2 、 h ( e ) + 3 2 . . . h(e)+1^2、h(e)-1^2、h(e)+2^2、h(e)-2^2、h(e)+3^2... h(e)+12h(e)12h(e)+22h(e)22h(e)+32...。①如果检查过程中 h ( e ) + i 2 > T a b l e S i z e h(e)+i^2>TableSize h(e)+i2TableSize 时(下个位置超出表尾),就把 h ( e ) + i 2 h(e)+i^2 h(e)+i2 对 TableSize 取模;②如果检查过程中 h ( e ) − i 2 < 0 h(e)-i^2<0 h(e)i20时(下个位置超出表头),就将 ( ( h ( e ) − i 2 ) m o d T a b l e S i z e + T a b l e S i z e ) m o d T a b l e S i z e ((h(e)-i^2)modTableSize+TableSize)modTableSize ((h(e)i2)modTableSize+TableSize)modTableSize 作为结果,如果为避免出现负数的麻烦可以只进行正方向的平方探测。有结论证明,如果 e e e [ 0 , T a b l e S i z e ] [0,TableSize] [0,TableSize] 范围内都无法找到位置,当 i ≥ T a b l e S i z e i≥TableSize iTableSize 时,也一定无法找到位置。
  3. 链地址法(拉链法):拉链法不计算新的 hash值,而是把所有 h ( e ) h(e) h(e) 相同的 e e e 连接成一条单链表。,若 e 1 , e 2 e_1,e_2 e1,e2 有相同 hash值,则可以形成这样一个单链表:在这里插入图片描述
    可以看到,线性探测法比较直观而拉链法操作比较多,因此对其进行模拟一下,请结合代码理解!
    在这里插入图片描述

代码(C++)

线性探测法
#include <iostream>

using namespace std;

// TS: TableSize
const int TS = 200003, null = 0x3f3f3f3f;
int hashtable[TS];

// 哈希函数,输入元素返回哈希值用于初步定位 hashtable
// h(e) 返回的是未经线性探测的位置
int h(int e)
{
    return (e % TS + TS) % TS;
}

// find(e) 返回经过线性探测的位置
int find(int e)
{
    int he = h(e);
    while (hashtable[he] != null && hashtable[he] != e)
    {
        he ++;
        if (he == TS) he = 0;
    }
    return he;
}


int main()
{
    int n;
    cin >> n;
    
    // 初始化时,每一位都没有被占用,即没有出现过
    for (int i = 0; i < TS; i ++) hashtable[i] = null;
    
    while (n --)
    {
        char op;
        int e;
        
        cin >> op >> e;
        // 找到最终位置后进行插入
        if (op == 'I') hashtable[find(e)] = e;
        else
        {
            // 通过经过线性探测的位置来判断 e 的性质
            //而不能通过计算一次哈希函数就去hashtable看
            if (hashtable[find(e)] == null) cout << "No" << endl;
            else cout << "Yes" << endl;
        }
    }
}
拉链法
#include <iostream>

using namespace std;

const int TS = 100003;
// TS: TableSize, no: node, ne: next
int hashtable[TS], no[TS], ne[TS], idx;

int h(int e)
{
    // 哈希函数,输入元素返回哈希值用于初步定位 hashtable
    return (e % TS + TS) % TS;
}

void insert(int e)
{
    int he = h(e);
    no[idx] = e;
    ne[idx] = hashtable[he];
    hashtable[he] = idx ++;
}

int find(int e)
{
    int he = h(e);
    // 通过 hashtable[he] 可以查询到最后一个被连接到 h(e) 位置的元素
    // 再顺着该元素往前查看,看 e 是否在此链中出现过
    for (int i = hashtable[he]; i != -1; i = ne[i])
    {
        // i 实际上为 idx
        if (no[i] == e) return true;
    }
    return false;
}


int main()
{
    int n;
    cin >> n;
    
    // 每个位置的链没有连接元素
    for (int i = 0; i < TS; i ++) hashtable[i] = -1;
    while (n --)
    {
        char op;
        int e;
        cin >> op >> e;
        
        if (op == 'I') insert(e);
        else
        {
            if (find(e)) cout << "Yes" << endl;
            else cout << "No" << endl;
        }
    }
}

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

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

相关文章

冷知识|鹤顶红还能用来修长城?

大家好&#xff0c;我是建模助手。 在上篇浅浅地蹭了波热点之后&#xff0c;我灵机一动&#xff0c;倒不如也搞一搞建筑方面的冷知识&#xff1f;冷热搭配&#xff0c;事半功倍... 问问大家&#xff0c;如果谈起古建筑&#xff0c;关键词都有什么&#xff1f;是庄严、震撼、壮…

DHCP实验及配置

DHCP实验配置基于接口拓扑图配置基于全局拓扑图配置基于接口 拓扑图 配置 [Huawei]dhcp enable//在全局下使能DHCP服务 [Huawei]interface GigabitEthernet0/0/0//进入接口 [Huawei-GigabitEthernet0/0/0] ip address 192.168.1.1 255.255.255.0 //配置接口地址 [Huawei-Giga…

什么是QoS?QoS是如何工作的?QoS的实验配置如何进行?

QoS&#xff08;Quality of Service&#xff09;是服务质量的简称。对于网络业务来说&#xff0c;服务质量包括哪些方面呢&#xff1f; 从传统意义上来讲&#xff0c;无非就是传输的带宽、传送的时延、数据的丢包率等&#xff0c;而提高服务质量无非也就是保证传输的带宽&…

人工智能:分享五个目前最火的ChatGPT开源项目

❤️作者主页&#xff1a;IT技术分享社区 ❤️作者简介&#xff1a;大家好,我是IT技术分享社区的博主&#xff0c;从事C#、Java开发九年&#xff0c;对数据库、C#、Java、前端、运维、电脑技巧等经验丰富。 ❤️荣誉&#xff1a; CSDN博客专家、数据库优质创作者&#x1f3c6;&…

Nacos简介(一)

目录 一、概览 二、注册中心基本概念 1) 什么是注册中心&#xff1f; 2) 如果没有注册中心&#xff1f;会怎样 3) 注册中心主要有三种角色&#xff1a; 4) 服务注册中心的作用 5&#xff09;CAP 理论 6&#xff09;CP和AP的选择 三、什么是 Nacos&#xff1f; 四、Nac…

C++——继承那些事儿你真的知道吗?

目录1.继承的概念及定义1.1继承的概念1.2 继承定义1.2.1定义格式1.2.2继承关系和访问限定符1.2.3继承基类成员访问方式的变化2.父类和子类对象赋值转换3.继承中的作用域4.派生类的默认成员函数5.继承与友元6. 继承与静态成员7.复杂的菱形继承及菱形虚拟继承如何解决数据冗余和二…

Spring 5(黑马)

文章目录传统JavaWeb开发的困惑IoC、DI和Aop思想提出Spring框架的诞生Spring 框架概述Spring 框架历史Spring Framework技术栈图示BeanFactory 快速入门DI 入门案例ApplicationContext快速入门BeanFactory 和 ApplicationContext的关系BeanFactory 的继承体系ApplicationContex…

全志H616——安装SQlite库并使用常用的数据库操作指令

在官网下载安装包&#xff1a;SQLite下载页面、https://www.sqlite.org/2022/sqlite-autoconf-3400100.tar.gz&#xff08;安装包&#xff09;下载到MobaXterm_Personal中解压&#xff1a;tar xvf sqlite-autoconf-3400100.tar.gz设置下载路径&#xff1a;./configure --help:.…

滴滴一面:order by 调优10倍,思路是啥?

背景说明&#xff1a; Mysql调优&#xff0c;是大家日常常见的调优工作。 所以&#xff0c;Mysql调优是一个非常、非常核心的面试知识点。 在40岁老架构师 尼恩的读者交流群(50)中&#xff0c;其相关面试题是一个非常、非常高频的交流话题。 近段时间&#xff0c;有小伙伴面…

计算机视觉 吴恩达 week 10 卷积

文章目录一、边缘检测二、填充 padding1、valid convolution2、same convolution三、卷积步长 strided convolution四、三维卷积五、池化层 pooling六、 为什么要使用卷积神经网络一、边缘检测 可以通过卷积操作来进行 原图像 n✖n 卷积核 f✖f 则输出的图像为 n-f1 二、填充…

【软考】系统集成项目管理工程师(二十)项目风险管理

一、项目风险管理概述1. 风险概念2. 风险分类3. 风险成本二、项目风险管理子过程1. 规划风险管理2. 识别风险3. 实施定性风险分析4. 实施定量风险分析5. 规划风险应对6. 控制风险三、项目风险管理流程梳理一、项目风险管理概述 1. 风险概念 风险是一种不确定事件或条件,一旦…

java赫夫曼编码

1.基本介绍 赫夫曼编码也翻译为 哈夫曼编码(Huffman Coding)&#xff0c;又称霍夫曼编码&#xff0c;是一种编码方式, 属于一种程序算法赫夫曼编码是赫哈夫曼树在电讯通信中的经典的应用之一。赫夫曼编码广泛地用于数据文件压缩。其压缩率通常在 20%&#xff5e;90%之间赫夫曼…

[Android]网络框架之Retrofit(kotlin)

目录 Retrofit简介 Retrofit基本使用 Retrofit的注解 Retrofit的转换器 文件的上传与下载 Retrofit简介 Retrofit是一款由Square公司开发的网络库&#xff0c;但是它和OkHttp的定位完全不同。 OkHttp侧重的是底层通信的实现&#xff0c;而Retrofit侧重的是上层接口的封装…

永磁同步电机中BEMF电阻的作用

一、电路原理图 二、原理分析 如图一我们测的是相电压&#xff0c;从理论上我们知道我们测得相电压是一个马鞍波形&#xff0c;马鞍波形中并没有隐含 转子的位置和速度信息。那么为什么我们还要有这样一个电路呢&#xff1f; 这个问题其实困惑了我好久&#xff1f;直到有一天…

曹云金对德云社最大的贡献,就是促进了薪酬体系改革

虽然曹云金已经离开德云社&#xff0c;但是关于他和德云社的话题&#xff0c;却从来没有间断过&#xff0c;尤其是他和小岳岳的对比&#xff0c;更是很有争议的一个话题。实话实说&#xff0c;曹云金在德云社的这些年&#xff0c;对这个这个民间相声社团发展&#xff0c;还是做…

Docker实战

目录一、FROM 语法二、label语法三、run语法四、workdir 语法五、add 和copy 语法六、ENV语法七、volume 和expose 语法八、run、cmd 和entrypoint一、FROM 语法scratch -- 从头开始尽量来使用官方提供的imageFROM 指定基础镜像&#xff0c;最好挑一些apline&#xff0c;slim之…

Qml学习——控件状态

最近在学习Qml&#xff0c;但对Qml的各种用法都不太熟悉&#xff0c;总是会搞忘&#xff0c;所以写几篇文章对学习过程中的遇到的东西做一个记录。 学习参考视频&#xff1a;https://www.bilibili.com/video/BV1Ay4y1W7xd?p1&vd_source0b527ff208c63f0b1150450fd7023fd8 其…

Apache安全加固配置教程(小白篇)

Apache安全加固配置教程(小白篇) 资源宝分享&#xff1a;www.httple.net 一&#xff0c;Apache服务器的介绍 Apache服务器它是Internet网上应用最为广泛的Web服务器软件之一。Apache服务器源自美国国家超级技术计算应用中心&#xff08;NCSA&#xff09;的 Web服务器项目中。目…

SAS应用入门学习笔记3

操作数据集的观测&#xff1a; Eg. 修改变量值等 变量的值取出来&#xff0c;那么我们需要对变量的值进行修改 weight height bmi? Missing 用到条件语if then、赋值语句、表达式 等。 表达式是操作数和操作符的序列。 例如&#xff1a;3 x x1 1、操作数&#xff1a;…

每日学术速递2.9

CV - 计算机视觉 | ML - 机器学习 | RL - 强化学习 | NLP 自然语言处理 Subjects: cs.CV、cs.AI、cs.LG、cs.IR 1.Graph Signal Sampling for Inductive One-Bit Matrix Completion: a Closed-form Solution(ICLR 2023) 标题&#xff1a;归纳单比特矩阵完成的图信号采样&am…