BF算法&KMP算法

子串的匹配算法通常称为串的模式匹配或串匹配。通常使用的匹配算法有BF算法和KMP算法

主串S和模式串T,若匹配成功返回主串中首次出现的位置,否则返回-1;

BF算法:(暴力匹配算法)//只要失败就重新比较

时间复杂度:最优时间复杂度O(m+n);最坏时间复杂度O(mn);//n和m分别为主串和模式串的长度

算法步骤:(一般情况n>>m)

1)声明两个指针i与j分别指向主串S和模式串T当前待比较的字符位置。

2)如果i与j均未到达主串S和模式串T的尾部循环进行一下步骤:

如果S[i]==T[j]:则i++和j++; //两指针同时后移

反之S[i]!=T[j]:j=1,i=i-j+2;//j指针置1,i指针从上一次的下一个位置进行匹配

(推导:假设在j处失配,则在之前j-1必定匹配成功,那么在主串S中当前指针为i回退j-1位即到达上一次匹配位置进行+1操作便为下一次主串的匹配位置)   公式i=i-(j-1)+1 --> i=i-j+2

循序结束时:如果j>T.length(模式串T的长度),说明匹配成功,返回i-T.length;

                   反之匹配失败,返回-1;

BF代码(c++):

int BF(string S,string T,int pos){//BF算法 

	int i=pos;//起始遍历位置 
	int j=1;//起始的模式串位置
	while(i<=S.length()&&j<=T.length()){//均未到达两串的末尾
		if(S.at(i-1)==T.at(j-1)){
			i++;
			j++;//相同比较下一个 
		}
		else{
			i=i-j+2;//回退到上一次的位置的下一个(i-j+2)
			j=1;//重新匹配
		}
	}
	if(j>T.length()){
		return i-T.length();//返回的第一个字符的位置即为i的当前值减去模式串长度
	}
	return 0;//匹配失败返回0
}

注:虽然BF算法的时间复杂度为O(mn),但在一般情况下,执行时间近似于O(m+n)

KMP算法:KMP算法为BF算法的改进算法,是由Knuth、Morris、Pratt同时设计实现的,因此叫KMP算法

由于BF算法每次失配主串S都回退到上一次的下一个位置,模式串也回溯到起始位置,时间复杂度较高(举个例子)

     1 2 3 4 5 6 7 8 9 10 11 14 15

S:a b a b c a b c a c   b   a   b   //主串S

T:a b c a c                         //模式串T

第一次匹配失败时:当匹配到第3位时(c!=a)BF算法中主串回溯到第二位,模式串回溯到第一位,但显然b!=a

第二次匹配失败时:当匹配到j=5,i=7时(b!=c)BF算法中主串回溯到第四位,但是显然i=4、5位都不与j=1匹配。

以上问题说明BF算法不停的回溯,其实之前有很多元素都不用进行二次比较,能不能i指针不产生回溯,只是进行j指针的

移动打到匹配效果。即模式串不停的由移动与主串达到匹配。利用已经得到的“部分匹配”的结果,使得在比较过程中

模式串尽量大的向右移动,由此产生了KMP算法;

特点:主串S的i指针不回溯,模式串的j指针改变;通过部分匹配的结果让模式串尽可能的向右滑动。

  下标: 1 2 3 4 5 6 7 8 9 10 11 12 13
  主串S:a b a b c a b c a  c  b  a  b
模式串T:a b c a c
主串与模式串的指针分别为i与j初始化为每个串的第一个元素下标  即i=j=1
主串中的i指针不回溯一直进行自加操作(++);模式串中的j指针回溯。
第一次匹配:i从1开始;当i=j=3时,模式串第三位匹配失败。
    i                 i=3
a b a b c
a b c
    j                 j=1..3
j指针回溯到1重新匹配,模式串向右滑动2位,j=1开始                 //j=j-2
                                               (j为已经匹配的字符数+1)

第二次匹配:i从3开始;当i=7,j=5时匹配失败,模式串第5位匹配失败。//j=j-3
                                               (j为已经匹配的字符数+1)
            i         i=7  
a b a b c a b c a c
    a b c a c
    j       j         j=1--5
经分析得到如果按照BF算法中i要回溯到4则i=4,i=5都与模式串的j=1不匹配,所以比较没有意义,浪费资源 
j指针回溯到2重新匹配,模式串向右滑动3位,j=2开始

第三次匹配:i从7开始;模式匹配成功
                  i   i=10
a b a b c a b c a c
          a b c a c

          j       j   j=1--5

一般情况:
假设主串为s1s2s3...sn
   模式串为t1t2t3...tm   (n>>m)主串一般远大于模式串
目前要解决的问题是:当匹配失败时j要回溯到何处与i值进行匹配(即模式串的移动,j中的那一个元素与i对应)
假设此时与模式串的第k个元素进行比较(k<j)则说明在模式串中的前k-1个字符与主串相互匹配,
即满足以下关系式:T[1...k-1]==S[i-k+1...i-1]                //已经匹配成功的部分(关系式1)   
(k-1为比较成功的字符串长度,比较第k位时主串中的指针一定在第i位,所以成功比较的元素在主串中
的位置范围为[i-k+1,i-1])
由部分匹配的结果:T[j-k+1...j-1]==S[i-k+1...i-1]     //k为相同前缀与后缀子串的最大长度

(第j位置匹配失败,前k-1项匹配成功T串中范围[j-k+1,j-1])    //(关系式2)

得到:T[1...k-1]==T[j-k+1...j-1](关系式3)       //k位之前的k-1个字符与j位之前的k-1个字符相同

(记住关系式3这是求解k的一个条件     存在前后缀最大相同子串)

