首页 > 其他分享 >字符串哈希详解

字符串哈希详解

时间:2025-01-20 22:29:39浏览次数:3  
标签:hash 哈希 ull long 详解 base 字符串 Hash

哈希函数的选取

通常我们采用的是多项式 Hash 的方法,对于一个长度为 l 的字符串 s 来说,我们可以这样定义多项式 Hash 函数:其中,M需要选择一个素数(至少要比最大的字符要大),b 是一个比最大字符大的整数。(ASCII码值比较)

之所以选择这样的哈希函数,不仅是因为它不容易产生哈希碰撞(就是不同的字符串出现相同的值的情况),还因为利用这个函数可以比较方便求出子串的哈希值

(1)先求前缀子串:s[0],s[0,1],s[0~2],...,s[0~n-1]的哈希值:类似与求前缀和。

例如s=“abcde”。基数为base, 先不考虑取模。

hash[0]=a
        hash[1]=a*base+b=hash[0]*base+s[1]
        hash[2]=a*base*base+b*base+c=(a*base+b)*base+c=hash[1]*base+s[2]
        hash[3]=a*base*base*base+b*base*base+c*base+d
                =(a*base*base+b*base+c)*base+d
                =hash[2]*base+s[3]
则:hash[0]=s[0],i>0时:hash[i]=hash[i-1]*base+s[i]

(2)求任意一子串s\[ l~r ]的哈希值h,带入上方的哈希函数得到:

h= s[l]* base^{r-l} + s[l+1]* base^{r-l-1} + ... +s[r]
hash[l-1]=s[0] * base^{l-1}+ s[1]*base^{l-2}+...+s[l-1]
hash[r]= s[0] * base^r+ s[1]* base^{r-1}+...+ s[l-1] * base^{r-l+1}+s[l] * base^{r-l}+...+ s[r]
故h=hash[r] - hash[l-1] * base^{r-l+1}

int base = 131;
vector<ull> hash(n+1);
vector<ull> mul(n+1);
mul[0]=1;

for(int i=1;i<=n;i++){
	hash[i] = hash[i-1]*base+(ull)text[i-1]; //hash[i]:前i个字母的哈希值
    mul[i] = mul[i-1]*base;     //mul[i]:base的i次方
}

M的选取

(1)将b和M尽量取大即可,越大值域越大,冲突越小。

M 最大可以取多大?    unsigned long long的最大值:2^64-1,刚好由于C++语言的特性,对于unsigned long long  a,若变量a保存的值超过2^64-1时,会自动对  2^64-1取余。所以所有变量的值都用unsigned long long即可,这种方法叫做自然取余法

2)双Hash法:一个字符串用不同的Base和MOD,hash两次,将这两个结果用一个二元组表示,作为一个总的Hash结果。
相当于我们用不同的Base和MOD,进行两次单Hash方法操作,然后将得到的结果,变成一个二元组结果,这样子,我们要看一个字符串,就要同时对比两个Hash 值,这样出现冲突的概率就很低了。MOD选择两个10^9级别的质数,只有模这两个数都相等才判断相等,目前暂时没有办法能卡掉这种写法(除了卡时间让它超时),比较好用的两个质数:

b(进制)的选取

关于进制的选择实际上非常自由,大于所有字符对应的数字的最大值,不要含有模数的质因子(那还模什么),比如一个字符集是a到z的题目,选择27、**131**、19260817 都是可以的。(131比较好用)

注意:
1. 只针对上述哈希函数有可以求子串哈希值这样的特性,其他哈希函数不一定有
2. 不要把任意字符对应到数字0,比如假如把a对应到数字0,那么将不能只从Hash结果上区分ab和b,因为都是b的值,一般而言,把a-z对应到数字1-26比较合适。也可以直接使用字母本身对应的ASCll码值求哈希值。

双哈希法求不重复字符串的示例:

//双哈希法求不重复字符串个数
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

typedef unsigned long long ull;
ull base=131;

struct data
{
    ull x,y;
}a[10010];

char s[10010];
int n,ans=1;
ull mod1=19260817;
ull mod2=19660813;

//求两种不同哈希值
ull hash1(char s[])
{
    int len=strlen(s);
    ull ans=0;
    for (int i=0;i<len;i++)
        ans=(ans*base+(ull)s[i])%mod1;
    return ans;
}


ull hash2(char s[])
{
    int len=strlen(s);
    ull ans=0;
    for (int i=0;i<len;i++)
        ans=(ans*base+(ull)s[i])%mod2;
    return ans;
}

bool comp(data a,data b)
{
    return a.x<b.x;
}

