AcWing 840. 模拟散列表 (解决hash冲突两种方法---开放寻址法和链地址法)

题目链接 :点击查看

题目描述 :

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

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

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

输入输出格式 :

输入

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

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

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

在森林中麋了鹿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值