字符串操作之哈希入门(持续更新!)


前言

如果现在有两个字符串,abcde和abcde,我们怎么知道他们相同的呢?显然,我们人脑是通过遍历第一个和第二个,确认前者和后者都相同,以此才能判断相同。但是如果是很多很大数据的时候,这种方法未免就显得慢了些,有什么更加快速的方法吗?
今天介绍哈希表,解决这种问题。


一、哈希是什么?

哈希表就是一种以 键-值(key-indexed) 存储数据的结构,我们只要输入待查找的值即key,即可查找到其对应的值。
我们通常使用简单的无序数组来实现:将键作为索引,值即为其对应的值,这样就可以快速访问任意键的值。这是对于简单的键的情况,我们将其扩展到可以处理更加复杂的类型的键。

说人话就是,如果把ASC表里的字符都看为一个个体,我们可以采用独特的方式将一串字符串转换为一个数字(通常会很大),以此来实现更加快速的查找,减少复杂度,防止卡常

二、使用步骤

使用哈希查找有两个步骤:

  1. 使用哈希函数将被查找的键转换为数组的索引。在理想的情况下,不同的键会被转换为不同的索引值,但是在有些情况下我们需要处理多个键被哈希到同一个索引值的情况。所以哈希查找的第二个步骤就是处理冲突

  2. 处理哈希碰撞冲突。

今天我们从入门开始!


三、实战操练

接来下我们通过一道题引入和思考:

洛谷P3370
题目描述

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

输入格式

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

输出格式

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

输入输出样例

5
abc
aaaa
abc
abcc
12345

输出样例

4

HINT
在这里插入图片描述

AC代码

#include<bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
const int base = 1324523;
const long long mod = 1e9+7;
typedef long long ll;
char s[10005];
ll ans[10005];

int main(){
    int n;
    cin >> n;
    memset(ans, 0, sizeof ans);
    for(int i=1;i<=n;i++){
        ans[i] = 0;
        scanf("%s",s);
        int len = strlen(s);
       //这里执行的就是哈希操作,我们把原字符串序列加权加载到ans数组中
        for(int j=0;j<len;j++)
            ans[i] = (ans[i]*base+(ll)s[j])%mod;
    }
    //进行排序,这样我们就知道哪些有没有重复
    sort(ans+1,ans+n+1);
    //查重操作
    int cnt = n;
    for(int i=1;i<=n-1;i++)
        if(ans[i]==ans[i+1])cnt--;
    
    printf("%d\n",cnt);
    return 0;
    }

一点说明:base代表的就是权值,通常采用的233,113等,不要取得太简单;mod就是模,我是喜欢用1e9+7,也可采用998244353(救救爸儿死死353,谐音梗好记hhh)

四、利用哈希实现字串查找比对

我们在上文中讲到哈希可以用于对比字符串,那么如果是字串的对比能实现吗?当然可以,我们先引入一道板子题吧
ZCMU-1953
1953: #103. 子串查找

Time Limit: 5 Sec Memory Limit: 256 MB
Submit: 329 Solved: 131
[Submit][Status][Web Board]
Description

这是一道模板题。

给定一个字符串 A 和一个字符串 B,求 B 在 A 中的出现次数。

A 中不同位置出现的 B 可重叠。

Input

输入共两行,分别是字符串 A 和字符串 B。

Output

输出一个整数,表示 B 在 A 中的出现次数。

Sample Input

zyzyzyz
zyz
Sample Output

3
HINT

1≤A,B 的长度 ≤106 ,A 、B 仅包含大小写字母。

说明:这道题用暴力查找呢当然也能过,既然这里讲的是哈希,我们就尝试用哈希实现

这里提示一下字串该怎么求:我们现在已经知道一整串的哈希加密后的字符是有对应的权值的,如果要知道l到r的长度的哈希值,就需要把r位的哈希值减去l-1位的哈希值乘上l-r+1的权值。

get_hash(int l,int r){
	return h[r]-h[l-1]*p[l-r+1];
	//注意:h数组内是各个位的权值,实际情况可能还需要取模防止爆表
}

以下展示完整AC代码:

#include <bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
const int base = 131;
const int mod = 1e9+7;
typedef long long ll;
typedef unsigned long long ull;
char str1[1000005],str2[1000005];
ll h[1000005],p[1000005];
int main(){
 ll ans1,ans2;
    for(int i=1;i<1000005;i++){
        p[i] =((p[i-1]%mod)*base)%mod;
    }
    while(~scanf("%s%s",str1,str2))
    {
        ans1 = ans2 = 0;
        int len1 = strlen(str1);
        int len2 = strlen(str2);
        
        for(int i=0;i<len2;i++)
        ans2 = (ans2*base+str2[i])%mod;
       // cout << ans2 << endl;
        
        int cnt = 0;
        h[0] = str1[0];
        p[0] = 1;
        //把每一位都存入h数组,同时将每一位的权值填入p数组
        for(int i=1;i<len1;i++){
            h[i] = (ull)(h[i-1]*base+(ull)str1[i])%mod;
            p[i] = (ull)((p[i-1]%mod)*base)%mod;
        }
        int l,r;
        for(int i=0;i<len1-len2+1;i++){
            //cout << h[i+len2-1]-h[i-1]*p[len2]<< endl;
            if(i==0){
                if(ans2==h[len2-1])cnt++;
            }
            //通过下方的才做才实现没有RTE错误,重要!
            else if(ans2==(((h[i+len2-1]-h[i-1]*p[len2])%mod+mod)%mod))cnt++;
        }
        printf("%d\n",cnt);
    }
    return 0;
    }

猜你喜欢

转载自blog.csdn.net/DAVID3A/article/details/114240142