哈希(HASH) 学习笔记

提示:本篇文章仅仅针对普及组的OIer,并且,这是网上少有的一篇关于介绍哈希代码的文章
注:提高组的大佬们勿喷。

板子题

题目描述

如题,给定\(N\)个字符串(第i个字符串长度为\(M_i\),字符串内包含数字、大小写字母,大小写敏感),请求出\(N\)个字符串中共有多少个不同的字符串。

输入

第一行包含一个整数\(N\),为字符串的个数。
接下来\(N\)行每行包含一个字符串,为所提供的字符串。

输出

输出包含一行,包含一个整数,为不同的字符串个数。

样例输入

5
abc
aaaa
abc
abcc
12345

样例输出

4

提示

时空限制:\(1000\)ms,\(128M\)
数据规模:
对于30%的数据:\(N<=10\)\(M_i≈6\)\(M_{max}<=15\);
对于60%的数据:\(N<=10000\)\(Mi≈100\)\(M_{max}<=150\)
对于100%的数据:N<=100000,Mi≈1000,Mmax<=1500
样例说明:
样例中第一个字符串(abc)和第三个字符串(abc)是一样的,所以所提供字符串的集合为{aaaa,abc,abcc,12345},故共计\(4\)个不同的字符串。

算法理解

哈希,又称散列、杂凑,英文名为Hash。

哈希函数

哈希函数这个东西,一般是提高组的内容,对于普及组来说,哈希的内容如下:

  1. 一个数一般直接 \(mod\) 一个大质数即可
  2. 一堆数全部乘起来然后在\(mod\) 一个大质数(如果有负数要平方,以免出现负下标),最好边乘边模,以免炸\(int\)
  3. 字符串可以利用ASCII码来进行进制转换,但还是要\(mod\) 一个大质数,边乘边模,防炸\(int\)
  4. 至于图、书的哈希函数,普及组一般不会做要求换一篇看看吧
    可能进制转换大家还听不懂,看代码:
for(j=1;j<=s.size();j++)
    	pos=(pos*100+s[j])%MOD;

这里\(pos\)就是哈希值,而 \(s\) 就是一个 \(string\) 类型的字符串。因为ASCII码的范围为32~128,所以这里乘上100就可以了。

当然,很多的地方都用到了Hash,比如说一款程序的密码,密码其实就是利用一种叫做 MD5码 的Hash函数来进行转换,并且产生哈希冲突的可能性为0,且无法破译(最多穷举),所以,我们 只能“重置密码”,而不能“找回密码”,就是这个原因。

哈希冲突

当我们取哈希函数为这样的时候:

    pos=a%5;

这里\(pos\)就是哈希值,而 \(s\) 就是一个 \(int\) 类型的数字。那么当\(a=2\)\(a=7\)的时候,\(pos\)的值都等于\(2\),那么该怎么办呢?其实这个就是哈希冲突。哈希的复杂度其实就在于哈希冲突的可能性,这就是要\(mod\) 一个大质数的原因。

设这一堆要哈希的数为
\(5,4,9,2\)
哈希函数:模\(5\)

方法一:开放寻址法

方法思路:这一个放不下了,就放下一个呗。

状态 \(a_0\) \(a_1\) \(a_2\) \(a_3\) \(a_4\)
\(-1\) \(-1\) \(-1\) \(-1\) \(-1\)
\(5\)进来 \(5\) \(-1\) \(-1\) \(-1\) \(-1\)
\(4\)进来 \(5\) \(-1\) \(-1\) \(-1\) \(4\)
\(9\)进来 \(5\) \(9\) \(-1\) \(-1\) \(4\)
\(2\)进来 \(5\) \(9\) \(2\) \(-1\) \(4\)
我们发现,当当前的数字的位置已经有数字的时候,就会到下一个位置,直到有空位为止。这种方法空间小,速度慢
代码(字符串):
int HASH (int pos){//开放寻址法 s是输入的字符串
	int cur=pos;
	while(!a[cur].empty()){
		if(a[cur]==s) return 0;//已经找到
		cur=(cur+1)%MOD;
	}
	a[cur]=s;
	return 1;//表示没有找到
}
方法二:挂链法

方法思路:新建一个链表,将每一个Hash之后相同的字符串放在同一个链表里面即可,使用一个数组来存放链表(节省空间,防MLE).
首先开一个\(head\)数组。\(head_i\)表示第\(i\)个下标的链表头,\(-1\)表示没有,再开一个\(nex\)数组,\(nex_i\)表示第\(i\)个数的下一个的地址,\(-1\)表示没有。添加的时候从头加。\(a\)数组表示的是数据。\(k\)表示现在储存到\(a\)数组的哪一个下标了。
这种方法时间小空间大。

int HASH(int pos){//挂链法 
	int cur=head[pos];
	while(cur!=-1){
		if(a[cur]==s) return 0;
		cur=nex[cur];
	}
	//空出0号位置,原因:未知 
	a[++k]=s;
	nex[k]=head[pos];
	head[pos]=k;
	return 1;
}

原题解析

其实,这题就是一道裸的哈希,并且也可以用map来做,但是,map其实就是哈希,只不过机带的要稍微慢一点。。。
哈希函数就用处理字符串的方法::进制法,然后不管是开放寻址法还是挂链法都无所谓了,但开放寻址法还是要稍微快一点。
AC代码(这就是个模板,希望大家能加上自己的码风,然后背下来)::

#include<cstdio>
#include<iostream>
#include<cstring>
#define maxn 100039
#define MOD 1000039
using namespace std;
int n,ans,i,j,pos,k;
int head[MOD+39],nex[MOD+39];
string s,a[MOD+39];
int HASH(int pos){//挂链法 
	int cur=head[pos];
	while(cur!=-1){
		if(a[cur]==s) return 0;
		cur=nex[cur];
	}
	//空出0号位置,原因:未知 
	a[++k]=s;
	nex[k]=head[pos];
	head[pos]=k;
	return 1;
}
/*int HASH (int pos){//开放寻址法 
	int cur=pos;
	while(!a[cur].empty()){
		if(a[cur]==s) return 0;
		cur=(cur+1)%MOD;
	}
	a[cur]=s;
	return 1;
}
*/
int main(){
    scanf("%d",&n);
    memset(head,-1,sizeof(head));
    for(i=1;i<=n;i++){
    	cin>>s;
    	pos=0;
    	for(j=1;j<=s.size();j++)
    	    pos=(pos*100+s[j])%MOD;
    	if(HASH(pos)) ans++;
    }
    printf("%d",ans);
	return 0;
}

END.

posted @ 2021-02-15 21:08  jiangtaizhe001  阅读(178)  评论(0)    收藏  举报