• 学习笔记:字符串-Hash


    Hash

    Hash算法(哈希算法)实际上就是将一串数据(一般是数组或字符串)通过一些特定的方法转化成可以代表这些数据的一个数(Hash值)。通过哈希就可以快速的完成对这一串数据的一些比较,比如说当你要检验很多组字符串之间有哪些是一样的,就可以先算出各个字符串的Hash值,再通过比较Hash值是不是一样来替代更慢的普通字符串比较。

    或者说Hash是一种从大范围到小范围的映射,数组或者字符串是一组大范围的数据,而通过Hash处理后得到的Hash值就是一个小范围的数据(一般是一个整型)。

    从再数学一点的角度来看,Hash就是一个数学函数,你给它一些数据,它给你一个特征值。你给它的数据只能转化成一个特征值,同时理想状态下一个特征值只对应一组数据。

    要想做到让这个Hash值能够代表这一串数据,就需要使用乘法或者位运算(或者全都要)。

    Hash冲突

    由于Hash实际上是通过一些运算来计算出Hash值,所以有可能会出现明明是两个不同的字符串 $a , b $ ,但是最后却得到了一样的Hash值( (hash(a)==hash(b)) ),这种情况我们就叫做Hash冲突。

    当然并不是Hash所有都一定会有冲突(康托展开就是一个很好的例子),但是在面对由于数据太多的而不能保证无冲突的时候,我们要做的就是选择一个最好的Hash方式来尽可能的减小Hash冲突的发生率来保证运行结果的正确性。

    Hash种类

    Hash有超级多种,如果想要Hash冲突率更低,可以:

    1. 使用单独的一种hash,但通过改变乘数或者余数得到多组hash值来同时进行比较

    2. 使用多种不同的hash,得到多组hash值同时比较

    3. 使用hash表,将同一hash值的冲突的数据存在一个链表里,存在冲突时通过访问列表里的所有元素来确定

      ……

    反正方法有很多

    乘法Hash(进制Hash)

    最基本也是花样最多的一种哈希。(好像还叫BKDRHash)

    核心思想就是把字符串看成是一个26进制的数组(这个是对于纯小写纯大写的字符串,如果加上数字就是36进制,如果区分大小写就是52进制……)然后把他换算回十进制。

    如果不能确定取多少作为乘数的话,那就取33就行(好像如果进制数大于33,乘数取33也是不错的)。取31的原因主要有两点:

    1. ​ 33是一个奇质数(虽然偶质数就那一个),它可以保证因数最少,从而尽可能减少哈希冲突的发生;

    2. ​ 33在进行乘法运算时会更快,因为 (x*33)​​ 可以被优化成 ((x<<5)+x)​​

    如果得到的十进制数超出了 int 或者 long long 的范围,有下面几种方式来处理:

    1. 使用unsigned让它随便溢出,反正溢出了还是正数;

    2. ​ 取模:

      ​ hash里关于取模的模数(哈希因子)该怎么取是一个非常经验的东西,这里有一个常用哈希模数表,或者直接记两个:int 范围内 :(402653189) ; long long 范围内:(212370440130137957) (其实直接拿 (1e7+7)(1e9+9) 也是可以的)

    代码的话就是这样:

    ull hash(string x){
    	ull res=0;
    	int hash_base=33;
    	//int hash_mod=402653189;
    	for(int i=0;i<x.length();i++){
    		res=res*hash_base+x[i];
    		//res%=hash_mod;
    	}
    	return res;
    }
    

    位运算Hash

    位运算的hash快到起飞 而且也很好记

    它主要是通过异或和移位来让每一个数据都能影响到最后的hash值。相当于是让hash值的不同几位保存几个数据异或的结果。

    代码的话是这样:

    ull hash(string x){
    	ull res=0;
    	for(int i=0;i<x.length();i++){
    		res=(res<<4)^(res>>28)^x[i];
    	}
    	return res;
    }
    

    FNVHash

    乘法Hash的一种高级变种玩意。全称叫 Fowler-Noll-Vo算法

    它同时使用位运算和乘法来计算hash值。这玩意就是硬记一下hash初始值和乘数这个是固定的对应值,不要乱改):

    hash值位数 hash初始值 乘数
    32 位 2166136261 16777619
    64 位 14695981039346656037 1099511628211

    代码的话就是这样:

    ull hash(string x){
    	ull res=2166136261;
    	int FNV_prime=16777619;
    	for(int i=0;i<x.length();i++){
    		hash^=x[i];
            hash*=FNV_prime;
    	}
    	return hash;
    }
    

    其实上面这个是FNVHash的一种,叫FNV-1a ,还有就是交换了一下异或和乘的顺序的FNV-1 (他们说FNV-1a是要比FNV-1好一点,尽量用FNV-1a)

    Hash的应用

    其实只要扯到字符串判断啊、数组判断啊、枚举字符串减少重复枚举啊都可以用hash(想用就用就行)

    子串判断

    (其实这玩意应该说是乘法hash的应用)

    根据乘法hash的性质,我们可以得到这样一个递推求hash的方法:( 其实就是拿数组存了普通乘法hash里每一个的res值,相当于是当前字符串的hash值)

    ull hash_val[1000010]={0};
    ull hash(string x){
    	int hash_base=33;
    	for(int i=0;i<x.length();i++){
    		hash=res*base+x[i];
    	}
    	return hash;
    }
    

    如果我们现在有一个字符串 (x)​​ ,我们现在想要求 (x[l]sim x[r])​​ 这个区间的子串的值,我们只需要知道 (hash\_val[l])​​ 和 (hash\_val[r])​​​ 就可以计算出这个子串的hash值:

    [hash=hash\_val[r]-hash\_val[l-1]*base^{r-l+1} ]

  • 相关阅读:
    Codeforces Round #562 (Div. 2) B. Pairs
    Codeforces Round #562 (Div. 2) A.Circle Metro
    K.河北美食
    H.天神的密码
    国标GB28181协议智能分析告警平台EasyGBS及EasyCVR监控系统内iframe的常见问题说明
    【解决方案】国标GB28181平台EasyGBS级联EasyCVR视频智能分析搭建“蓝天卫士”网络视频监控系统技术方案
    【解决方案】基于国标GB28181协议/HIKSDK/Ehome协议EasyCVR智能融合分析平台在智慧校园人脸识别中的应用
    TSINGSEE青犀视频基于开源Webrtc服务器编译mediasoupClient运行报”SignalEncoderTimeOut, Encoder timed out”
    TSINGSEE青犀视频云边端H265播放器EasyPlayer-RTSP在C#版本增加OSD功能说明
    TSINGSEE青犀视频自主研发的H265播放器被集成后无法播放视频是什么原因?
  • 原文地址:https://www.cnblogs.com/lazy-people/p/15138030.html
Copyright © 2020-2023  润新知