【我的板子】用前缀函数和Z函数实现KMP

一、前缀函数知识

点击查看

给定一个文本t和一个字符串 s ,我们尝试找到并展示 s 在 t中的所有出现。为了简便起见,我们用n 表示字符串 s 的长度,用m 表示文本t 的长度
这种在一个字符串中查找另一个字符串的出现位置和次数,我们可以处理成一个字符串str=s+’#’+t;就转化成了在str中找最长公共前后缀长度为s.size()的位置,并记录个数

vector<int> prefix_function(string s) {
  int n = (int)s.length();
  vector<int> pi(n);
  for (int i = 1; i < n; i++) {
    int j = pi[i - 1];
    while (j > 0 && s[i] != s[j]) j = pi[j - 1];
    if (s[i] == s[j]) j++;
    pi[i] = j;
  }
  return pi;
}
string s,t,str;
cin>>s>>t;
str=s+'#'+t;
prefix_function(str);
int count=0;
int a[10000];
for(int i=0;i<str.size();++i)
{
	if(pi[i]==s.size())
	{
		a[++count]=str[i-s.size()-1];
	}
}
//所求a数组就是字符串s在字符串t中出现的下标,count是出现的次数。a数组从下标1开始	

二、用Z函数实现

1、什么是Z函数?

假设我们有一个长度为n的字符串s。该字符串的Z函数为一个长度为n的数组,其中第i个元素为满足从位置i开始且为s前缀的字符串的最大长度。
换句话说,z[i] 是s和从i开始的s的后缀的最大公共前缀长度。显然z[0]就应该等于字符串的长度。
示例:
z(a,a,a,a)=[4,3,2,1]
z(a,b,b,a)=[4,0,0,1]
z(a,a,a,b,a,a,b)=[7,2,1,0,2,1,0]

首先我们可以暴力计算这个Z函数

vector<int> z_function_trivial(string s) {
  int n = (int)s.length();
  vector<int> z(n);
  z[0]=n;
  for (int i = 1; i < n; ++i)
    while (i + z[i] < n && s[z[i]] == s[i + z[i]]) ++z[i];
  return z;
}

显然这是很低效的一种做法,我们思考怎么利用已有的值来简化计算

Z函数

我们仍按照从i=1到len- 1的顺序计算,但是循环每个i时不再
简单把z[i]赋初值为0,而是尽可能的使用之前计算过的值。
那么考虑一下当用暴力的方法得到一个z[i]值的时候,我们还
得到了什么。
如下图
0~r-l和l~r段是匹配的
0~j和l~i是匹配的
j~r-l和i~r是匹配的

所以从i开始的字符串找最长公共前缀的话,和从j开始的字符串找到最长公共前缀长度是有关系的
在这里插入图片描述

vector<int> z_function(string s) {
  int n = (int)s.length();
  vector<int> z(n);
  for (int i = 1, l = 0, r = 0; i < n; ++i) 
  {
    if (i <= r) z[i] = min(r - i + 1, z[i - l]);//i-l相当于上图中的j
    while (i + z[i] < n && s[z[i]] == s[i + z[i]]) ++z[i];
    if (i + z[i] - 1 > r) l = i, r = i + z[i] - 1;
  }
  return z;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值