算法---哈希及其在字符串中的应用(字符串hash)

这篇具有很好参考价值的文章主要介绍了算法---哈希及其在字符串中的应用(字符串hash)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

哈希算法

---前言

         "加密是信息时代的锁,密码是钥匙。" - 斯科特·莱普斯基(Scott Adams)

        当今,为了信息的存储安全,密码学兴起,哈希(hash)算法也由此应运而生,哈希算法是一种加密算法,是将一个数据转换为一个标志,这个标志和源数据有十分紧密的关系。哈希 算法还有一个特点,就是很难找到逆向的规律。所以在日常的使用中,哈希算法常被用来对字符串进行压缩编码,将一段字符串转化成对应的数字或其他标志,以方便存储和比较。

---正文

        ·整数哈希(哈希介绍)

        做为竞赛算法来说,整数哈希算法经常用来存储极大的数据以起到缩小数据规模的效果,而经过哈希算法处理后的数据会被存到一个数据结构-散列表(hash表)中,其采用映射的思想,可以通过关键字在表中快速查找数据,而Hash函数的设计也层出不穷,在算法竞赛中,最常用的就是取模法,下文也以取模法为例来介绍+实现。

举个例子,对于数据5,9,18,28,16来说,如果使用hash表存储,模数为7,那么就应该为

5:5 mod 7=5,存储在下标为5处。

9:9 mod 7=2,存储在下标为2处。

18:18 mod 7=4,存储在下标为4处。

28:28 mod 7=0,存储在下标为0处。

16: 16 mod 7=2,应当存储在下标为2处。

算法---哈希及其在字符串中的应用(字符串hash),算法,字符串,哈希,算法,哈希算法

        这时我们发现16和9的模数相同,也就是说,发生了哈希冲突,对于整数哈希冲突,也有许多不同的解决方案,此处,提供两种解决方案,开放地址法链地址法。

开放地址法:将当前的数向后(或向其他方向,不定)挪移,直到可以存储。

链地址法:将当前的数在该下标下再拉一条链,形成一个链表,并将该数存储到链表下。

        但是显而易见,这两种办法对于解决哈希冲突并不十分有效,所以我们要尽量减少哈希冲突的情况,一般而言,将模数设为足够大的质数可以有效减少哈希冲突出现的情况。

        不过,对于整数哈希,我们一般不需要手写哈希,可以使用C++的STL库中的自带的无序map:unordered_map和无序集合:unordered_set来代替手写整数哈希,故此处不附代码,若想手写直接按照上述过程模拟即可。

        ·字符串哈希(哈希拓展)

        经过上述介绍,我们知道利用哈希函数可以很快的查询和比较,而对于字符串来说,若要比较字符串是否相等,需要逐位比较,效率显然很低,时间复杂度为O(n),所以我们考虑使用哈希优化,而如何将字符串转成可以正常按位置比较大小的数字呢?,类比进制转换,我们把字符串看成一个128进制(ASCII码)的数字,每个字符相对应的就是其ASCII码值,再按权展开就能得到其哈希值,如abcd这个字符串,它的哈希值就是97*128^3+98*128^2+99*128^1+100*128^0,由此我们可以得到一个公式(其中hash[i]表示第1位到第i位的哈希值,base表示底数,mod表示模数,s是原字符串):

hash[i]=(hash[i-1]*base%mod+s[i]%mod)%mod

哈希值是可以进行正常比较的,我们类比十进制来理解,一个数按权展开后就是得到的原数,其值完全可以正常比较,所以哈希值也是可以进行字符串之间的正常比较的,而且它不需要逐位比较,效率比逐位比较更高,基本为O(1),这样就能极大程度的优化时间,使得程序不会超时。

        但是,一般来说,底数(即权值)最好要定义成一个质数(避免哈希冲突),所以一般会选择第一个比128大的质数131来做底数,不过,事实上,只要大小合理,且为质数,任何大于128的数都是一个不错的底数,但是最常用的底数仍然是131。

模板(求哈希值)

题面概括:

        给定一个字符串,求其对应的哈希值,对998244353取模。

