BF算法
//BF算法的模式匹配
#include<iostream>
using namespace std;
#define M 20
#define N 20
int BF_search(char S[], char T[], int m, int n) { //主串、子串
int index = 0;
int i = 0, j = 0;
while ((i<=m) && (j<=n)) {
if (S[i] == T[j]) {
i++;
j++;
if (j >= n)
{
return index + 1;
}
}
else {
//不匹配的情况下进行回溯
//回溯的依据:子串回溯到开头位置,主串回溯到0--1--2--3--4--5
index++;
i = index;
j = 0;
}
}
}
int main() {
int m=0, n=0;
cout << "输入主串和子串长度:";
cin >> m >> n;
char S[M], T[N];//主串,子串
cout << "请输入主串:" << endl;
for (int i = 0; i < m; i++) {
cin >> S[i];
}
cout << "请输入子串:" << endl;
for (int i = 0; i < n; i++) {
cin >> T[i];
}
int res=0;
res=BF_search(S, T,m,n);
if (res > 0) {
cout << "子串在主串的" << res << "位置";
}
else {
cout << "未找到";
}
}
KMP算法
主串TEXT
子串PATTERN
A B A B A B C C A B C A C
B A B C C A B
前缀表
首先,需要一个前缀表。前缀表用来标识子串在每次移动的时候回溯到什么位置去。这样,可以避免每次都要从头开始查找。
0 | 1 | 2 | 3 | 4 | 5 | 6 |
B | A | B | C | C | A | B |
0 | 0 | 1 | 0 | 0 | 0 | 0 |
B 0
BA 0
BAB 1
BABC 0
BABCC 0
BABCCA 0
BABCCAB
传入三个参数:要比较的pattern表,要填入回溯信息的前缀表,pattern表的长度。默认前缀表prefix[0]的位置为0,从第二个元素(下标为1)开始进行比较。当i(prefix表的指示变量值)小于n(pattern表的长度)的时候,进行循环。由观察可知,当pattern[len]==prifix[i]的时候,最大匹配前缀len++,同时prefix[i]=len。当不相等的时候,为了防止数组出界,需要设定len>0, len的长度由prefix[len-1]即也就是自身位置的前一位
void prefix_table(char pattern[],int prefix[],int n){
//要比较的表 前缀表 表的长度
prefix[0]=0;
int len=0;//比较的长度
int i=1;
while(i<n){
if(pattern[i]==pattern[len]){
len++;
prefix[i]=len;
i++;
}
else{
//不相等的时候
if(len>0){
len=prefix[len-1];
//当两个字符串不适配的时候,
//回溯回退的位置始终是模式串的不适配字符的上一位字符前后缀相同的位置,
//求next数组,前缀与后缀不适配时,同样是移动模式串,
//只不过这个模式串是自身的前缀部分.而视频中的len恰好指向了前缀部分,
//但是要找自身的前一位 所以prefix【len-1】
}
else{
prefix[i]=0;
i++;
}
}
}
}
pattern移动
对prefix[]进行处理:将第一位prefix[0]=-1,将最后一位即prefix[n-1]舍弃。剩余均往后移动一位。
0 | 1 | 2 | 3 | 4 | 5 | 6 |
B | A | B | C | C | A | B |
0 | 0 | 1 | 0 | 0 | 0 | 0 |
-1 | 0 | 0 | 1 | 0 | 0 | 0 |
void move_prefix_table(int prefix[],int n){
int i;
for(i=n-1;i>0;i--){
prefix[i]=prefix[i-1];
}
prefix[0]=-1;
}
KMP搜索实现
当有了前缀数组以后,可以开始进行kmp查找了。规定:主串为text[i],长度为m;子串为pattern[j],长度为n。找到子串后,返回子串第一个下标的位置。
过程:当i<m时,进行循环。text[i]==pattern[j]的时候,i、j分别向后移动一位;否则,子串按照prefix数组进行回溯,重新找到j。当回溯过程中出现j=-1的情况时,意味着i和j都要向右移动一位。在主串中找到子串结束条件是:j=n-1且pattern[n-1]恰好等于text[i]。
*由于不能排除子串重读多次出现的原因,所以在找到以后仍然要通过prefix数组进行回溯,即j=prefix[j]。
void kmp_search(char text[],char pattern[]){
int n =strlen(pattern); //求出pattern的大小
int prefix[n];
prefix_table(pattern,prefix,n);
move_prefix_table(prefix,n);
//text[i] len=m
//pattern[j] len=n
int i=0,j=0;
int m=strlen(text);
while(i<m){
if(j==n-1&&text[i]==pattern[j]){
cout<<"found pattern at"<<i-j;
j=prefix[j];
}
if(text[i]==pattern[j]){
i++;
j++;
}
else{
j=prefix[j];
if(j==-1){
i++;
j++;
}
}
}
}
完整代码如下
#include<iostream>
#include<cstring>
using namespace std;
void prefix_table(char pattern[],int prefix[],int n){
//要比较的表 前缀表 表的长度
prefix[0]=0;
int len=0;//比较的长度
int i=1;
while(i<n){
if(pattern[i]==pattern[len]){
len++;
prefix[i]=len;
i++;
}
else{
//不相等的时候
if(len>0){
len=prefix[len-1];
//当两个字符串不适配的时候,
//回溯回退的位置始终是模式串的不适配字符的上一位字符前后缀相同的位置,
//求next数组,前缀与后缀不适配时,同样是移动模式串,
//只不过这个模式串是自身的前缀部分.而视频中的len恰好指向了前缀部分,
//但是要找自身的前一位 所以prefix【len-1】
}
else{
prefix[i]=0;
i++;
}
}
}
}
void move_prefix_table(int prefix[],int n){
int i;
for(i=n-1;i>0;i--){
prefix[i]=prefix[i-1];
}
prefix[0]=-1;
}
void kmp_search(char text[],char pattern[]){
int n =strlen(pattern); //求出pattern的大小
int prefix[n];
prefix_table(pattern,prefix,n);
move_prefix_table(prefix,n);
//text[i] len=m
//pattern[j] len=n
int i=0,j=0;
int m=strlen(text);
while(i<m){
if(j==n-1&&text[i]==pattern[j]){
cout<<"found pattern at"<<i-j;
j=prefix[j];
}
if(text[i]==pattern[j]){
i++;
j++;
}
else{
j=prefix[j];
if(j==-1){
i++;
j++;
}
}
}
}
int main(){
char pattern[]="ABABCABAA";
char text[]="AABABCABAACDGE";
kmp_search(text,pattern);
/*
int prefix[9];
int n=9;
prefix_table(pattern,prefix,n);
int i;
move_prefix_table(prefix,n);
for(i=0;i<n;i++){
cout<<prefix[i];
}*/
return 0;
}