字符串匹配
BF算法(朴素模式匹配)
時間復雜度O(m*n),普通的模式匹配算法
BF算法的思想就是將目標串S的第一個字符與模式串T的第一個字符進行匹配,若相等,則繼續比較S的第二個字符和 T的第二個字符;
若不相等,則比較S的第二個字符和T的第一個字符,依次比較下去,直到得出最后的匹配結果。BF算法是一種蠻力算法。
模板
輸出s2在s1里出現的次數
我寫的,兩行獲取兩行字符,第二行的字符串在第一行的字符串匹配,看看有幾個
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int maxn = 1e6 + 5;
int main(){
char str[maxn];
gets(str);
char s[maxn];
gets(s);
int len1 = strlen(str);
int len2 = strlen(s);
int cnt = 0;
for(int i = 0; i < len1; i++){
if(str[i] == s[0]){
int flag = 1;
for(int j = 0; j < len2; j++){
if(str[i + j] == s[j]){
}else{
flag = 0;
break;
}
}
if(flag)cnt++;
}
}
printf("%d\n",cnt);
return 0;
}
匹配成功,繼續,匹配失敗回溯
輸出第一次匹配的下標
#include <iostream>
#include <string.h>
using namespace std;
int BF(char a[], char b[]){
int index = 0;//第一個字符串的下標
int i = 0;//第一個字符串目前下標
int j = 0;//第二個字符串目前下標
int len1 = strlen(a);
int len2 = strlen(b);
while(i != len1 && j != len2){
if(a[i] == b[j]){
i++;
j++;
}else{
index++;
i = index;
j = 0;
}
}
if(j == len2) return index + 1;//匹配成功
else return 0;
}
int main(){
char a[200];
cout << "請輸入主串:";
cin >> a;
char b[200];
cout << "請輸入子串:";
cin >> b;
cout << "子串在主串首次匹配的位置是:" << BF(a,b) << endl;
return 0;
}
kmp算法
時間復雜度O(m+n)
對於朴素算法的一個缺點就是每次子串匹配失敗就會回溯,所以kmp針對這個問題,使得主串不需要回溯,只對子串進行回溯
而next就解決了這個問題,如果說主串和子串進行匹配,而在主串i與子串j位置失配,那么i前面和j前面那一段必定是匹配的,那么也就是說,可以采用回溯子串使得子串中與其前面具有相同結構部分進行回溯
前綴:指的是字符串的子串中從原串最前面開始的子串,如abcdef的前綴有:a,ab,abc,abcd,abcde
后綴:指的是字符串的子串中在原串結尾處結尾的子串,如abcdef的后綴有:f,ef,def,cdef,bcdef
注意,前綴與后綴均不包含本身
kmp算法最大的特點是加入了next[i]數組
next[i]的含義是對於字符串去掉下標為i的字符后的字符串的前綴與后綴的最長匹配
這是普遍的定義
求解next
動圖next求解
下面是基於清華數據結構教材的定義去求我絕對不是為了考試才這么去解釋的也就是字符串下標以0位開頭的
比如求ababa的next值
i | 字符 | 去掉i之后的字符 | 前綴 | 后綴 | next |
---|---|---|---|---|---|
0 | a | null | null | null | -1 |
1 | ab | a | null | null | 0 |
2 | aba | ab | a | b | 0 |
3 | abab | aba | ab | ba | 1 |
4 | ababa | abab | aba | bab | 2 |
考研題庫里面求出來的值是該方法求出的值加1,因為初始下標的定義不同,next[i]是進行匹配的,所以如果字符串從1開始,那就把next值加1,而字符串從0開始,就按照上面去求即可
void getnext(){
int i = 0,j = -1;
Next[0]=-1;
while(i < len2){
if(j == -1||t[i] == t[j]){
i++;
j++;
Next[i]=j;
}else{
j = Next[j];
}
}
}
主要i和j的起始位置即可,本文都是以下標0開始
優化kmp
當子串重復個數很多的時候,子串的回溯很浪費時間,所以加入了新的優化
比如aaaaaaaa進行匹配時,當最后一個a匹配失敗,就會一個一個地向前回溯,很浪費時間,如果最后一個a的next直接指到第一個a就好了
那么在求next時加一個判斷
if(T[i] == T[j])next[i] = next[j];
void getnext(){
int i = 0,j = -1;
Next[0]=-1;
while(i < len2){
if(j == -1||t[i] == t[j]){
i++;
j++;
if(t[i]!=t[j])Next[i]=j;
else Next[i] = Next[j];
}else{
j = Next[j];
}
}
}
如何手動求
只要在求得next[]之后,再遍歷一下,看t[i]和t[next[i]]是否相同,相同的話就把t[i]的next改了,next[i] = next[next[i]];
kmp
主要在於j的回溯
int kmp(){
int i = 0,j = 0;
while(i < len1 && j < len2){
if(j == -1||s1[i] == s2[j]){
i++;
j++;
}else j = Next[j];
}
return i - len2;
}
模板
傳送門
輸出匹配的首字母下標和next[]數組
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
char a[1000005];
char b[1000005];
int Next[1000005];
int len1, len2;
void getnext(){
int p = 0;//p是下標
Next[1] = 0;
for(int i = 2; i <= len2; i++){
while(p && b[i] != b[p + 1]){
p = Next[p];
}
if(b[p + 1] == b[i])p++;
Next[i] = p;
}
}
void kmp(){
int p = 0;
for(int i = 1; i <= len1; i++){
while(p && b[p + 1] != a[i])p = Next[p];
if(b[p + 1] == a[i])p++;
if(p == len2){
printf("%d\n", i - len2 + 1);
p = Next[p];//改成p=0的話是不重復
}
}
for(int i = 1; i <= len2; i++)
printf("%d ",Next[i]);
}
int main(){
scanf("%s", a + 1);
scanf("%s", b + 1);
len1 = strlen(a + 1);
len2 = strlen(b + 1);
getnext();
kmp();
return 0;
}
另一種寫法
#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
const int maxn = 1e6+5;
using namespace std;
int Next[maxn];
char s[maxn];
char t[maxn];
int len1, len2;
void kmp(){
int i = 0,j = 0;
while(i < len1){
if(j == -1||s[i] == t[j]){
i++;
j++;
}else{
j = Next[j];
}
if(j == len2){
printf("%d\n",i - len2 + 1);
j = Next[j];
}
}
}
void getnext(){
int i = 0,j = -1;
Next[0] = -1;
while(i < len2){
if(j == -1 || t[i] == t[j]){
i++;
j++;
Next[i] = j;
}else{
j = Next[j];
}
}
}
void print(){
for(int i = 1; i <= len2; i++){
printf("%d ", Next[i]);
}
}
int main(){
scanf("%s", s);
scanf("%s", t);
len1 = strlen(s);
len2 = strlen(t);
getnext();
kmp();
print();
return 0;
}
例題
要求匹配不重復,輸出匹配的個數
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
char a[1000005];
char b[1000005];
int Next[1000005];
int len1, len2;
int cnt;
void getnext(){
int p = 0;
Next[1] = 0;
for(int i = 2; i <= len2; i++){
while(p && b[i] != b[p+1]){
p = Next[p];
}
if(b[p + 1] == b[i])p++;
Next[i] = p;
}
}
void kmp(){
int p = 0;
for(int i = 1; i <= len1; i++){
while(p && b[p + 1] != a[i])p = Next[p];
if(b[p + 1] == a[i])p++;
if(p == len2){
cnt++;
p = 0;//達到不重復的目的
}
}
}
int main(){
while(1){
cnt = 0;
scanf("%s", a + 1);
if(strcmp(a + 1, "#") == 0)break;
scanf("%s", b + 1);
len1 = strlen(a + 1);
len2 = strlen(b + 1);
getnext();
kmp();
printf("%d\n", cnt);
}
return 0;
}
輸出匹配的第一個位置
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
int a[1000005];
int b[1000005];
int Next[1000005];
int len1, len2;
int cnt;
int flag = -1;
void getnext(){
int p = 0;
Next[1] = 0;
for(int i = 2; i <= len2; i++){
while(p && b[i] != b[p + 1]){
p = Next[p];
}
if(b[p + 1] == b[i])p++;
Next[i] = p;
}
}
void kmp(){
int p = 0;
for(int i = 1; i <= len1; i++){
while(p && b[p + 1] != a[i])p = Next[p];
if(b[p + 1] == a[i])p++;
if(p == len2){
flag = i - len2 + 1;
p = Next[p];//
return;
}
}
}
int main(){
int t;
cin >> t;
while(t--){
flag = -1;
int n, m;
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i++){
scanf("%d", &a[i]);
}
for(int j = 1; j <= m; j++){
scanf("%d", &b[j]);
}
len1 = n;
len2 = m;
getnext();
kmp();
printf("%d\n", flag == -1 ? -1 : flag);
}
return 0;
}