哈希(弱只因篇)
哈希是什么
哈希是什么,我能说我也不知道吗哈哈哈,先来看一下哈希的概念
散列函数(英語:Hash function)又称散列算法、哈希函数,是一种从任何一种数据中创建小的数字“指纹”的方法。
散列函数把消息或数据压缩成摘要,使得数据量变小,将数据的格式固定下来。
该函数将数据打乱混合,重新创建一个叫做散列值(hash values,hash codes,hash sums,或hashes)的指纹。
散列值通常用一个短的随机字母和数字组成的字符串来代表。[1]好的散列函数在输入域中很少出现散列冲突。
在散列表和数据处理中,不抑制冲突来区别数据,会使得数据库记录更难找到。
如今,雜湊演算法也被用來加密存在資料庫中的密碼(password)字串,
由於雜湊演算法所計算出來的雜湊值(Hash Value)具有不可逆(無法逆向演算回原本的數值)的性質,
因此可有效的保護密碼。
简而言之,我认为就是一个加密兼顾转换算法,将每个数据的独特特征利用某个映射折射出来,从而达到快速查找,时间复杂度为O(1);但是有的时候有些数据很抽象,一旦不进行改变的操作,很容易出错,比如数组开到贼大,结果一大片空间没用到的...,或者超出整型范围等等,接下来有几种hash函数的操作
1、直接定址法
直接这个字眼一看就知道,直接将数据本身不同且显而易见的特征提出来当作key。
比如全国普查每个省/直辖市的人口,每个省和直辖市哦都有编号,都是不一样的,那么就是他们的key;
或者一堆数对,找找看有没有重复的,那就key = a * 10 + b;
为什么不可以key = a * b,因为有些的ab是颠倒过来的,这个也叫哈希冲突,后面再细说,
就是很简单运算处理,~~有点像桶排序,也利用数本身的特性~~,可以处理比较简单的数据,不会发生冲突,但是内存占用极大
2、除留余数法
最常用的方法,也就是取一个值p,这个p无限接近或者等于数据的长度,但是需要注意与2的整数幂需要尽可能的远,可以尽可能减小冲突,当然不可能完全避免~~卡数据咯~~
hash[key] = key % p
3、数字分析法
说白了,就是看数字,分析它们最有可能不一样的位置,假设有八十个八位十进制数,但是它们前面两位都是81,第三位只有1、4,最后一位可能是2、5、7,
但是中间的四位数近乎不一样,于是乎我们可以根据这四位来定制他们的哈希地址,简单点想,就分成两部分,两部分相加,然后舍去进位
4、平方取中法
通常在制定哈希函数时,我们无法确定key的全部情况,那么我们就可以将key平方后取中间几位作为哈希地址,因为平方后的key中间几位通常跟平方前的整个key都是相关的
5、折叠法
将key分割成几个位数相同的部分(最后一个部分位数想不想同都可以),将他们叠加在一起,就是key的哈希地址,当key的每一位数字分布大致均匀时就可以采用。例如ISBN
6、随机数法
使用一个随机函数,取key的随机值作为哈希地址,当key长度不等时就可以使用
考虑哈希函数选择方法的因素大概分为:1、计算哈希函数所需的时间;2、key的长度;3、哈希表的大小;4、key的分布情况;5、记录的查找频率
大概就这几种方法,但是这些方法都会有一个共同的问题:哈希冲突,这个是一个很棘手的问题,哈希冲突也就是其中任意组数据取到的哈希地址是一样的,从而导致属于哈希的优点就不复存在了,因为可能取到的数据过于庞大,很难判断冲突发生在哪个地方,但是还是有可以缓解哈希冲突的方法
1、拉链法
不会o(TヘTo),日后学会,立马来更新
2、开放定址法
对于增量序列,一共有两种方法
1.线性探测法:线性,就说明地址是线性相关的,这种一般好定义,也就是当前数据根据哈希函数所指定的key重复,
那么就往下一个的单位移动,直到查到表尾,再重新从表头开始,直到有空位,但是一旦具有一样特征的数据变多,
就会有一个问题---容易发生哈希冲突,导致数据地址积累,大大降低哈希的查找效率
2、平方探测法:查找跳跃长度为线性整数的平方,即0 、 1 、-1 、 (2)^2 、-(2)^2 、3^2 、-(3)^2…。
该方法可以使表中存的元素相对均匀的分布,避免了堆积现象。
3、再哈希法
顾名思义,再一次使用哈希函数,但是前后两次使用的哈希函数时不一样
简言之,就是双哈希、三哈希、四哈希...n哈希,应该不至于这么离谱
4、建立一个公共溢出区
发生哈希冲突的同义词就统统放进溢出区中即可
字符串哈希
字符串怎么哈希?利用他们的ascii码,怎么取取决于个人,将一个字符串转换成一串数字,ascii码加起来吗?加起来,万一叫你找相同的单词,结果两个不同的单词但是他们的字母类别以及数量都一样,没了TAT,妹这么简单,早就应该回了,那么该怎么转换呢,最开始的想法当然也有吸收别人的精华因为我们需要注意字符串的顺序,数量,类别,目前主要是字母字母ascii码刚刚好在100以内,那就乘100,每两位数字放一个字符的ascii,妙啊!但是我的hash数组转不下去啊TAT,先凉拌一会,就算用long long也装不下去啊TAT,这个时候就要想什么东西可以装下这么抽象的数据,欸!可以取余数啊!没办法,人太蠢,想了好久,怕它溢出就裂开了,那么取余的数是不是有考究,而且尽可能避免哈希冲突,我就取2^64作为mod,那么写起来每个都要写,有点麻烦,还有个unsigned long long,它自动帮你取余,多舒服要荔枝,接下来放一个关于字符串哈希模板以及一个刚刚做不久的题单TT
Moba
#include <iostream>
#include <string>
#include <cstring>
using namespace std;
#define base 107//为什么不用一百呢,因为刚刚做一道字符串哈希,突然发现100有一些数据冲突了,油饼啊TAT
#define ull unsigned long long
const int maxn = 1e6 + 10;
ull Hash[maxn];//建立哈希数组记录地址,哦哦切记,不能写hash,因为c++里面有以hash为名的函数
ull m[maxn];//建立这个数组是用来干嘛的呢,因为我们的Hash每加一位,就需要先将前面的乘以base,在后面查找的时候就需要注意把前面的乘上相应的倍数
char str[maxn];
char goal[maxn];
int ans;
ull judge(int l,int r){
return Hash[r] - Hash[l-1]*p[r-l+1];
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
m[0] = 1;
for(int i=1;i<=maxn;i++)p[i] = p[i-1] * base;//预先处理
while(cin >> str >> goal){
ans = 0;
int goal_Hash = 0;
int len = strlen(str);
Hash[0] = str[0] - 'A' + 1;//ascii码转换根据题目来决定
for(int i=0;i<strlen(goal);i++)goal_Hash = goal_Hash * base + goal[i] - 'A' + 1;
for(int i=1;i<len;i++)Hash[i] = Hash[i-1] * base + str[i] - 'A' + 1;//hash值处理
for(int i=strlen(goal)-1;i<len;i++){
if(i==strlen(goal)&&Hash[i]==cnt)ans++;//这个可以拿出去判断,但是放进来要注意越界问题
else if(Hash[i] - Hash[i-strlen(goal)]*p[strlen(goal)] == cnt)ans++;
}
cout << ans << endl;
}
}
Moba.two(双哈希三哈希)
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn = 1e6 + 10;
int base[3] = {31,131,2333333};
int mod[3] = {1000000007,1000000007,999999998}
ll H[3][maxn];
ll p[3][maxn];
string a;
void init(){
p[0][0] = p[1][0] = p[2][0] = 1;
for(int i = 1;i < maxn;i ++){
for(int j = 0;j < 3;j ++){
p[j][i] = p[j][i - 1] * base[j];
}
}
}//预处理
void HASH(){
H[0][0] = H[1][0] = H[2][0] = a[i] - 'a' + 1;//这个差值根据题目而定
for(int i = 1;i < a.size();i ++){
for(int j = 0;j < 3;j ++){
H[j][i] = (H[j][i-1] * base + a[i] - 'a' + 1) % mod[j];
}
}
}//求哈希值
inline ll sum(int l,int r,int k){
return ((H[k][r] - H[k][l - 1] * p[k][r - l + 1]) % mod[k] + mod[k]) + mod[k];
}//求差值
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
init();
while(cin >> a){
处理哈希值
根据题目来制定如何使用哈希值,当然如果MLT,可以考虑使用滚动数组来优化空间复杂度
输出答案
}
}
在数据结构中哈希表是一种效率极高的工具前提用的好,STL中也有hash,就是unordered_map,有兴趣的也可以了解一下map和unordered_map的差别
这里有个很好的视频,关于哈希表的,还有一篇文章hash表
接下来就是我练习的字符串哈希题单(还练没多少,其他关于哈希的后面再继续update)
1、luoguP3370---【模板】字符串哈希---题解
2、luoguP2957---[USACO09OCT]Barn Echoes G---题解
3、POJ3461---Oulipo---题解
4、POJ2406---Power Strings---题解
5、POJ2752---Seek the Name, Seek the Fame---题解
6、luoguP3501---POI2010 ANT-Antisymmetry---题解
7、luoguP3498---[POI2010]KOR-Beads---题解
8、POJ2774---Long Long Message---题解
9、POJ1200---Crazy Search---题解
10、CF1200E---Compress Words---题解
11、BZOJ3916---Three friends---题解
update:2023.3.17
此次更新,将模板优化一下,添加几道新题
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】