思路:

        纯模板题,直接按上述过程模拟即可。

代码:文章来源地址https://www.toymoban.com/news/detail-835351.html

#include<bits/stdc++.h>
using namespace std;
long long has[100005];
const int mod=998244353;
int main(){
    string s;
    getline(cin,s);
    has[0]=s[0]%mod;
    for(int i=1;i<s.size();i++){
    	has[i]=(has[i-1]*128+(long long)s[i]%mod)%mod;
	}
	cout<<has[s.size()-1];
    return 0;
}

T2.查字典

题面概括:

        给定n组单词和其翻译后的单词,然后输入q个单词,将单词翻译后输出,如果没有对应的单词则输出“eh"。

思路:

        这道题也是模板题,先把原单词转为哈希值后存储,每输入一个需要翻译的单词,就将其的哈希值求出,并与之前求出的所有原单词的哈希值做比较,如果相等,输出记录的对应的翻译后的单词即可,如果没有相等的单词则输出”eh"。

代码:

#include<iostream>
using namespace std;
long long has[1005];
const int mod=998244353;
string k[1005];
int main(){
    int n,q;
    string s;
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>k[i]>>s;
        int len=s.size();
        for(int j=0;j<len;j++){
            if(j==len-1){
                has[i]=(has[i]+(long long)s[j])%mod;
            }
            has[i]=((has[i]+(long long)s[j])*128)%mod;
        }
    }
    cin>>q;
    for(int i=1;i<=q;i++){
        cin>>s;
        int sum=0;
        int len=s.size();
        for(int j=0;j<len;j++){
            if(j==len-1){
                sum=(sum+(long long)s[j])%mod;
            }
            sum=((sum+(long long)s[j])*128)%mod;
        }
        bool flag=0;
        for(int j=1;j<=n;j++){
            if(sum==has[j]){
                flag=1;
                cout<<k[j]<<endl;
            }
        }
        if(flag==0){
            cout<<"eh"<<endl;
        }
    }
    return 0;
}

T3.消失的密文

———————————————————————————————————————————

前置知识:

        对于一个已经求得下标从1开始所有长度哈希值的字符串来说,如何求它的子串呢?,我们考虑将哈希值当成前缀和的形式来求得,那么如果要求l~r这段子串的哈希值,是用hash[r]-hash[l-1]吗?答案是否定的,因为,hash[l-1]比hash[r]少乘了r-l+1个base(底数),导致指数无法对齐,所以我们就可以得到正确的公式(hash(l,r)表示l~r这段子串的哈希值,p[i]表示底数的i次方):

hash(l,r)=(hash[r]-(hash[l-1]*p[r-l+1])%mod+mod)%mod

———————————————————————————————————————————

题面概括:

        给定一个密码表S,S[i]表示第i个小写字母解密后对应的字母(下标由1开始),然后给出一条密文K,密文的末尾缺失了一部分单词,而完整密文的前半段是密文,后半段是解密后的”明文“,要求求出最短的完整的密文。

思路:

        这道题的难点,也是中心,就在于如何确定给出的密文中哪部分是密文,那部分是“明文”,也就是完整密文的中点位置,对于这道题来说,密文不是很长,所以可以使用枚举的方法来枚举中点,先用hash数组记录哈希值,再枚举中点k,取最前面n-k个字符的哈希值和k后面n-k个字符的哈希值(先将面n-k个字符转为明文后)作比较,如果两者相等就说明k是最小的中点,直接跳出循环并输出前k个密文和后k个明文即可。

代码:

