1686: 算法4-1,4-3:定位子串 + kmp算法
Description
若干对字符串,每对字符串占一行并用一个空格分开。前一个字符串为母串,后者为子串。字符串只包含英文字母的大小写。每个字符串不超过98个字符。
输出子串在母串中首次出现的位置,如果母串中不包含子串则输出0。每个整数占一行。
Input and Output
输入
ACMCLUB ACM
DataStructure data
domybest my
输出
1
0
3
Source
蓝桥杯ACM训练系统 http://www.dotcpp.com/oj/problem1686.html
Solution
方法一:滑窗法。时间是 O(n*m)
#include <iostream>
#include <cstring>
#define Max_nums 100
using namespace std;
int findSubIndex(char *str1, char* str2){
for(int i=0; i<=strlen(str1)-strlen(str2); i++){
int j=0;
// 这里开个大小strlen(str2)的滑窗
while(j<strlen(str2) && str1[i+j]==str2[j]) j++;
if(j==strlen(str2)) return i+1;
}
return 0;
}
int main(){
char str1[Max_nums],str2[Max_nums];
while(cin>>str1>>str2){
cout<< findSubIndex(str1, str2)<<endl;
}
return 0;
}
方法二:kmp算法。时间: O(m+n)
#include <iostream>
#include <cstring>
#define Max_nums 100
using namespace std;
void getNext(char *str, int *next){
//生成next数组值
next[0] = -1;
int k=-1, j=0;
while(j < strlen(str)){
if(k==-1 || str[k]==str[j])
next[++j] = ++k;
else
k=next[k];
}
}
int findSubIndex_KMP(char *str1, char *str2, int *next){
//查找模式串str2是否为主串str1的子串
//若是返回子串在母串中第一次出现的位置
//若不是则输出0
int i=0, j=0;
int len2=strlen(str2); //strlen()返回无符号整数,不能用于和负数比较
while(i<strlen(str1) && j<len2){
if(j==-1 || str1[i]==str2[j]){
i++;
j++;
}
else
j=next[j];
}
return j==strlen(str2) ? i+1-j : 0;
}
int main(){
char str1[Max_nums],str2[Max_nums];
int next[Max_nums];
while(cin>>str1>>str2){
getNext(str2, next);
cout<<findSubIndex_KMP(str1,str2,next)<<endl;
}
return 0;
}
kmp算法
一、参考:
二、字符匹配问题
设主串为:a b a c a a b a c a b a c a b a a b b
模式串为:a b a
问模式串是否为主串的一个子串,这即是一个字符串匹配问题。
-
常用的暴力解法是滑窗法(时间是O(n*m),n、m分别是主串、模式串数量级),在主串中取和模式串一样大小的窗,比较窗中字符串是否等于模式串,不等于这个窗就往右移1位(窗左边弹出,右边入新字符),... ...。
-
KMP算法是一种改进的字符串匹配算法。时间是O(n+m)。KMP算法的关键是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。具体实现就是实现一个next()函数,函数本身包含了模式串的局部匹配信息。
三、什么是next数组:
在简单的一次匹配失败后,我们会想将模式串尽量的右移和主串进行匹配。右移的距离在KMP算法中是如此计算的:在已经匹配的模式串子串中,找出最长的相同的前缀和后缀,然后移动使它们重叠。我们提前计算出每个匹配失败的位置应该移动的距离,存放在next()中。
例子1:
模式串 | A | B | C | D | A | B | D |
---|---|---|---|---|---|---|---|
j | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
next | -1 | 0 | 0 | 0 | 0 | 1 | 2 |
例子2:
| 模式串| A| B | A | B | A | B | C| A|
| :-------- | :--------😐 :------: | :--------😐 :------: | :--------😐 :------: |
| j| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7|
| next | -1 | 0| 0| 1| 2| 3| 4| 0|
例子3:
模式串 | A | A | A | A | A | B |
---|---|---|---|---|---|---|
j | 0 | 1 | 2 | 3 | 4 | 5 |
next | -1 | 0 | 1 | 2 | 3 | 4 |
我们换一个方式,结合算法解释一下 j 和 next 关系:
首先 j 是匹配过程中遍历模式串的指针、就是模式串字符数组下标。i 是遍历主串的指针,就是主串字符数组下标。
然后,匹配过程中,当到第 j 位出现字符不匹配时:
- 在滑窗法中, j 回归0(j=0),主串滑动选下一个窗口,i是需要回退的。
- 在kmp算法中, j回归到next[j](j=next[i]),主串的 i 不需要回退。
四、代码如下
生成next数组:
void getNext(char *str, int *next){
//生成next数组值
next[0] = -1;
int k=-1, j=0;
while(j < strlen(str)){
if(k==-1 || str[k]==str[j])
next[++j] = ++k;
else
k=next[k];
}
}
kmp算法
int findSubIndex_KMP(char *str1, char *str2, int *next){
//查找模式串str2是否为主串str1的子串
//若是返回子串在母串中第一次出现的位置
//若不是则输出0
int i=0, j=0;
int len2=strlen(str2); //strlen()返回无符号整数,不能用于和负数比较
while(i<strlen(str1) && j<len2){
if(j==-1 || str1[i]==str2[j]){
i++;
j++;
}
else
j=next[j];
}
return j==strlen(str2) ? i+1-j : 0;
}