KMP & 字符串哈希

KMP

最关键的就是求next数组:

其实我们kmp的思想就是利用之前匹配成功的串,减少匹配次数,以降低时间复杂度;

简单来说就是一个最大前缀和一个最大后缀匹配的问题。

当我们在匹配过程中,匹配失败时,我们可以根据next数组,快速定位以上一个匹配成功的字符结尾的最大后缀。

我们时刻要想着我们求的是最大前缀和最大后缀相同,所以,上一次匹配成功后,我们拿上一次匹配成功的后一个字符和当前字符进行比较;

看看是否一致,如果不一致的话,我们则后退一步,如果还是不一致的话,那我们就接着退,直到退无可退;

如果一致后,那我们令我们的匹配的最大前后缀+1;因为我们没错是拿后面的和前面的匹配,所以我们的数组下标一定要从一开始,否则会越界;

 【模板】KMP字符串匹配 - 洛谷

#include<bits/stdc++.h>
using namespace std;

const int N = 1000010;

string s = " ",t = " ";

int border[N];

int main()
{
	string a , b;
	
	cin >> a >> b;
	s += a;
	t += b;
	
	int n = s.size();
	int m = t.size();
	
    //求next数组
	for(int i =2 ,j = 0 ;i <= m; i++)
	{
		while(j && t[i] != t[j+1]) j = border[j]; 
		if(t[i] == t[j+1]) j++;
		border[i] = j;  
	}
	
    //进行匹配
	for(int i = 1 ,j = 0 ;i<= n ; i++)
	{
		while(j && s[i] != t[j+1]) j = border[j];
		if(s[i] == t[j+1]) j++;
		if(j == m - 1)
		{
			cout<<i- m + 2 <<endl;
			j = border[j];
		} 
	}
	for(int i =1 ;i < m ;i++)
	{
		cout<<border[i]<<" ";
	}
	
	return 0;
} 

字符串哈希

字符串哈希说白了就是,将一个字符串映射成一个数字,这样我们每次询问的话都是O(1)的复杂度,接下来我们先来看看如何对字符串进行映射;

我们获取到一个字符串后,将该字符串看成是一个P进制的数;

我们知道如何将二进制转化成十进制;同理我们将一个字符串转化成P进制;

 这里我选取对应索引的ASCII作为P进制的乘数;这个数可以随便选取,前提是保证唯一性不为零

我们将上述字符串转化:(101 * P^0 + 99 *P^1 + 105 * P^2 + 118 * P^3 + 111 * P ^4 + 110* P ^5)mod Q

因为这个数算出来的话会很大,所以我们这里 mod 上一个大质数(为什么是质数,这个和数论有关)这样的话,我们就把一个字符串映射成了一个数。

(这里有人会问,会不会出现冲突,答案是很肯定的,一定会出现冲突,除非我不卡你,这是不可能的);

 那有什么用呢,我们可以对字符串进行询问,查看子串匹配问题等等问题;

那接下来我们就来看看如何求一个子串的hash值呢;

下面我们要求从L到R的hash值;

我们知道应该应 1-R 的hash值减去 1 - (L - 1)  的hash值;

我们考虑直接减去的答案对吗,显然是错误的;

因为两者的起点不一样;一个从 R 开始,一个从 L - 1 开始;

这里我们举一个简单的例子:

123456789:(L ~R) = (7 ~ 9) 

我们要获得789 能直接用123456789 - 12456吗;

不能;我们要用123456789 - 123456000;

我们要把(1 ~ L - 1) hash值乘上一个 P^{R-L+1},这样是不是就对了;

【模板】字符串哈希 - 洛谷

#include<iostream>
#include<cmath>
#include<cstring>
#include<algorithm>
#define x first
#define y second 
#define _for(i,s,t) for(int i = (s);i <=t ; i++)
using namespace std;

typedef unsigned long long ULL;
typedef long long LL;
typedef pair<int,int> PII;

const int N = 100010 ,base = 131,P = 9999971;

int n;

int h[N],idx;

int main()
{
	cin>>n;
	_for(i,1,n)
	{
		string str;
		cin>>str;
		int num = 0;
		int m = str.size();
		for(int j =0 ;j< m ;j++)
		{
			num = (num * base +str[j]) % P;
		} 
		h[idx] = num,idx++;
	}
	sort(h , h + idx);
	 
	int res = 0;
	
	for(int i =0 ;i<= n ;i++)
	{
		if(h[i]!=h[i+1]) res++;
	}
	
	cout<<res<<endl;
	return 0;
}

我们根据刚才学的一顿操作完后,喜提一发WA;我们人品不太行,被卡了;

既然我们用一个hash映射不行,那我们就用两个 ------- 双哈希;

所谓双哈希就是我们映射两次;

#include<iostream>
#include<cmath>
#include<cstring>
#include<algorithm>
#define x first
#define y second 
#define _for(i,s,t) for(int i = (s);i <=t ; i++)
using namespace std;

typedef unsigned long long ULL;
typedef long long LL;
typedef pair<int,int> PII;

const int N = 100010 ,base1 = 131,P1 = 9999971,base2 = 101 , P2 = 9999973;

int n;

int h1[N],h2[N],idx;

PII hah[N];

int main()
{
	cin>>n;
	_for(i,1,n)
	{
		string str;
		cin>>str;
		int num1 = 0, num2 = 0 ;
		int m = str.size();
		for(int j =0 ;j< m ;j++)
		{
			num1 = (num1 * base1 +str[j]) % P1;
			num2 = (num2 * base2 + str[j]) % P2;
		} 
		h1[idx] = num1 , h2[idx] = num2 , idx++;
	}
	
	for(int i =0 ;i< idx; i++)
	{
		hah[i] = {h1[i],h2[i]};
	}
	
	sort(hah, hah + idx);
	
	int res = 0;
	
	for(int i =0 ;i<= n ;i++)
	{
		int a1 = hah[i].x ,a2 = hah[i+1].x;
		int b1 = hah[i].y ,b2 = hah[i+1].y;
		
		if(a1 != a2 || b1 != b2) res++;
	}
	
	cout<<res<<endl;
	return 0;
}

Novice on the way !

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
字符串匹配算法是一种用来查找一个字符串(即目标串)在另一个字符串(即模式串)中的出现位置的算法。其中,KMP算法是一种比较常用的字符串匹配算法。 KMP算法的核心思想是通过利用模式串中已经匹配过的信息,来尽量减少目标串和模式串的比较次数,从而提高匹配效率。它利用一个最长公共前缀和最长公共后缀数组,记录模式串中已经匹配成功的前缀和后缀的长度。通过根据这些信息来移动模式串的位置,避免不必要的比较。 而字符串算法是一种将字符串映射为一个较短的固定长度的数值的算法。通过对字符串的每个字符进行一系列运算,如求幂、取模等,最终得到一个哈值。这个哈值可以代表该字符串的特征,不同字符串的哈值一般不会相同。 字符串算法的主要作用是将字符串转化为一个定长的数字,方便在数据结构中进行比较和存储。在字符串匹配中,使用哈算法可以将目标串和模式串转换为哈值,然后比较哈值是否相等来判断是否匹配。由于比较哈值的时间复杂度较低,使用字符串算法可以提高匹配效率。 总的来说,字符串匹配算法和字符串算法都是用来处理字符串匹配的问题。KMP算法通过利用已知信息来减少比较次数,提高匹配效率;而字符串算法则是将字符串转化为哈值,便于进行比较和存储。两者都在一定程度上提高了字符串匹配的效率。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

是饿梦啊

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值