#include<bits/stdc++.h>
using namespace std;
long long has[100005],p[100005],mhas[100005];
const int mod=998244353;
long long mid;
map<char,char> ma,pma;
int main(){
    int n;
    string s,b;
    cin>>n;
    for(int i=1;i<=n;i++){
    	ma.clear();
    	pma.clear();
        memset(has,0,sizeof has);
        memset(mhas,0,sizeof mhas);
        cin>>s>>b;
        b=' '+b;
        s=' '+s;
        for(int i=1;i<=s.size();i++){
        	ma['a'-1+i]=s[i];
        	pma[s[i]]='a'-1+i;
		}
        int len=b.size();
        len-=1;
        p[1]=1;
        for(int j=2;j<=len;j++){
            p[j]=p[j-1]*128;
        }
        for(int j=1;j<=len;j++){
            if(j==len){
                has[j]=has[j-1]+(long long)b[j];
                mhas[j]=mhas[j-1]+(long long)ma[b[j]];
            }
            has[j]=(has[j-1]+(long long)b[j])*128;
            mhas[j]=(mhas[j-1]+(long long)ma[b[j]])*128;
        }
        for(int j=(len+1)/2;j<=len;j++){
            long long hh=mhas[len]-mhas[j-1]*p[len-(j-1)+1];
            if(has[len-j+1]==hh){
                mid=j;
                break;
            }
        }
		for(int j=1;j<mid;j++){
			cout<<b[j];
		}
		for(int j=1;j<mid;j++){
			cout<<pma[b[j]];
		}
		cout<<"\n";
    }
    return 0;
}

T4.不同子串(双哈希)

———————————————————————————————————————————

前置知识:

        整数哈希中最重要的问题便是哈希冲突,而在字符串哈希中同样如此,由于字符串哈希的局特殊性,我们无法使用一些在整数哈希中使用的方法来解决哈希冲突,所以我们主要来通过方法使得哈希冲突的概率降到一个极其微小的地步,这里介绍一个方法:双哈希,顾名思义,就是对同一个字符串,使用两个底数不同且模数不同的哈希函数分别求值,在判断时要求两个哈希函数的值必须都一一对应才能判断两个字符串相等,这样能令哈希冲突基本不可能出现。

———————————————————————————————————————————

 题面概括:

        给出一个字符串S,求这个字符串中有多少长度不同的子串。

思路:

        这道题是一道经典的哈希例题,但是如果直接使用单哈希会被卡数据,所以考虑使用双哈希,与上一题相同,这道题也是枚举所有字串开头的字符下标位置(也可以是结尾字符下标位置),然后对没有出现的哈希值进行记录,并累加,最后累加的值即为答案,输出即可,注意,在记录和判断时要使用pair,否则会出现数据不对应导致答案错误。

代码:

//双哈希 
#include<bits/stdc++.h>
using namespace std;
long long hasa[1000005],hasb[1000005],p[1000005],p1[1000005];
const int mod=1e9+7,modb=998244353;
const int base=131,base1=128;
map<pair<long long,long long>,int> ma;
int main(){
    string s;
	int n,l;
	cin>>n>>l;
	cin>>s;
	s=' '+s;
	int len=s.size();
	len-=1;
	p[0]=p1[0]=1;
	for(int i=1;i<=len;i++){
	    p[i]=p[i-1]*base%mod;
	    p1[i]=p1[i-1]*base1%modb;
	}
	for(int i=1;i<=len;i++){
	    hasa[i]=((hasa[i-1]*base)%mod+(long long)s[i]%mod)%mod;
	    hasb[i]=((hasb[i-1]*base1)%modb+(long long)s[i]%modb)%modb;
	}
	long long cnt=0;
	for(int i=l;i<=len;i++){
	    long long xx=hasa[i]-hasa[i-l]*p[l]%mod;
	    long long yy=hasb[i]-hasb[i-l]*p1[l]%modb;
	    //cout<<i<<":"<<xx<<" "<<yy<<endl; 
	    if(ma[make_pair(xx,yy)]==0){
	    	cnt++;
		}
		ma[make_pair(xx,yy)]=1;
	}
	cout<<cnt;
	return 0;
}

