模拟散列表

拉链法存储

这里构造哈希表用的方法和本质是拉链存储,但又不通过链表实现,神奇。
idx的作用和之前一篇解释Trie树的构建的方法差不多,可以对比参考一下。

分解函数

int h[N], e[N], ne[N],idx;

1.关于数组存储:
这里的h数组就是散列表的基础模型,通过关键词将不同数分配到同一个卡槽去。
奇妙的用法在于后面的e和ne数组一级idx,e数组存储的是元素,ne数组存储的是在e数组中与之下标一致的元素的下一个元素的指向,相当于链表中next的作用,idx是指针,帮助构造ne。

void Insert(int x)
{
	int k = (x % N + N) % N;//c++里面负数取余数仍是负数,但是数组下标只是整数,所以要这样写
	e[idx] = x;
	ne[idx] = h[k];
	h[k] = idx++;
}

2.插入元素
比如:
x%m m=10
h: {-1, -1, 3, 6, -1, 7, -1, -1, 0, -1}
e: {8, 2, 12, 22, 3, 5, 13, 25, 0, 0}
ne: {-1, -1, 1, 2, -1, -1, 4, 5, 0, 0}
e数组从i到n为先后插入散列表的数,ne为对应相同下标下在e数组的元素指向连接的上一个元素的位置,为-1表现不连接数据,是该条链中插入的第一个元素(先插入的在链表尾,后插入的是链表头指向的元素)
idx标志着元素在e数组放置的位置
比如当存入2以后,idx=1,e[1]=8,ne[1]=h[1]=-1(初始都为-1)更新h[1]的指向为1,h[1]=1,等到插入12时,idx=2,e[2]=12,按照链表插入的顺序,先将新的节点和链表连接也就是ne[2]=h[2],表示数据12连接e[1]=2,接着更新h[2(12%10=2)]指向,h[2]=idx++;->h[2]=2;等到插入22,h[2]=3;
(插入是采用头插法的,先让新的元素连接链表头指向的第一个元素,在更新链表头的指向,使它指向新插入的元素)
在这里插入图片描述

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;
}

2.查找元素,同样通过关键字的一系列操作得到该元素在散列表卡槽的位置,然后对该卡槽上的数据进行遍历,i = ne[i],明白存储的原理这一步就明白了,ne[i]存的是e[i]的上一个元素在e中的位置

完整代码

题目链接–模拟散列表

#include<iostream>
#include<algorithm>
#include<cstring>//memset的头文件
using namespace std;
const int N = 1e5+3;//质数
int h[N], e[N], ne[N],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;
}
void Insert(int x)
{
	int k = (x % N + N) % N;//c++里面负数取余数仍是负数,但是数组下标只是整数,所以要这样写
	e[idx] = x;
	ne[idx] = h[k];
	h[k] = idx++;
}
int main()
{
	int n,x; string ord;
	memset(h, -1, sizeof h);
	cin >> n;
	while (n--)
	{
		cin.ignore();
		cin >> ord >> x;
		if (ord[0] == 'I') Insert(x);
		else {
			if (Find(x)) cout << "Yes" << endl;
			else cout << "No" << endl;
		}
	}
	return 0;
}

开放寻址法存储

这个思路很简单

代码

和上面是同一道题

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
int null= 0x3f3f3f3f;
//0x3f3f3f3f的十进制是1061109567,是10^9级别的(和0x7fffffff一个数量级),而一般场合下的数据都是小于10^9的
//所以它可以作为无穷大使用而不致出现数据大于无穷大的情形。
const int N = 2*1e5 + 3;//开放寻址法一般开题目范围的两到三倍
int h[N];
void Insert(int x)
{
	int k = (x % N + N) % N;
	for (int i = k;; i++)
	{
		if (h[i] == null) {
			h[i] = x;
			return;
		}
	}
}
bool Find(int x)
{
	int k = (x % N + N) % N;
	while (h[k] != null && h[k] != x)k++;
	//这里注意一下,当发现一个位置是空那就是说明k关键词的已经全部找完了
	if (h[k] == x) return true;
	else return false;
}
int main()
{
	memset(h, null, sizeof h);//全部置空
	int n, x;
	char ord;
	cin >> n;
	while (n--)
	{
		cin.ignore();
		cin >> ord >> x;
		if (ord == 'I') Insert(x);
		else
		{
			if (Find(x)) cout << "Yes" << endl;
			else cout << "No" << endl;
		}
	}
	return 0;
}

总结

一般来说都是采用开放寻址法的,因为开放寻址所需要的空间更少,拉链法更容易造成冗余,但是拉链法查找时间比开放寻址法会少。

补充:字符串哈希

(其实散列码相同,字符串也不一定相同)
题目链接–字符串哈希
字符串哈希开两个数组,一个p数组存第i位对应的权值(可以这么说吧…)(左边为高位,右边第一位为0位),一个h数组存从字符串开始到第i位的哈希值
比如str=“ababc” h[1]存的是“a”的哈希值,h[3]存的是"aba"的哈希值。
字符串哈希存储的原理是通过存储从字符串开头到子字符串结尾的哈希值,根据经验值p去131或者13331可以几乎避免哈希冲突,一个子字符串的哈希值几乎是唯一的,存放在对应的h数组里
哈希值的计算比如h[3]=p^2* str[1]+p1*str[2]+p3*str[3];(字符串从第一位开始存储)
所以初始化h[i]=h[i-1]*P+str[i];
因为子串的哈希值几乎是没有冲突的,所以在比对子串是否相等只要计算子串的哈希值是否一致
这里的子串就不一定要求从整个字符串得第一位开始了,可以任意区间截取
原理是这样的:
在这里插入图片描述

#include<iostream>
#include<algorithm>
#include<math.h>
using namespace std;
int P = 131;
//也可以是13331,这两个是经验者,和他配套取模用2^64这样冲突的可能性最小
//这里要求没有冲突,和数字的哈希表不太一样,注意一下,总结一下就是一位配一位
const int N = 1e5 + 5;
typedef unsigned long long ULL;
ULL M = pow(2, 64);
ULL p[N], h[N];
ULL get(int l, int r)
{
	//cout << p[r - l + 1] << " " << pow(P, r - l + 1) << endl;
	return h[r] - h[l - 1] * p[r - l + 1];
}
int main()
{
	int n, m;
	cin >> n >> m;
	char* str = new char(n + 1);
	cin >> str + 1;
	p[0] = 1;
	//Inti
	for (int i = 1; i <= n; i++)
	{
		p[i] = p[i - 1] * P;
		h[i] = h[i - 1] * P + str[i];
		//h[i]表示从第一个字符到第i个字符构成的字符串的哈希值
		//映射公式 (X1×Pn−1+X2×Pn−2+⋯+Xn−1×P1+Xn×P0)modQ
	}
	int l1, r1, l2, r2;
	while (m--)
	{
		cin >> l1 >> r1 >> l2 >> r2;
		if (get(l1, r1) == get(l2, r2)) cout << "Yes" << endl;
		else cout << "No" << endl;
	}
	return 0;
}

在acwing运行正常,但是自己用vs运行会触发一个断点,不明白。。。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值