一本通提高篇 哈希和哈希表 (二)哈希表

哈希表

哈希表是一种高效的数据结构。它的优点同字符串哈希一样,查找的算法时间效率几乎就是常数时间,同时也很容易实现,多产生的代价仅仅是消耗内存。

那么什么是哈希表呢 ,我的理解是:按一种分类方式将所有元素分一次类,同一个类别的元素再通过另一种方式存到这个类里
总之就是两次哈希表
假设元素第一次的分类是 D D D,第二次的分类是 E E E,那么我们就可以用 < D , E > <D,E> <D,E>来存储这一个元素
哈希表就长这个样:
在这里插入图片描述
可以说是一个链表
那么我们就可以类比图论中的单向边
存图用 v e c t o r vector vector和前向星 这里也可以用

哈希函数的构造

哈希函数决定了哈希表查找效率的关键,因为只要哈希值的分布足够平均,单次查找链表的复杂度就会尽量地小。
三种效果较好且较为容易实现的哈希函数:

1.除余法
选择一个适当的正整数 b b b,用其对 b b b取模的余数作为哈希值,即 H ( k e y ) = k e y    m o d    b H(key)=key\,\,mod\,\,b H(key)=keymodb,这个方法我用得最多 (其实不会其他的… ,而且多数情况下性价比最高,关键是 b b b的选取。一般选用存储的下的较大的质数(一般情况下根据空间取 1 0 6 10^6 106左右的质数)。这样能尽量避免冲突。
假设 b = 1000 b=1000 b=1000,哈希函数分类最多才 1000 1000 1000类,冲突很多每次查找的常数就越大。还不如多开点空间,用空间换时间更划算一点。
2.乘积取整法
我们用一个 k e y key key乘以一个在 ( 0 , 1 ) (0,1) (0,1)中的实数 A A A (最好是无理数, 5 − 1 2 \frac{\sqrt{5}-1}{2} 25 1是一个实际效果很好的数),得到一个 ( 0 , k ) (0,k) (0,k)之间的实数;取其小数部分,乘以哈希表的大小 M M M再向下取整,即得 k e y key key H a s h Hash Hash表中的位置。函数表达式可以写成: H ( k e y ) = { M ( k e y × A    m o d 1 ) } H(key)=\{M(key×A\,\,mod1)\} H(key)={M(key×Amod1)},其中 { x } \{x\} {x}表示 x x x的小数部分
这方法还是可行的 不过还是第一种的简单
3.基数转换法
基数转换法也是采用字符串哈希所用的转换方法:将 k e y key key值看成是另一种进制的数,然后再把它转换成对应的十进制数,再用除余法对其取余。一般取大于 10 10 10的数作转换的基数,并且两个基数是互质的
如: k e y = 15539 key=15539 key=15539,现在将它看做是十一进制数 ( 15539 ) 11 (15539)_{11} (15539)11,然后将它再转换成十进制数 ( 15539 ) 11 = ( 21943 ) 10 (15539)_{11}=(21943)_{10} (15539)11=(21943)10
这方法??为啥不直接除余法 怕冲突多常数大? 有这进制转换的功夫还不如直接查了…

其实哈希函数的构造方法多种多样,并没有硬性地规定。只要能尽量地规避冲突,都是可以的。

例题

图书管理

题面

S o l u t i o n : Solution: Solution:裸哈希表
用俩哈希搞就okk了
代码:

#include<bits/stdc++.h>
using namespace std;
#define N 300300
#define reg register
#define QAQ puts("QAQ");
const int mod1=155339,mod2=155399,p1=15539,p2=93551;
struct node{
	int to,nxt;
}edge[N];
int n,cnt,len,sum1,sum2,head[N];
char op[10],s[500];
inline void add(int x, int y){
	edge[++cnt].to=y,edge[cnt].nxt=head[x],head[x]=cnt;
}
inline bool query(int x, int y){
	for(reg int i=head[x];i;i=edge[i].nxt){
		if(y==edge[i].to)return true;
	}
	return false;
}
int main(){
	scanf("%d",&n);
	for(reg int i=1;i<=n;i++){
		cin>>op;
		gets(s);len=strlen(s),sum1=sum2=0;
		for(reg int i=0;i<len;i++){
			sum1=(sum1*p1+s[i])%mod1;
			sum2=(sum2*p2+s[i])%mod2;
		}
		if(op[0]=='a')add(sum1,sum2);
		else {
			if(query(sum1,sum2))puts("yes");
			else puts("no");
		}
	}
}
门票

题面
S o l u t i o n : Solution: Solution:
按照模数分类 每个分类后面直接接这个数就行
相当于连一条 n % m o d − > n n\%mod->n n%mod>n的边
代码 ↓ ↓

#include<bits/stdc++.h>
using namespace std;
#define reg register
#define int long long
#define N 2000200
const int mod=1553399;
struct node{
	int to,nxt;
}edge[N];
int a,b,c,cnt,now,head[N];
inline void add(int u, int v){
	edge[++cnt].to=v,edge[cnt].nxt=head[u],head[u]=cnt;
}
inline bool query(int u, int v){
	for(reg int i=head[u];i;i=edge[i].nxt){
		if(edge[i].to==v)return true;
	}
	return false;
}
signed main(){
	scanf("%lld%lld%lld",&a,&b,&c);
	now=1;add(1,1);
	for(reg int i=2;i<=2000001;i++){
		now=(a*now+now%b)%c;
		if(query(now%mod,now)){printf("%lld\n",i-1);return 0;}
		add(now%mod,now);
	}
	puts("-1");
}
收集雪花

题面
S o l u t i o n : Solution: Solution:直接离散化
离散化之后 我们定义一个数组 l s t lst lst记录每个数上次出现的位置
对于每个数 如果上次出现的位置在我们的区间内 我们直接将左区间调到这个位置加一
在每次操作中都要记得维护 a n s ans ans

#pragma GCC optimize(2)
#include<bits/stdc++.h>
using namespace std;
#define N 1000100
#define reg register
inline void read(int &x){
	int s=0,w=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){s=(s<<3)+(s<<1)+(ch&15);ch=getchar();}
	x=s*w;
}
int n,ans,a[N],b[N],c[N],lst[N];
int main(){
	read(n);
	for(reg int i=1;i<=n;i++)read(a[i]),b[i]=a[i];
	sort(b+1,b+1+n);
	for(reg int i=1;i<=n;i++)c[i]=lower_bound(b+1,b+1+n,a[i])-(b+1);
	for(reg int i=1,j=1;i<=n;i++){
		if(lst[c[i]]){
			if(lst[c[i]]>=j)j=lst[c[i]]+1;
			lst[c[i]]=i;
		}
		else lst[c[i]]=i;
		ans=max(ans,i-j+1);
	}
	printf("%d\n",ans);
}

总结

字符串哈希是一种非常高效的算法, O ( 1 ) O(1) O(1)的转移也是非常划算。但有时很容易被卡,也没有一些算法高效,所以用其他方法尽量不用哈希。
哈希表是一种非常实用的数据结构,其哈希函数决定了它的效率。哈希表没什么大毛病,比什么 m a p , u n o r d e r e d _ m a p , s e t , m u l t i s e t map,unordered\_map,set,multiset map,unordered_map,set,multiset常数小多了,也不难打。
P S . PS. PS.变量名一定不要起 h a s h hash hash

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值