到了这里,关于算法---哈希及其在字符串中的应用(字符串hash)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【JavaScript数据结构与算法】字符串类(反转字符串中的单词)

    个人简介 👀 个人主页: 前端杂货铺 🙋‍♂️ 学习方向: 主攻前端方向,也会涉及到服务端(Node.js) 📃 个人状态: 在校大学生一枚,已拿多个前端 offer(秋招) 🚀 未来打算: 为中国的工业软件事业效力 n 年 🥇 推荐学习:🍍前端面试宝典 🍉Vue2 🍋Vue3 🍓Vue2/3项目

    2023年04月09日
    浏览(74)
  • 前端算法题——字符串中的第一个唯一字符

    给定一个字符串,找到它的第一个不重复的字符,并返回它的索引。如果不存在,则返回 -1。 遍历字符串 用一个对象来记数,出现过一次就+1, 遍历完毕,再次遍历字符串,看它们在之前记录的对象里的值,是否是1,是就返回下标,不是返回-1。

    2024年02月22日
    浏览(28)
  • Python 中的字符串基础与应用

    在Python中,字符串可以用单引号或双引号括起来。\\\'hello\\\' 与 \\\"hello\\\" 是相同的。您可以使用print()函数显示字符串文字: 示例: 将字符串分配给变量是通过变量名后跟等号和字符串完成的: 示例 您可以使用三个引号将多行字符串分配给变量:示例,您可以使用三个双引号: 或

    2024年02月08日
    浏览(32)
  • 力扣热门算法题 349. 两个数组的交集,387. 字符串中的第一个唯一字符,394. 字符串解码

    349. 两个数组的交集,387. 字符串中的第一个唯一字符,394. 字符串解码,每题做详细思路梳理,配套PythonJava双语代码, 2024.04.02 可通过leetcode所有测试用例。 目录 349. 两个数组的交集 解题思路 完整代码 Python Java 387. 字符串中的第一个唯一字符 解题思路 完整代码 Python Java

    2024年04月08日
    浏览(30)
  • 算法:删除字符串中的所有相邻重复项

    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 目录 一、问题描述 二、栈解法 三、双指针解法 总结 提示:以下是本篇文章正文内容,下面案例可供参考 给出由小写字母组成的字符串str,重复项删除操作会选择两个相邻且相同的字母,并删除它们。

    2024年01月22日
    浏览(31)
  • 数据结构与算法之字符串: Leetcode 557. 反转字符串中的单词 III (Typescript版)

    翻转字符串中的单词 III https://leetcode.cn/problems/reverse-words-in-a-string-iii/ 描述 给定一个字符串 s ,你需要反转字符串中每个单词的字符顺序,同时仍保留空格和单词的初始顺序。 示例 1: 示例 2: 提示: 1 = s.length = 5 * 1 0 4 10^4 1 0 4 s 包含可打印的 ASCII 字符。 s 不包含任何开头或

    2024年02月01日
    浏览(63)
  • 哈希表+字符串

    一)知识回顾: 1)哈希表是什么?哈希表是存储数据的容器 2)哈希表有啥用?快速的查找某一个元素 3)什么时候使用哈希表?频繁的查找某一个数的时候, 当我们快速查找某一个数的时候,不光要想到哈希表还需要想到二分查找,但是二分查找算法的局限性太强了,必须数组中有序

    2024年02月10日
    浏览(26)
  • 2023-8-26 字符串哈希

    题目链接:字符串哈希

    2024年02月11日
    浏览(23)
  • LeetCode 2744.最大字符串配对数目:哈希表

    力扣题目链接:https://leetcode.cn/problems/find-maximum-number-of-string-pairs/ 给你一个下标从 0  开始的数组  words  ,数组中包含 互不相同  的字符串。 如果字符串  words[i]  与字符串 words[j]  满足以下条件,我们称它们可以匹配: 字符串  words[i]  等于  words[j]  的反转字符串。 0

    2024年01月22日
    浏览(31)
  • LeetCode-763. 划分字母区间【贪心 哈希表 双指针 字符串】

    给你一个字符串 s 。我们要把这个字符串划分为尽可能多的片段,同一字母最多出现在一个片段中。 注意,划分结果需要满足:将所有划分结果按顺序连接,得到的字符串仍然是 s 。 返回一个表示每个字符串片段的长度的列表。 示例 1: 输入:s = “ababcbacadefegdehijhklij” 输

    2024年04月10日
    浏览(34)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包