int main()
{
    scanf("%d",&n);
    for (int i=1;i<=n;i++)
    {
        scanf("%s",s);
        //求每个字符串对应得两个哈希值,放到a数组里
        a[i].x=hash1(s);  
        a[i].y=hash2(s);
    }
	//排序后分别对比相邻的前一个字符串两个哈希值是否相同,不相同则为不同字符串
    sort(a+1,a+n+1,comp);
    for (int i=2;i<=n;i++)
        if (a[i].x!=a[i-1].x || a[i-1].y!=a[i].y)
            ans++;
    printf("%d\n",ans);
}

[不同的循环子字符串](https://leetcode.cn/problems/distinct-echo-substrings/) 1837
[子串的最大出现次数](https://leetcode.cn/problems/maximum-number-of-occurrences-of-a-substring/)

[2261. 含最多 K 个可整除元素的子数组](https://leetcode.cn/problems/k-divisible-elements-subarrays/)查找数组中最多k个可以整除p的元素的子数组个数(长度相同,元素也相同的子数组是相同的子数组)

标签:hash,哈希,ull,long,详解,base,字符串,Hash
From: https://blog.csdn.net/m0_74136087/article/details/145270002

相关文章

  • 项目管理-------十大管理-输入-输出-工具和技术详解
    在项目管理的广袤领域中,存在着十大核心知识领域,它们如同精密运转的齿轮,相互协作,共同推动项目从概念走向成功交付。这些管理领域涵盖了项目从启动到收尾的全生命周期,是确保项目顺利进行、达成目标的关键所在。接下来,让我们逐一深入探究这十大管理领域,包括它们的输入、输出、......
  • 【数据库】详解MySQL数据库索引
    目录1.介绍2.索引概述2.1.优缺点3.索引结构3.1.B+Tree索引3.2.Hash索引4.索引分类5.索引语法5.1.创建索引5.2.查看索引5.3.删除索引6.SQL性能分析6.1.慢查询日志6.2.profile详情6.3.explain执行计划7.索引使用7.1索引使用原则7.1.1.最左前缀法则7.1.2.索引......
  • 【C++提高篇】—— C++泛型编程之模板基本语法和使用的详解
    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档文章目录前言一、模板的概念二、函数模板2.1函数模板的使用2.2函数模板注意事项2.3普通函数与函数模板的区别2.4普通函数与函数模板的调用规则2.5模板的局限性三、类模板3.1类模板的使用3.2类模板......
  • TIA SCL编程清除字符串中所有的空格
    今天做一个小的练习,这是2025年第一个记录的学习笔记。在IA新建一个FC,名字叫做TrimSpace,建立以下内部变量: 写一段SCL代码:#len:=LEN(#str_in);#str_trim_out:='';FOR#i:=1TO#lenDOIFMID(IN:=#str_in,L:=1,P:=#i)<>''THEN#str_t......
  • 【leetcode 22】541. 反转字符串II
    思路:其实在遍历字符串的过程中,只要让i+=(2*k),i每次移动2*k就可以了,然后判断是否需要有反转的区间。因为要找的也就是每2*k区间的起点,这样写,程序会高效很多。classSolution{publicStringreverseStr(Strings,intk){char[]ch=s.toCh......
  • 内存字符串有关问题
    问题一:#include<iostream>#include<cstdint>#include<cstring>usingnamespacestd;typedefstructdata{charhwid[4];charsip[4];charrev[4];}Data;intmain(){Datastdata;memset(&stdata,0,sizeof(stdata));......
  • Python识别处理验证码技术详解
    目录一、验证码的种类二、OCR技术简介三、使用OCR技术识别验证码1.安装所需库2.下载和处理验证码图片3.使用OCR进行识别4.完整代码示例四、处理复杂验证码五、案例:识别古诗文网验证码六、总结验证码作为一种常见的安全手段,广泛应用于各种网站和应用中,以防止......
  • GBase UCASE 和 UPPER 函数详解
    UCASE 和 UPPER 是两个用于将字符串中的字符转换为大写形式的SQL函数。它们在数据处理、报告生成、文本分析以及各种需要统一字符串格式的场景中非常实用。通过这些函数,用户可以确保数据的一致性,方便后续的比较和分析操作。1. UCASE 和 UPPER 函数的基本语法这两个函数在......
  • JavaScript详解十二 ——事件概述、操作元素
    1、事件概述JS使我们有能力创建动态页面,而事件是可以被JS侦测的行为简单理解:触发----响应机制网页中每个元素都可以产生某些可以触发JS的事件,例如点击事件事件是由三部分组成事件源事件类型事件处理程序称为事件三要素事件源:事件被触发的对象谁被触发事件类型:如何触......
  • JavaScript详解十三 ——节点操作
    节点操作1、创建节点docment.createElement('节点')参数:标签名字符串这些元素原先不存在,是根据需求动态生成的,所以也成为动态创建元素节点,会将创建好的对象作为返回值返回2、创建文本document.createTextNode()可以用来创建一个文本节点对象参数:文本内容字符串,并将新的......