结论:在匹配过程中主串中的第i个字符与模式中的第j个字符比较不等时(S[i]!=T[j]),仅需要将模式
串向右滑动至模式中的第k个字符,与主串中的第i个字符对齐,此时的模式中的k-1个子串必然和主串满足
关系式1,在从j=k位置依次与i对应位置的元素进行比较。(j回溯的位置即为k  j=k)
令next[j]=k为当在第j位比较失败时,模式中重新与此该主串中字符比较的字符位置。

(模式串向右移动的位数为:已匹配的字符数-失配字符上一位字符所对应的前后缀公共元素的最大长度)

定义next函数

            0  j=1(t1与si比较不同时,下一步比较t1和si+1)
next[j]=Max{k|1<k<j 且 t1t2t3...tk-1==tj-k+1tj-k+2...tj-1}

            1  k=1(不存在相同的子串,下一步比较t1和si)

当为0时为第一个元素的初始化;k=1表明为第一个元素无相同子串next[j]置1;其他说明存在前后缀最大子串,为最大的长度

总之next计算方式:(next数组的值只与模式串有关)

初始化-->  next[1]=0;

如果T[i]==T[j]  则i++,j++  next[j+1]=next[j]+1

如果T[i]!=T[j] 则j=next[j];    //一直回溯到0之后置1  否则为next[j+1]=next[k]+1;  k为中间回溯位置

KMP算法代码:

int KMP(char s1[],char s2[],int pos){//pos为匹配的起始位置
     int i=pos;                      //s1为主串,s2为模式串
     int j=1;
     while(i<=strlen(s1)&&j<=strlen(s2)){
          if(j==0||s1[i-1]==s2[j-1]){//或者j回退到0
             i++;
             j++;//如果匹配相同,同时后移
          }
          else{
             j=next[j];//如果不同j回退到next[j]处于i位置元素重新比较
          }
     }
     if(j>strlen(s2)){//匹配成功
          return i-strlen(s2);//i当前位置减去串长即为初始位置
     }
     return 0;
} 

next数组的计算:

void getnext(char s[]){
	//由模式确定next
	int i=1;
	next[1]=0;//初始化为0
	int j=0;
	while(i<strlen(s)){//i指针一直后移
		if(j==0||s[i-1]==s[j-1]){//如果相等
			++i;
			++j;//i与j指针同时后移
			next[i]=j;
		}
		else{//如果不等且未回溯到0
			j=next[j];//j回溯至next[j]
		}
	}
}

next数组的优化:

void getnextal(char s[]){
	//由模式确定next
	int i=1;
	next[1]=0;//初始化为0
	int j=0;
	while(i<strlen(s)){//i指针一直后移
		if(j==0||s[i-1]==s[j-1]){//如果相等
			++i;
			++j;//i与j指针同时后移
			if(s[i-1]!=s[j-1])
			   next[i]=j;
			else
			   next[i]=next[j];
		}
		else{//如果不等且未回溯到0
			j=next[j];//j回溯至next[j]
		}
	}
}

最后附上一道题目:(心累看来strlen函数不能乱调用,提前保存吧!!!)

数据结构实验之串一:KMP简单应用

Time Limit: 1000 ms Memory Limit: 65536 KiB

Problem Description

给定两个字符串string1和string2,判断string2是否为string1的子串。

Input

 输入包含多组数据,每组测试数据包含两行,第一行代表string1(长度小于1000000),第二行代表string2(长度小于1000000),string1和string2中保证不出现空格。

Output

 对于每组输入数据,若string2是string1的子串,则输出string2在string1中的位置,若不是,输出-1。

Sample Input

abc
a
123456
45
abc
ddd

Sample Output

1
4
-1
#include<stdio.h>
#include<string.h>
#define N 1000005
char s2[N];//模式
char s1[N];//主
int next[N];
void getnext(char s[]){
	//由模式确定next
	int i=1;
	next[1]=0;//初始化为0
	int j=0;
	while(i<strlen(s)){//i指针一直后移
		if(j==0||s[i-1]==s[j-1]){//如果相等
			++i;
			++j;//i与j指针同时后移
			next[i]=j;
		}
		else{//如果不等且未回溯到0
			j=next[j];//j回溯至next[j]
		}
	}
}
void getnextal(char s[]){
	//由模式确定next
	int i=1;
	next[1]=0;//初始化为0
	int j=0;
	int len=strlen(s);
	while(i<len){//i指针一直后移
		if(j==0||s[i-1]==s[j-1]){//如果相等
			++i;
			++j;//i与j指针同时后移
			if(s[i-1]!=s[j-1])
			   next[i]=j;
			else
			   next[i]=next[j];
		}
		else{//如果不等且未回溯到0
			j=next[j];//j回溯至next[j]
		}
	}
}
int KMP(char s1[],char s2[],int pos){//pos为匹配的起始位置
     int i=pos;                      //s1为主串,s2为模式串
     int j=1;
	 int len1=strlen(s1);
	 int len2=strlen(s2);
     while(i<=len1&&j<=len2){
          if(j==0||s1[i-1]==s2[j-1]){//或者j回退到0
             i++;
             j++;//如果匹配相同,同时后移
          }
          else{
             j=next[j];//如果不同j回退到next[j]处于i位置元素重新比较
          }
     }
     if(j>strlen(s2)){//匹配成功
          return i-strlen(s2);//i当前位置减去串长即为初始位置
     }
     return -1;
} 
int main(){
	while(scanf("%s",s1)!=EOF){//主
		scanf("%s",s2);//模式串
		getnextal(s2);
		printf("%d\n",KMP(s1,s2,1));
	}
	return 0;
}


猜你喜欢

转载自blog.csdn.net/m0_37848958/article/details/80230856