题目链接 :点击查看
题目描述 :
维护一个集合,支持如下几种操作:
I x
,插入一个数 x;Q x
,询问数 x 是否在集合中出现过;
现在要进行 N 次操作,对于每个询问操作输出对应的结果。
输入输出格式 :
输入
第一行包含整数 N,表示操作数量。
接下来 N 行,每行包含一个操作指令,操作指令为
I x
,Q x
中的一种。
输出
对于每个询问指令
Q x
,输出一个询问结果,如果 x 在集合中出现过,则输出Yes
,否则输出No
。每个结果占一行。
输入输出样例 :
输入
5
I 1
I 2
I 3
Q 2
Q 5
输出
Yes
No
题目分析 :
散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构 ,这是百度对于hash表的定义,其实hash表的主要用途是将一个较大范围内离散的点映射到较小范围区间(是否有点像离散化?但离散化需要进行保序而hash表却不必)。那么该怎么映射?我们这里采取取模的形式,即较大数据范围 % 较小数据范围,这样就可以保证结果不超过那个较小的数据范围。当然,映射时不同点的地址可能会发生冲突(取模得到同一个结果),在此我们可以采取两种方式来解决hash冲突 -----------开放寻址法和链地址法。
我们先来介绍开放寻址法,其主要思想为若是当前地址已经被占用(发生了hash冲突),则继续向下寻找下标,直到这个地址(下标)之前没有被占用。在此我们定义一个find函数来返回数x在哈希表中的下标,在find中首先用t = (x % N + N) % N ,这是考虑到x为负数的情况,将其余数转化为正数。同时我们需要一个标尺来判断当前下标是否被用过,比如定义null = 0x3f3f3f3f(要大于范围较大的区间的左右端),初始化h为0x3f3f3f3f。这样,如果h[t] != null说明已经被占用过,则t ++ 去寻找下一个地址,当然要是之前hash表中已经存过x这个数,则不用再进行寻址了。特别地,要是t == N,即到达数组边界,令t = 0,再从头开始寻找。开放寻址法详见如下代码。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
using namespace std;
const int N = 2e6 + 7, null = 0x3f3f3f3f;
int h[N];//数组长度一般开到个数上限的2-3倍。
int find(int x) {//find函数用来找到映射的地址
int t = (x % N + N) % N;//将正负数取模的情况合并
while (h[t] != null && h[t] != x) {//h[t]如不等于null且h[t]映射的不是数x,说明t位置之前已经被其他数用过
t ++ ; // 再寻找下一个地址
if (t == N) t = 0;//如果t到达区间边界则从0开始寻找
}
return t;
}
int main() {
memset(h, 0x3f, sizeof(h));//memset是按字节来初始化的,int中有四个字节,初始化成0x3f就是将每个字节都初始化成0x3f,所以每个int就是 0x3f3f3f3f。
int n;
cin >> n;
while (n -- ) {
char op;
int x;
cin >> op >> x;
if (op == 'I') h[find(x)] = x;
else {
if (h[find(x)] == null) cout << "No" << endl;
else cout << "Yes" << endl;
}
}
return 0;
}
接着,我们介绍链地址法。与开放寻址法不同的是,在发生hash冲突的时候,我们不再向下继续寻址,而是在这个节点外接一条链表,链表中的每个节点都是取模后地址相同的点。与寻常单链表的操作基本相同,在接链表时,我们采用头插法进行操作,但是这里是把h[k]看成链表的头节点。链地址法详见如下代码。
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int N = 1e5 + 7;
int h[N], e[N], ne[N], cur;//cur表示当前已经用到的节点下标
void insert(int x) {
int t = (x % N + N) % N;
e[cur] = x;//头插法 , 把h[k]看成单链表的头结点
ne[cur] = h[k];
h[k] = cur ++ ;
}
bool find(int x) {
int t = (x % N + N) % N;//链表末尾的ne域定义为1
for (int i = h[k]; i != -1; i = ne[i]) {
if (e[i] == x) {
return true;
}
}
return false;
}
int main() {
int n;
cin >> n;
memset(h, -1, sizeof(h));
while (n -- ) {
char op;
int x;
cin >> op >> x;
if (op == 'I') insert(x);
else cout << (find(x) ? "Yes" : "No") << endl;
}
return 0;
}
-------------------------------------------------------------------------------
下面我们给出两种方法的模板
开放寻址法
int h[N];
// 如果x在哈希表中,返回x的下标;如果x不在哈希表中,返回x应该插入的位置
int find(int x)
{
int t = (x % N + N) % N;
while (h[t] != null && h[t] != x)
{
t ++ ;
if (t == N) t = 0;
}
return t;
}
拉链法
int h[N], e[N], ne[N], idx;
// 向哈希表中插入一个数
void insert(int x)
{
int k = (x % N + N) % N;
e[idx] = x;
ne[idx] = h[k];
h[k] = 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;
}